0

如hereherehere所述,我将XamlServices其用作通用序列化机制。尽管这在大多数情况下都非常有效,但我不清楚如何让它序列化包含不可打印字符(特别是空字符)的字符串属性值。

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

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其他方式将我的二进制字符串属性值强制转换为字符串可序列化形式的想法。

4

2 回答 2

1

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

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

由于 CLRstring类型是密封的,因此无法对其进行子类化,但可以通过创建自定义类型来实现类似的结果,该类型封装了字符串值以及将其与 Base64 进行转换所需的逻辑,以及隐式转换到/从string它使其能够用作 CLRstring类型的插件替代品。这是我对这种自定义类型的实现:

/// <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()and XamlServices.Parse(),但此技术同样适用于XamlWriter.Save()and XamlReader.Parse()

于 2013-11-12T11:32:53.687 回答
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被执行?

于 2013-11-11T23:51:59.303 回答