2013-11-11 9 views
0

Я использую XamlServices как универсальный механизм сериализации, как описано here, here и here. Хотя это очень хорошо подходит для большинства сценариев, я не понимаю, как получить его для сериализации значений свойств строки, содержащих непечатаемые символы (в частности, нулевые символы).XAML-сериализация свойств двоичной строки

Вот простой пример класса, который я мог бы пожелать, чтобы сериализовать:

public class MyClass 
{ 
    public string Value { get; set; } 
} 

Если я создаю экземпляр этого класса (обратите внимание на нулевой символ в назначенной стоимости недвижимости) ...

var instance = new MyClass 
{ 
    Value = "Some\0Value", 
}; 

... и сериализовать его с помощью XamlServices ...

var xaml = XamlServices.Save(instance); 

... это исключает исключение hexadecimal value 0x00, is an invalid character.

Я думаю, это означает, что сериализация XAML не поддерживает двоичные строковые данные, поэтому я счастлив преобразовать строку в кодированную форму (например, Base64) во время сериализации.

Я пытался добиться этого преобразования пути создания значения сериалайзера пользовательских XAML, который реализует кодирование Base64 и применяя его к соответствующим свойствам класса ...

public class MyClass 
{ 
    [ValueSerializer(typeof(Base64ValueSerializer))] 
    public string Value { get; set; } 
} 

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

Аналогичным образом, я создал настраиваемый конвертер типов с той же целью. Еще раз, метод ConvertTo не вызывается во время сериализации, хотя, интересно, его метод ConvertFrom вызывает вызов во время десериализации и правильно заполняет целевое свойство из строковых данных, кодированных Base64.

Я ищу идеи, как получить XamlServices подчиняться мой обычай TypeConverter или ValueSerializer или какие-то другие средства, чтобы заставить мои двоичные значения строки свойств в строку сериализуемой форме.

+0

Если вы сохраните с помощью 'var xaml = System.Windows.Markup.XamlWriter.Save (instance);' и поместите контрольную точку в свой метод 'Base64ValueSerializer' CanConvertToString', он будет удален. Однако, если вы используете 'XamlServices.Save', он не попадает в ValueSerializer. Я не могу найти причину в документации. Не могли бы вы просто сохранить с помощью 'XamlWriter.Save'? –

+0

@Adolfo Perez: это интересное наблюдение, и я определенно буду открыт для использования XamlWriter, если он решает проблему. Однако, обновив свой код, чтобы отразить вашу идею, я вижу, что, несмотря на попадание точки останова в CanConvertToString, он впоследствии не вызывает ConvertToString, поэтому преобразование Base64 по-прежнему не увенчалось успехом. Это разочаровывает, но спасибо за ваше предложение. –

+0

Если вы переопределяете возврат: 'public override bool CanConvertToString (значение объекта, IValueSerializerContext контекст) { var b = base.CanConvertToString (значение, контекст); return true; }. Он будет сериализовать ваш xaml следующим образом: '' Where & # x0 - символ unicode для NULL http://www.fileformat.info/info/unicode/char/0000/index.htm –

ответ

1
public Base64ValueSerializer : ValueSerializer 
{ 
    public override bool CanConvertToString(object value, IValueSerializerContext context) 
    { 
    //If your value string contains a '\0' then base.CanConvertToString will return false 
    //var canConvert = base.CanConvertToString(value, context); 
    return IsValidString(value); 
    } 
    private bool IsValidString(string input) 
    { 
    //Check if input string contains 'invalid' characters that can be converted 
    //automatically to its HTML equivalent, like '\0' to '&#x0' 
    bool isValid = ... 
    return isValid; 
    } 
} 

Ваша IsValidString логика будет зависеть от другого типа «недопустимые символы» вы ожидаете, и являются ли эти могут быть автоматически переведены. В идеале вы должны иметь возможность контролировать перевод с помощью переопределения ConvertToString, и я предполагаю, что, поскольку ваше свойство уже является строкой, оно даже не попадает туда, но не уверен.

Вы пытались изменить свою собственность на тип «объект» и посмотреть, будет ли выполнен метод ConvertToString в вашем ValueSerializer?

