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 }