Строка конфигурации с Null DefaultValue

у меня есть следующая ConfigurationProperty как часть элемента:

[ConfigurationProperty("example", IsRequired = false, DefaultValue = null)]
public string Example { 
    get { return (string)base["example"]; }
    set { base["example"] = value; }
}

если я установил его следующим образом, он принимает "Hello" строку и работает правильно:

<myElement example="Hello"/>

если его нет, я сталкиваюсь с проблемами:

<myElement/>

вместо того, чтобы принять значение по умолчанию null как указано выше, он принимает на String.Empty. Почему это так, и как я могу заставить его принять значение по умолчанию null?

обновление

это определенно потому, что base["example"] возвращает String.Empty, где base это ConfigurationElement (индексатор определяется здесь:https://msdn.microsoft.com/en-us/library/c8693ks1 (v=против 110).aspx), но я все еще не уверен, почему он не принимает значение null.

обновление

даже DefaultValue = default(string) задает строку String.Empty.

обновление

даже base.Properties.Contains("example") возвращает true если свойство не существует в конфигурации.

5 ответов


исходя из ссылка на источник ConfigurationProperty класс, это, возможно, не ошибка, но особенность.

вот соответствующий внутренний метод,InitDefaultValueFromTypeInfo (С некоторыми незначительными изменениями форматирования мной):

private void InitDefaultValueFromTypeInfo(ConfigurationPropertyAttribute attribProperty,
                                          DefaultValueAttribute attribStdDefault) {
     object defaultValue = attribProperty.DefaultValue;

     // If there is no default value there - try the other attribute ( the clr standard one )
     if ((defaultValue == null || defaultValue == ConfigurationElement.s_nullPropertyValue) &&
         (attribStdDefault != null)) {
         defaultValue = attribStdDefault.Value;
     }

     // If there was a default value in the prop attribute - check if we need to convert it from string
     if ((defaultValue != null) && (defaultValue is string) && (_type != typeof(string))) {
         // Use the converter to parse this property default value
         try {
             defaultValue = Converter.ConvertFromInvariantString((string)defaultValue);
         }
         catch (Exception ex) {
             throw new ConfigurationErrorsException(SR.GetString(SR.Default_value_conversion_error_from_string, _name, ex.Message));
         }
     }

     if (defaultValue == null || defaultValue == ConfigurationElement.s_nullPropertyValue) {
         if (_type == typeof(string)) {
             defaultValue = String.Empty;
         }
         else if (_type.IsValueType) {
             defaultValue = TypeUtil.CreateInstanceWithReflectionPermission(_type);
         }
     }

     SetDefaultValue(defaultValue);
 }

последние if блок интересен: если ваше свойство имеет тип string, и значение по умолчанию null, затем значение по умолчанию изменяется на string.Empty.

первый if блок намекает на возможное объяснение этого странного поведения. The [ConfigurationProperty] атрибут DefaultValue свойство является необязательным. Если DefaultValue не устанавливается программистом, тогда он по умолчанию . Первый if блок использует это значение по умолчанию null чтобы проверить, является ли . Если нет, он возвращается к извлечению значения по умолчанию из [DefaultValue] атрибут, если такой присутствует.

это все означает: указание DefaultValue = null имеет тот же эффект, что и не указывать его вообще, и в этом случае подсистема конфигурации выбирает" нормальное " значение по умолчанию для строк: пустая строка.

решение:

вот несколько хакерский обходной путь: не объявляйте свое свойство конфигурации как string, но как тонкий тип обертки вокруг строки; затем объявите подходящий конвертер типов:

[ConfigurationProperty("name", IsRequired = false)]
[TypeConverter(typeof(IncognitoStringConverter))]  // note: additional attribute!
public IncognitoString Name                        // note: different property type
{
    get
    {
        return (IncognitoString)base["name"];
    }
    set
    {
        base["name"] = value;
    }
}

вот реализации для IncognitoString и IncognitoStringConverter:

public struct IncognitoString
{
    private IncognitoString(string value)
    {
        this.value = value;
    }

    private readonly string value;

    public static implicit operator IncognitoString(string value)
    {
        return new IncognitoString(value);
    }

    public static implicit operator string(IncognitoString incognitoString)
    {
        return incognitoString.value;
    }

    … // perhaps override ToString, GetHashCode, and Equals as well.
}

public sealed class IncognitoStringConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        return (IncognitoString)(string)value;
    }
}

, потому что IncognitoString неявно конвертируется в string, вы можете присвоить значение свойства любой строковой переменной. Я знаю, это хаки и действительно сложно просто получить nullable свойства. Возможно, просто жить с пустой строкой.


другой обходной путь - сделать вызов примерно так:

[ConfigurationProperty("Prompt")]
public string Prompt
{
    get { return this.GetNullableStringValue("Prompt"); }
}

private string GetNullableStringValue(string propertyName)
{
    return (string)this[new ConfigurationProperty(propertyName, typeof(string), null)];
}

вызов GetNullableString как это обходит атрибут свойства конфигурации и останавливает его по умолчанию DefaultValue в null. Вы также можете поместить метод в базовый класс, чтобы сделать его немного аккуратнее.

вы просто должны помнить, что вы вызываете его, если вы хотите изменить значение по умолчанию.

вы также можете позвонить в this.ElementInformation.Properties[propertyName] Если вы хотели поднять некоторые из других вещей, которые вы может определить атрибут - просто не используйте это для заполнения DefaultValue


вместо проверки значения свойства для null вы можете легко проверить, установлено ли свойство в файле конфигурации или возвращено значение по умолчанию. Это можно сделать как ValueOrigin на ConfigurationElement ' s ElementInformation.

// if not the default value...    
if (MyConfigurationElement.ElementInformation.Properties["example"].ValueOrigin!=
        PropertyValueOrigin.Default)
{
    ...
}

см. также документацию по значениям Перечисление PropertyValueOrigin.


на ConfigurationElement тип имеет ElementInformation свойство, которое, в свою очередь, имеет IsPresent собственность.

Итак, вместо того, чтобы пытаться вернуть null ConfigurationElement, вы можете проверить IsPresent свойство для просмотра " находится ли Связанный объект ConfigurationElement в файле конфигурации."1

например:

if (Configuration.Example.ElementInformation.IsPresent)
{
    ...
}

Я решил использовать более читаемый и многоразовый метод расширения ToNullIfEmpty(). Я оставил значение DefaultValue на месте, если когда-либо произойдет изменение неинтуитивного поведения, которое преобразует нулевые строки в строку.Пустой.

[ConfigurationProperty("dataCenterRegion", DefaultValue = null)]
public string DataCenterRegion
{
    get { return ((string)this["dataCenterRegion"]).ToNullIfEmpty(); }
    set { this["dataCenterRegion"] = value; }
}

public static partial class ExtensionMethods
{        
    /// <summary>
    /// Return null if the string is empty or is already null.
    /// Otherwise, return the original string.
    /// </summary>
    public static string ToNullIfEmpty(this string str)
    {
        return String.IsNullOrEmpty(str) ? null : str;
    }

    /// <summary>
    /// Return null if the string is white space, empty or is already null.
    /// Otherwise, return the original string.
    /// </summary>
    public static string ToNullIfWhiteSpaceOrEmpty(this string str)
    {
        return String.IsNullOrWhiteSpace(str) ? null : str;
    }
}