1 //------------------------------------------------------------------------------
2 // <copyright file="ConfigurationProperty.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 
7 using System;
8 using System.ComponentModel;
9 using System.Security.Permissions;
10 using System.Collections.Generic;
11 using System.Collections.Specialized;
12 using System.Reflection;
13 using System.Text;
14 using System.Diagnostics.CodeAnalysis;
15 
16 namespace System.Configuration {
17 
18     public sealed class ConfigurationProperty {
19         internal static readonly ConfigurationValidatorBase NonEmptyStringValidator = new StringValidator(1);
20         private readonly static ConfigurationValidatorBase DefaultValidatorInstance = new DefaultValidator();
21         internal static readonly string DefaultCollectionPropertyName = "";
22         private string _name;
23         private string _providedName;
24         private string _description;
25         private Type _type;
26         private Object _defaultValue;
27         private TypeConverter _converter;
28         private ConfigurationPropertyOptions _options;
29         private ConfigurationValidatorBase _validator;
30         private String _addElementName = null;
31         private String _removeElementName = null;
32         private String _clearElementName = null;
33         private volatile bool _isTypeInited;
34         private volatile bool _isConfigurationElementType;
35 
ConfigurationProperty(String name, Type type)36         public ConfigurationProperty(String name, Type type) {
37             object defaultValue = null;
38 
39             ConstructorInit(name, type, ConfigurationPropertyOptions.None, null, null);
40 
41             if (type == typeof(string)) {
42                 defaultValue = String.Empty;
43             }
44             else if (type.IsValueType) {
45                 defaultValue = TypeUtil.CreateInstanceWithReflectionPermission(type);
46             }
47             SetDefaultValue(defaultValue);
48         }
49 
ConfigurationProperty(String name, Type type, Object defaultValue)50         public ConfigurationProperty(String name, Type type, Object defaultValue)
51             : this(name, type, defaultValue, ConfigurationPropertyOptions.None) {
52         }
53 
ConfigurationProperty(String name, Type type, Object defaultValue, ConfigurationPropertyOptions options)54         public ConfigurationProperty(String name, Type type, Object defaultValue, ConfigurationPropertyOptions options)
55             : this(name, type, defaultValue, null, null, options) {
56         }
57 
ConfigurationProperty(String name, Type type, Object defaultValue, TypeConverter typeConverter, ConfigurationValidatorBase validator, ConfigurationPropertyOptions options)58         public ConfigurationProperty(String name,
59                                      Type type,
60                                      Object defaultValue,
61                                      TypeConverter typeConverter,
62                                      ConfigurationValidatorBase validator,
63                                      ConfigurationPropertyOptions options)
64             : this(name, type, defaultValue, typeConverter, validator, options, null) {
65         }
66 
ConfigurationProperty(String name, Type type, Object defaultValue, TypeConverter typeConverter, ConfigurationValidatorBase validator, ConfigurationPropertyOptions options, string description)67         public ConfigurationProperty(String name,
68                                      Type type,
69                                      Object defaultValue,
70                                      TypeConverter typeConverter,
71                                      ConfigurationValidatorBase validator,
72                                      ConfigurationPropertyOptions options,
73                                      string description) {
74             ConstructorInit(name, type, options, validator, typeConverter);
75 
76             SetDefaultValue(defaultValue);
77         }
78 
ConfigurationProperty(PropertyInfo info)79         internal ConfigurationProperty(PropertyInfo info) {
80             Debug.Assert(info != null, "info != null");
81 
82             // Bellow are the attributes we handle
83             TypeConverterAttribute attribConverter = null;
84             ConfigurationPropertyAttribute attribProperty = null;
85             ConfigurationValidatorAttribute attribValidator = null;
86 
87             // Compatability attributes
88             // If the approprite data is provided in the ConfigPropAttribute then the one bellow will be ignored
89             DescriptionAttribute attribStdDescription = null;
90             DefaultValueAttribute attribStdDefault = null;
91 
92             TypeConverter typeConverter = null;
93             ConfigurationValidatorBase validator = null;
94 
95             // Find the interesting attributes in the collection
96             foreach (Attribute attribute in Attribute.GetCustomAttributes(info)) {
97                 if (attribute is TypeConverterAttribute) {
98                     attribConverter = (TypeConverterAttribute)attribute;
99                     typeConverter = TypeUtil.CreateInstanceRestricted<TypeConverter>(info.DeclaringType, attribConverter.ConverterTypeName);
100                 }
101                 else if (attribute is ConfigurationPropertyAttribute) {
102                     attribProperty = (ConfigurationPropertyAttribute)attribute;
103                 }
104                 else if (attribute is ConfigurationValidatorAttribute) {
105                     // There could be more then one validator attribute specified on a property
106                     // Currently we consider this an error since it's too late to fix it for whidbey
107                     // but the right thing to do is to introduce new validator type ( CompositeValidator ) that is a list of validators and executes
108                     // them all
109 
110                     if (validator != null) {
111                         throw new ConfigurationErrorsException(SR.GetString(SR.Validator_multiple_validator_attributes, info.Name));
112                     }
113 
114                     attribValidator = (ConfigurationValidatorAttribute)attribute;
115                     attribValidator.SetDeclaringType(info.DeclaringType);
116                     validator = attribValidator.ValidatorInstance;
117                 }
118                 else if (attribute is DescriptionAttribute) {
119                     attribStdDescription = (DescriptionAttribute)attribute;
120                 }
121                 else if (attribute is DefaultValueAttribute) {
122                     attribStdDefault = (DefaultValueAttribute)attribute;
123                 }
124 
125             }
126 
127             Type propertyType = info.PropertyType;
128             // Collections need some customization when the collection attribute is present
129             if (typeof(ConfigurationElementCollection).IsAssignableFrom(propertyType)) {
130                 ConfigurationCollectionAttribute attribCollection =
131                     Attribute.GetCustomAttribute(info,
132                                                     typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;
133 
134                 // If none on the property - see if there is an attribute on the collection type itself
135                 if (attribCollection == null) {
136                     attribCollection =
137                         Attribute.GetCustomAttribute(propertyType,
138                                                         typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;
139                 }
140                 if (attribCollection != null) {
141                     if (attribCollection.AddItemName.IndexOf(',') == -1) {
142                         _addElementName = attribCollection.AddItemName;
143                     }
144                     _removeElementName = attribCollection.RemoveItemName;
145                     _clearElementName = attribCollection.ClearItemsName;
146 
147                 }
148 
149             }
150 
151             // This constructor shouldnt be invoked if the reflection info is not for an actual config property
152             Debug.Assert(attribProperty != null, "attribProperty != null");
153 
154             ConstructorInit(attribProperty.Name,
155                                 info.PropertyType,
156                                 attribProperty.Options,
157                                 validator,
158                                 typeConverter);
159 
160             // Figure out the default value
161             InitDefaultValueFromTypeInfo(attribProperty, attribStdDefault);
162 
163             // Get the description
164             if ((attribStdDescription != null) && !string.IsNullOrEmpty(attribStdDescription.Description)) {
165                 _description = attribStdDescription.Description;
166             }
167         }
168 
ConstructorInit(string name, Type type, ConfigurationPropertyOptions options, ConfigurationValidatorBase validator, TypeConverter converter)169         private void ConstructorInit(string name,
170                                         Type type,
171                                         ConfigurationPropertyOptions options,
172                                         ConfigurationValidatorBase validator,
173                                         TypeConverter converter) {
174             if (typeof(ConfigurationSection).IsAssignableFrom(type)) {
175                 throw new ConfigurationErrorsException(SR.GetString(SR.Config_properties_may_not_be_derived_from_configuration_section, name));
176             }
177 
178             _providedName = name; // save the provided name so we can check for default collection names
179             if ((options & ConfigurationPropertyOptions.IsDefaultCollection) != 0 &&
180                 String.IsNullOrEmpty(name)) {
181                 name = DefaultCollectionPropertyName;
182             }
183             else {
184                 ValidatePropertyName(name);
185             }
186 
187             _name = name;
188             _type = type;
189             _options = options;
190             _validator = validator;
191             _converter = converter;
192 
193             // Use the default validator if none was supplied
194             if (_validator == null) {
195                 _validator = DefaultValidatorInstance;
196             }
197             else {
198                 // Make sure the supplied validator supports the type of this property
199                 if (!_validator.CanValidate(_type)) {
200                     throw new ConfigurationErrorsException(SR.GetString(SR.Validator_does_not_support_prop_type, _name));
201                 }
202             }
203         }
204 
ValidatePropertyName(string name)205         private void ValidatePropertyName(string name) {
206             if (string.IsNullOrEmpty(name)) {
207                 throw new ArgumentException(SR.GetString(SR.String_null_or_empty), "name");
208             }
209 
210             if (BaseConfigurationRecord.IsReservedAttributeName(name)) {
211                 throw new ArgumentException(SR.GetString(SR.Property_name_reserved, name));
212             }
213         }
214 
SetDefaultValue(object value)215         private void SetDefaultValue(object value) {
216             // Validate the default value if any. This should make errors from invalid defaults easier to catch
217             if (value != null && value != ConfigurationElement.s_nullPropertyValue) {
218                 bool canAssign = _type.IsAssignableFrom(value.GetType());
219                 if (!canAssign && this.Converter.CanConvertFrom(value.GetType())) {
220                     value = this.Converter.ConvertFrom(value);
221                 }
222                 else if (!canAssign) {
223                     throw new ConfigurationErrorsException(SR.GetString(SR.Default_value_wrong_type, _name));
224                 }
225 
226                 Validate(value);
227 
228                 _defaultValue = value;
229             }
230         }
231 
InitDefaultValueFromTypeInfo(ConfigurationPropertyAttribute attribProperty, DefaultValueAttribute attribStdDefault)232         private void InitDefaultValueFromTypeInfo(ConfigurationPropertyAttribute attribProperty,
233                                                     DefaultValueAttribute attribStdDefault) {
234             object defaultValue = attribProperty.DefaultValue;
235 
236             // If there is no default value there - try the other attribute ( the clr standard one )
237             if ((defaultValue == null || defaultValue == ConfigurationElement.s_nullPropertyValue) &&
238                 (attribStdDefault != null)) {
239                 defaultValue = attribStdDefault.Value;
240             }
241 
242             // If there was a default value in the prop attribute - check if we need to convert it from string
243             if ((defaultValue != null) && (defaultValue is string) && (_type != typeof(string))) {
244                 // Use the converter to parse this property default value
245                 try {
246                     defaultValue = Converter.ConvertFromInvariantString((string)defaultValue);
247                 }
248                 catch (Exception ex) {
249                     throw new ConfigurationErrorsException(SR.GetString(SR.Default_value_conversion_error_from_string, _name, ex.Message));
250                 }
251             }
252             if (defaultValue == null || defaultValue == ConfigurationElement.s_nullPropertyValue) {
253                 if (_type == typeof(string)) {
254                     defaultValue = String.Empty;
255                 }
256                 else if (_type.IsValueType) {
257                     defaultValue = TypeUtil.CreateInstanceWithReflectionPermission(_type);
258                 }
259             }
260             SetDefaultValue(defaultValue);
261         }
262 
263         public string Name {
264             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_name is actually immutable once constructed")]
265             get {
266                 return _name;
267             }
268         }
269 
270         public string Description {
271             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_description is actually immutable once constructed")]
272             get {
273                 return _description;
274             }
275         }
276 
277         internal string ProvidedName {
278             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_providedName is actually immutable once constructed")]
279             get { return _providedName; }
280         }
281 
282         internal bool IsConfigurationElementType {
283             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_type is effectively readonly")]
284             get {
285                 if (!_isTypeInited) {
286                     _isConfigurationElementType = typeof(ConfigurationElement).IsAssignableFrom(_type);
287                     _isTypeInited = true;
288                 }
289                 return _isConfigurationElementType;
290             }
291         }
292 
293         public Type Type {
294             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_type is actually immutable once constructed")]
295             get {
296                 return _type;
297             }
298         }
299 
300         public Object DefaultValue {
301             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_defaultValue is actually immutable once constructed")]
302             get {
303                 return _defaultValue;
304             }
305         }
306 
307         public bool IsRequired {
308             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_options is actually immutable once constructed")]
309             get {
310                 return (_options & ConfigurationPropertyOptions.IsRequired) != 0;
311             }
312         }
313 
314         public bool IsKey {
315             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_options is actually immutable once constructed")]
316             get {
317                 return (_options & ConfigurationPropertyOptions.IsKey) != 0;
318             }
319         }
320 
321         public bool IsDefaultCollection {
322             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_options is actually immutable once constructed")]
323             get {
324                 return ((_options & ConfigurationPropertyOptions.IsDefaultCollection) != 0);
325             }
326         }
327 
328         public bool IsTypeStringTransformationRequired  {
329             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_options is actually immutable once constructed")]
330             get {
331                 return (_options & ConfigurationPropertyOptions.IsTypeStringTransformationRequired) != 0;
332             }
333         }
334 
335         public bool IsAssemblyStringTransformationRequired {
336             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_options is actually immutable once constructed")]
337             get {
338                 return (_options & ConfigurationPropertyOptions.IsAssemblyStringTransformationRequired) != 0;
339             }
340         }
341 
342         public bool IsVersionCheckRequired {
343             [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_options is actually immutable once constructed")]
344             get {
345                 return (_options & ConfigurationPropertyOptions.IsVersionCheckRequired) != 0;
346             }
347         }
348 
349         public TypeConverter Converter {
350             get {
351                 CreateConverter();
352 
353                 return _converter;
354             }
355         }
356 
357         public ConfigurationValidatorBase Validator {
358             get {
359                 return _validator;
360             }
361         }
362 
363         internal String AddElementName {
364             get {
365                 return _addElementName;
366             }
367         }
368         internal String RemoveElementName {
369             get {
370                 return _removeElementName;
371             }
372         }
373         internal String ClearElementName {
374             get {
375                 return _clearElementName;
376             }
377         }
378 
ConvertFromString(string value)379         internal Object ConvertFromString(string value) {
380             object result = null;
381 
382             try {
383                 result = Converter.ConvertFromInvariantString(value);
384             }
385             catch (Exception ex) {
386                 throw new ConfigurationErrorsException(SR.GetString(SR.Top_level_conversion_error_from_string, _name, ex.Message));
387             }
388 
389             return result;
390         }
391 
ConvertToString(Object value)392         internal string ConvertToString(Object value) {
393             string result = null;
394 
395             try {
396                 if (_type == typeof(bool)) {
397                     result = ((bool)value) ? "true" : "false"; // the converter will break 1.1 compat for bool
398                 }
399                 else {
400                     result = Converter.ConvertToInvariantString(value);
401                 }
402             }
403             catch (Exception ex) {
404                 throw new ConfigurationErrorsException(SR.GetString(SR.Top_level_conversion_error_to_string, _name, ex.Message));
405             }
406 
407             return result;
408         }
409         [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "_name and _validator are effectively readonly")]
Validate(object value)410         internal void Validate(object value) {
411             try {
412                 _validator.Validate(value);
413             }
414             catch (Exception ex) {
415                 throw new ConfigurationErrorsException(SR.GetString(SR.Top_level_validation_error, _name, ex.Message),ex);
416             }
417         }
CreateConverter()418         private void CreateConverter() {
419             // Some properties cannot have type converters.
420             // Such examples are properties that are ConfigurationElement ( derived classes )
421             // or properties which are user-defined and the user code handles serialization/desirialization so
422             // the property itself is never converted to/from string
423 
424             if (_converter == null) {
425                 // Enums are exception. We use our custom converter for all enums
426                 if (_type.IsEnum) {
427                     _converter = new GenericEnumConverter(_type);
428                 }
429                 else if (!_type.IsSubclassOf(typeof(ConfigurationElement))) {
430                     _converter = TypeDescriptor.GetConverter(_type);
431 
432                     if ((_converter == null) ||
433                             !_converter.CanConvertFrom(typeof(String)) ||
434                             !_converter.CanConvertTo(typeof(String))) {
435                         throw new ConfigurationErrorsException(SR.GetString(SR.No_converter, _name, _type.Name));
436                     }
437                 }
438             }
439         }
440     }
441 }
442