+0

Спасибо за ваш ответ. Здесь есть две очень многообещающие идеи: [1] используя описанный вами метод, чтобы обойти проверку сериализации и [2] преобразовать тип свойства в объект, надеясь, что он заставит все методы ValueSerializer или TypeConverter вызываться. К сожалению, хотя [1] работает, он терпит неудачу во время десериализации, потому что, по-видимому, ни XamlReader, ни XamlServices не знают, как обращаться с �. Опция [2] также не работает - оба механизма рассматривают ее так же, как строку. :(Жаль, что мы действительно близки к решению, но не совсем там. –

+0

Я принимаю ваш ответ, так как он дал некоторое вдохновение, которое в конечном итоге привело меня к работоспособному решению (которое я буду документировать как отдельный ответ) Еще раз спасибо за вашу помощь. –

+0

Отличный им интересен, когда вы решили его решить. –

1

Я принял ответ Адольфо Переса, поскольку именно его идеи привели меня к работоспособному решению, но я размещаю здесь некоторые подробности о том, что я сделал, для всех, кто может пытаться добиться чего-то подобного.

Адольфо предложил изменить тип проблемного имущества от string к object в надежде, что он будет дурачить процесс сериализации XAML в использовании моих привычек TypeConverter или ValueSerializer реализации. Фактически, этот подход не сработал, но он заставил меня поэкспериментировать с использованием нового настраиваемого типа для хранения двоичных строковых данных.

Поскольку тип CLR string запечатан, его невозможно подклассифицировать, но подобный результат может быть достигнут путем создания настраиваемого типа, который инкапсулирует строковое значение и логику, необходимую для его преобразования в/из Base64 , а также неявное преобразование в/из string, что позволяет использовать его в качестве замены плагина для CLR string. Вот моя реализация этого пользовательского типа:

/// <summary> 
/// Implements a string type that supports XAML serialization of non-printable characters via an associated type converter that converts to Base64 format. 
/// </summary> 
[TypeConverter(typeof(BinaryStringConverter))] 
public class BinaryString 
{ 
    /// <summary> 
    /// Initializes a new instance of the <see cref="BinaryString"/> class and populates it with the passed string value. 
    /// </summary> 
    /// <param name="value">A <see cref="string"/> that represents the value with which to populate this instance.</param> 
    public BinaryString(string value) 
    { 
     Value = value; 
    } 

    /// <summary> 
    /// Gets the raw value of this instance. 
    /// </summary> 
    public string Value { get; private set; } 

    /// <summary> 
    /// Implements an implicit conversion from <see cref="string"/>. 
    /// </summary> 
    /// <param name="value">A <see cref="string"/> that represents the value to convert.</param> 
    /// <returns>A new <see cref="BinaryString"/> that represents the converted value.</returns> 
    public static implicit operator BinaryString(string value) 
    { 
     return new BinaryString(value); 
    }  

    /// <summary> 
    /// Implements an implicit conversion to <see cref="string"/>. 
    /// </summary> 
    /// <param name="value">A <see cref="BinaryString"/> that represents the value to convert.</param> 
    /// <returns>The <see cref="string"/> content of the passed value.</returns> 
    public static implicit operator string(BinaryString value) 
    { 
     return value.Value; 
    } 

    /// <summary> 
    /// Returns the value of this instance in <c>Base64</c> format. 
    /// </summary> 
    /// <returns>A <see cref="string"/> that represents the <c>Base64</c> value of this instance.</returns> 
    public string ToBase64String() 
    { 
     return Value == null ? null : Convert.ToBase64String(Encoding.UTF8.GetBytes(Value)); 
    } 

    /// <summary> 
    /// Creates a new instance from the passed <c>Base64</c> string. 
    /// </summary> 
    /// <param name="value">A <see cref="string"/> that represent a <c>Base64</c> value to convert from.</param> 
    /// <returns>A new <see cref="BinaryString"/> instance.</returns> 
    public static BinaryString CreateFromBase64String(string value) 
    { 
     return new BinaryString(value == null ? null : Encoding.UTF8.GetString(Convert.FromBase64String(value))); 
    } 
} 

Одно из преимуществ использования пользовательского типа в этой роли является то, что соответствующий тип преобразователя может быть применен непосредственно на уровне класса, а не украшать индивидуальные свойства в потребляющей кода , Вот тип преобразователь:

/// <summary> 
/// Implements a mechanism to convert a <see cref="BinaryString"/> object to or from another object type. 
/// </summary> 
public class BinaryStringConverter : TypeConverter 
{ 
    /// <summary> 
    /// Returns whether this converter can convert the object to the specified type, using the specified context. 
    /// </summary> 
    /// <param name="context">An <see cref="ITypeDescriptorContext"/> that provides a format context.</param> 
    /// <param name="destinationType">A <see cref="Type"/> that represents the type you want to convert to.</param> 
    /// <returns><c>true</c> if this converter can perform the conversion; otherwise, <c>false</c>.</returns> 
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) 
    { 
     return destinationType == typeof(string) || base.CanConvertTo(context, destinationType); 
    } 

    /// <summary> 
    /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context. 
    /// </summary> 
    /// <param name="context">An <see cref="ITypeDescriptorContext"/> that provides a format context.</param> 
    /// <param name="sourceType">A <see cref="Type"/> that represents the type you want to convert from.</param> 
    /// <returns><c>true</c> if this converter can perform the conversion; otherwise, <c>false</c>.</returns> 
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) 
    { 
     return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); 
    } 

    /// <summary> 
    /// Converts the given value object to the specified type, using the specified context and culture information. 
    /// </summary> 
    /// <param name="context">An <see cref="ITypeDescriptorContext"/> that provides a format context.</param> 
    /// <param name="culture">A <see cref="CultureInfo"/>. If null is passed, the current culture is assumed.</param> 
    /// <param name="value">The <see cref="object"/> to convert.</param> 
    /// <param name="destinationType">A <see cref="Type"/> that represents the type you want to convert to.</param> 
    /// <returns>An <see cref="object"/> that represents the converted value.</returns> 
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) 
    { 
     if (destinationType == typeof(string)) 
     { 
      return ((BinaryString)value).ToBase64String(); 
     } 

     return base.ConvertTo(context, culture, value, destinationType); 
    } 

    /// <summary> 
    /// Converts the given object to the type of this converter, using the specified context and culture information. 
    /// </summary> 
    /// <param name="context">An <see cref="ITypeDescriptorContext"/> that provides a format context.</param> 
    /// <param name="culture">A <see cref="CultureInfo"/>. If null is passed, the current culture is assumed.</param> 
    /// <param name="value">The <see cref="object"/> to convert.</param> 
    /// <returns>An <see cref="object"/> that represents the converted value.</returns> 
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) 
    { 
     if (value is string) 
     { 
      return BinaryString.CreateFromBase64String((string)value); 
     } 

     return base.ConvertFrom(context, culture, value); 
    } 
} 

С помощью этих двух элементов в месте, потребляя код не почти как раньше - только немного проще, так как он больше не требует какого-либо явного преобразования типа или атрибуты значения сериализации ...

public class MyClass 
{ 
    public BinaryString Name { get; set; } 
} 

... и процесс сериализации, как и раньше, за исключением того, что он теперь корректно поддерживает непечатные символы в значении Назначают собственности:

var data = new MyClass 
{ 
    Name = "My\0Object" 
}; 

var xaml = XamlServices.Save(data); 
var deserialized = XamlServices.Parse(xaml) as MyClass; 

Примечание т В этом примере я вернулся к использованию XamlServices.Save() и XamlServices.Parse(), но эта техника работает одинаково хорошо с XamlWriter.Save() и XamlReader.Parse().

+0

Отличное решение! Он выглядит очень прочным. Рад, что я смог помочь. –

+0

Я думаю, что полезно отметить, что даже если вы только хотите сериализуйте свой объект и не заботитесь о десериализации, вам все равно нужно переопределить как «CanConvertTo», так и «CanConvertFrom». Я предполагаю, что сериализатор _XAML_ проверяет, может ли десериализоваться значение в сериализованной последовательности «string». – Grx70

Смежные вопросы