XAML二进制字符串属性的序列化

时间:2013-11-11 17:49:11

标签: c# .net xaml type-conversion xaml-serialization

我使用XamlServices作为通用序列化机制,如hereherehere所述。虽然这对大多数情况都很有效,但我不清楚如何让它序列化包含不可打印字符的字符串属性值(特别是空字符)。

这是一个我可能希望序列化的类的简单示例:

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)。

我尝试通过创建实现Base64编码并将其应用于相关类属性的自定义XAML值序列化器来实现此转换...

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

...但它的转换方法永远不会被调用,大概是因为XAML序列化机制认为在序列化字符串属性时不需要自定义序列化程序。

同样,我创建了一个具有相同目标的自定义类型转换器。再一次,它的ConvertTo方法在序列化期间不会被调用,尽管有趣的是,它在反序列化期间会调用它的ConvertFrom方法,并从Base64编码的字符串数据中正确填充目标属性。

我正在寻找有关如何让XamlServices遵守我的自定义TypeConverterValueSerializer或其他方法来强制我的二进制字符串属性值为字符串可序列化形式的想法。< / p>

2 个答案:

答案 0 :(得分: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方法是否已执行?

答案 1 :(得分:1)

我已经接受了Adolfo Perez的回答,因为正是他的想法使我找到了一个可行的解决方案,但我在这里发布了一些关于我所做的事情的详细信息,以及其他可能尝试实现类似内容的人。

Adolfo建议将问题属性的类型从string更改为object,希望它会欺骗XAML序列化过程使用我的自定义TypeConverterValueSerializer实现。实际上,这种方法不起作用,但它让我尝试使用新的自定义类型来存储二进制字符串数据。

由于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; }
}

...并且序列化过程与以前一样,除了它现在正确支持assign属性值中的不可打印字符:

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()同样有效。