1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.ComponentModel;
6 using System.Diagnostics;
7 using System.Reflection;
8 
9 namespace System.Configuration
10 {
11     public sealed class ConfigurationProperty
12     {
13         internal static readonly ConfigurationValidatorBase s_nonEmptyStringValidator = new StringValidator(1);
14         private static readonly ConfigurationValidatorBase s_defaultValidatorInstance = new DefaultValidator();
15         internal static readonly string s_defaultCollectionPropertyName = "";
16         private TypeConverter _converter;
17         private volatile bool _isConfigurationElementType;
18         private volatile bool _isTypeInited;
19         private ConfigurationPropertyOptions _options;
20 
ConfigurationProperty(string name, Type type)21         public ConfigurationProperty(string name, Type type)
22         {
23             object defaultValue = null;
24 
25             ConstructorInit(name, type, ConfigurationPropertyOptions.None, null, null);
26 
27             if (type == typeof(string))
28             {
29                 defaultValue = string.Empty;
30             }
31             else if (type.IsValueType)
32             {
33                 defaultValue = TypeUtil.CreateInstance(type);
34             }
35 
36             SetDefaultValue(defaultValue);
37         }
38 
ConfigurationProperty(string name, Type type, object defaultValue)39         public ConfigurationProperty(string name, Type type, object defaultValue)
40             : this(name, type, defaultValue, ConfigurationPropertyOptions.None)
41         { }
42 
ConfigurationProperty(string name, Type type, object defaultValue, ConfigurationPropertyOptions options)43         public ConfigurationProperty(string name, Type type, object defaultValue, ConfigurationPropertyOptions options)
44             : this(name, type, defaultValue, null, null, options)
45         { }
46 
ConfigurationProperty(string name, Type type, object defaultValue, TypeConverter typeConverter, ConfigurationValidatorBase validator, ConfigurationPropertyOptions options)47         public ConfigurationProperty(string name,
48             Type type,
49             object defaultValue,
50             TypeConverter typeConverter,
51             ConfigurationValidatorBase validator,
52             ConfigurationPropertyOptions options)
53             : this(name, type, defaultValue, typeConverter, validator, options, null)
54         { }
55 
ConfigurationProperty(string name, Type type, object defaultValue, TypeConverter typeConverter, ConfigurationValidatorBase validator, ConfigurationPropertyOptions options, string description)56         public ConfigurationProperty(string name,
57             Type type,
58             object defaultValue,
59             TypeConverter typeConverter,
60             ConfigurationValidatorBase validator,
61             ConfigurationPropertyOptions options,
62             string description)
63         {
64             ConstructorInit(name, type, options, validator, typeConverter);
65 
66             SetDefaultValue(defaultValue);
67         }
68 
ConfigurationProperty(PropertyInfo info)69         internal ConfigurationProperty(PropertyInfo info)
70         {
71             Debug.Assert(info != null, "info != null");
72 
73             ConfigurationPropertyAttribute propertyAttribute = null;
74             DescriptionAttribute descriptionAttribute = null;
75 
76             // For compatibility we read the component model default value attribute. It is only
77             // used if ConfigurationPropertyAttribute doesn't provide the default value.
78             DefaultValueAttribute defaultValueAttribute = null;
79 
80             TypeConverter typeConverter = null;
81             ConfigurationValidatorBase validator = null;
82 
83             // Look for relevant attributes
84             foreach (Attribute attribute in Attribute.GetCustomAttributes(info))
85             {
86                 if (attribute is TypeConverterAttribute)
87                 {
88                     typeConverter = TypeUtil.CreateInstance<TypeConverter>(((TypeConverterAttribute)attribute).ConverterTypeName);
89                 }
90                 else if (attribute is ConfigurationPropertyAttribute)
91                 {
92                     propertyAttribute = (ConfigurationPropertyAttribute)attribute;
93                 }
94                 else if (attribute is ConfigurationValidatorAttribute)
95                 {
96                     if (validator != null)
97                     {
98                         // We only allow one validator to be specified on a property.
99                         //
100                         // Consider: introduce a new validator type ( CompositeValidator ) that is a
101                         // list of validators and executes them all
102 
103                         throw new ConfigurationErrorsException(
104                             string.Format(SR.Validator_multiple_validator_attributes, info.Name));
105                     }
106 
107                     ConfigurationValidatorAttribute validatorAttribute = (ConfigurationValidatorAttribute)attribute;
108                     validatorAttribute.SetDeclaringType(info.DeclaringType);
109                     validator = validatorAttribute.ValidatorInstance;
110                 }
111                 else if (attribute is DescriptionAttribute)
112                 {
113                     descriptionAttribute = (DescriptionAttribute)attribute;
114                 }
115                 else if (attribute is DefaultValueAttribute)
116                 {
117                     defaultValueAttribute = (DefaultValueAttribute)attribute;
118                 }
119             }
120 
121             Type propertyType = info.PropertyType;
122 
123             // If the property is a Collection we need to look for the ConfigurationCollectionAttribute for
124             // additional customization.
125             if (typeof(ConfigurationElementCollection).IsAssignableFrom(propertyType))
126             {
127                 // Look for the ConfigurationCollection attribute on the property itself, fall back
128                 // on the property type.
129                 ConfigurationCollectionAttribute collectionAttribute =
130                     Attribute.GetCustomAttribute(info,
131                         typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute ??
132                     Attribute.GetCustomAttribute(propertyType,
133                         typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;
134 
135                 if (collectionAttribute != null)
136                 {
137                     if (collectionAttribute.AddItemName.IndexOf(',') == -1) AddElementName = collectionAttribute.AddItemName;
138                     RemoveElementName = collectionAttribute.RemoveItemName;
139                     ClearElementName = collectionAttribute.ClearItemsName;
140                 }
141             }
142 
143             // This constructor shouldn't be invoked if the reflection info is not for an actual config property
144             Debug.Assert(propertyAttribute != null, "attribProperty != null");
145 
146             ConstructorInit(propertyAttribute.Name,
147                 info.PropertyType,
148                 propertyAttribute.Options,
149                 validator,
150                 typeConverter);
151 
152             // Figure out the default value
153             InitDefaultValueFromTypeInfo(propertyAttribute, defaultValueAttribute);
154 
155             // Get the description
156             if (!string.IsNullOrEmpty(descriptionAttribute?.Description))
157                 Description = descriptionAttribute.Description;
158         }
159 
160         public string Name { get; private set; }
161 
162         public string Description { get; }
163 
164         internal string ProvidedName { get; private set; }
165 
166         internal bool IsConfigurationElementType
167         {
168             get
169             {
170                 if (_isTypeInited)
171                     return _isConfigurationElementType;
172 
173                 _isConfigurationElementType = typeof(ConfigurationElement).IsAssignableFrom(Type);
174                 _isTypeInited = true;
175                 return _isConfigurationElementType;
176             }
177         }
178 
179         public Type Type { get; private set; }
180 
181         public object DefaultValue { get; private set; }
182 
183         public bool IsRequired => (_options & ConfigurationPropertyOptions.IsRequired) != 0;
184 
185         public bool IsKey => (_options & ConfigurationPropertyOptions.IsKey) != 0;
186 
187         public bool IsDefaultCollection => (_options & ConfigurationPropertyOptions.IsDefaultCollection) != 0;
188 
189         public bool IsTypeStringTransformationRequired
190             => (_options & ConfigurationPropertyOptions.IsTypeStringTransformationRequired) != 0;
191 
192         public bool IsAssemblyStringTransformationRequired
193             => (_options & ConfigurationPropertyOptions.IsAssemblyStringTransformationRequired) != 0;
194 
195         public bool IsVersionCheckRequired => (_options & ConfigurationPropertyOptions.IsVersionCheckRequired) != 0;
196 
197         public TypeConverter Converter
198         {
199             get
200             {
201                 CreateConverter();
202                 return _converter;
203             }
204         }
205 
206         public ConfigurationValidatorBase Validator { get; private set; }
207 
208         internal string AddElementName { get; }
209 
210         internal string RemoveElementName { get; }
211 
212         internal string ClearElementName { get; }
213 
ConstructorInit( string name, Type type, ConfigurationPropertyOptions options, ConfigurationValidatorBase validator, TypeConverter converter)214         private void ConstructorInit(
215             string name,
216             Type type,
217             ConfigurationPropertyOptions options,
218             ConfigurationValidatorBase validator,
219             TypeConverter converter)
220         {
221             if (typeof(ConfigurationSection).IsAssignableFrom(type))
222             {
223                 throw new ConfigurationErrorsException(
224                     string.Format(SR.Config_properties_may_not_be_derived_from_configuration_section, name));
225             }
226 
227             // save the provided name so we can check for default collection names
228             ProvidedName = name;
229 
230             if (((options & ConfigurationPropertyOptions.IsDefaultCollection) != 0) && string.IsNullOrEmpty(name))
231             {
232                 name = s_defaultCollectionPropertyName;
233             }
234             else
235             {
236                 ValidatePropertyName(name);
237             }
238 
239             Name = name;
240             Type = type;
241             _options = options;
242             Validator = validator;
243             _converter = converter;
244 
245             // Use the default validator if none was supplied
246             if (Validator == null)
247             {
248                 Validator = s_defaultValidatorInstance;
249             }
250             else
251             {
252                 // Make sure the supplied validator supports the type of this property
253                 if (!Validator.CanValidate(Type))
254                     throw new ConfigurationErrorsException(string.Format(SR.Validator_does_not_support_prop_type, Name));
255             }
256         }
257 
ValidatePropertyName(string name)258         private void ValidatePropertyName(string name)
259         {
260             if (string.IsNullOrEmpty(name))
261                 throw new ArgumentException(SR.String_null_or_empty, nameof(name));
262 
263             if (BaseConfigurationRecord.IsReservedAttributeName(name))
264                 throw new ArgumentException(string.Format(SR.Property_name_reserved, name));
265         }
266 
SetDefaultValue(object value)267         private void SetDefaultValue(object value)
268         {
269             // Validate the default value if any. This should make errors from invalid defaults easier to catch
270             if (ConfigurationElement.IsNullOrNullProperty(value))
271                 return;
272 
273             if (!Type.IsInstanceOfType(value))
274             {
275                 if (!Converter.CanConvertFrom(value.GetType()))
276                     throw new ConfigurationErrorsException(string.Format(SR.Default_value_wrong_type, Name));
277 
278                 value = Converter.ConvertFrom(value);
279             }
280 
281             Validate(value);
282             DefaultValue = value;
283         }
284 
InitDefaultValueFromTypeInfo( ConfigurationPropertyAttribute configurationProperty, DefaultValueAttribute defaultValueAttribute)285         private void InitDefaultValueFromTypeInfo(
286             ConfigurationPropertyAttribute configurationProperty,
287             DefaultValueAttribute defaultValueAttribute)
288         {
289             object defaultValue = configurationProperty.DefaultValue;
290 
291             // If the ConfigurationPropertyAttribute has no default try the DefaultValueAttribute
292             if (ConfigurationElement.IsNullOrNullProperty(defaultValue))
293                 defaultValue = defaultValueAttribute?.Value;
294 
295             // Convert the default value from string if necessary
296             if (defaultValue is string && (Type != typeof(string)))
297             {
298                 try
299                 {
300                     defaultValue = Converter.ConvertFromInvariantString((string)defaultValue);
301                 }
302                 catch (Exception ex)
303                 {
304                     throw new ConfigurationErrorsException(string.Format(SR.Default_value_conversion_error_from_string,
305                         Name, ex.Message));
306                 }
307             }
308 
309             // If we still have no default, use string Empty for string or the default for value types
310             if (ConfigurationElement.IsNullOrNullProperty(defaultValue))
311             {
312                 if (Type == typeof(string))
313                 {
314                     defaultValue = string.Empty;
315                 }
316                 else if (Type.IsValueType)
317                 {
318                     defaultValue = TypeUtil.CreateInstance(Type);
319                 }
320             }
321 
322             SetDefaultValue(defaultValue);
323         }
324 
ConvertFromString(string value)325         internal object ConvertFromString(string value)
326         {
327             object result;
328 
329             try
330             {
331                 result = Converter.ConvertFromInvariantString(value);
332             }
333             catch (Exception ex)
334             {
335                 throw new ConfigurationErrorsException(string.Format(SR.Top_level_conversion_error_from_string, Name,
336                     ex.Message));
337             }
338 
339             return result;
340         }
341 
ConvertToString(object value)342         internal string ConvertToString(object value)
343         {
344             try
345             {
346                 if (Type == typeof(bool))
347                 {
348                     // The boolean converter will break 1.1 compat for bool
349                     return (bool)value ? "true" : "false";
350                 }
351                 else
352                 {
353                     return Converter.ConvertToInvariantString(value);
354                 }
355             }
356             catch (Exception ex)
357             {
358                 throw new ConfigurationErrorsException(
359                     string.Format(SR.Top_level_conversion_error_to_string, Name, ex.Message));
360             }
361         }
362 
Validate(object value)363         internal void Validate(object value)
364         {
365             try
366             {
367                 Validator.Validate(value);
368             }
369             catch (Exception ex)
370             {
371                 throw new ConfigurationErrorsException(
372                     string.Format(SR.Top_level_validation_error, Name, ex.Message), ex);
373             }
374         }
375 
CreateConverter()376         private void CreateConverter()
377         {
378             if (_converter != null) return;
379 
380             if (Type.IsEnum)
381             {
382                 // We use our custom converter for all enums
383                 _converter = new GenericEnumConverter(Type);
384             }
385             else if (Type.IsSubclassOf(typeof(ConfigurationElement)))
386             {
387                 // Type converters aren't allowed on ConfigurationElement
388                 // derived classes.
389                 return;
390             }
391             else
392             {
393                 _converter = TypeDescriptor.GetConverter(Type);
394 
395                 if ((_converter == null) ||
396                     !_converter.CanConvertFrom(typeof(string)) ||
397                     !_converter.CanConvertTo(typeof(string)))
398                 {
399                     // Need to be able to convert to/from string
400                     throw new ConfigurationErrorsException(string.Format(SR.No_converter, Name, Type.Name));
401                 }
402             }
403         }
404     }
405 }