【问题标题】:XAML serialization of binary string properties二进制字符串属性的 XAML 序列化
【发布时间】:2013-11-11 17:49:11
【问题描述】:

我使用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)。

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

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

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

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

我正在寻找如何让 XamlServices 遵守我的自定义 TypeConverterValueSerializer 或其他一些方法来将我的二进制字符串属性值强制转换为字符串可序列化形式的想法。

【问题讨论】:

  • 如果您使用var xaml = System.Windows.Markup.XamlWriter.Save(instance); 保存并在您的Base64ValueSerializer CanConvertToString 方法中放置一个断点,它将被命中。但是,如果您使用XamlServices.Save,它不会进入 ValueSerializer。我在文档中的任何地方都找不到原因。你不能用XamlWriter.Save保存吗?
  • @Adolfo Perez:这是一个有趣的观察,如果 XamlWriter 能解决问题,我肯定愿意使用它。但是,在更新了我的代码以反映您的想法后,我看到,尽管在 CanConvertToString 中遇到了断点,但它随后不会调用 ConvertToString,因此 Base64 转换仍然不成功。这很令人失望,但感谢您的建议。
  • 如果覆盖返回:` public override bool CanConvertToString(object value, IValueSerializerContext context) { var b = base.CanConvertToString(value, context);返回真; }` 它将像这样序列化您的 xaml:<MyClass Value="Some�Value" xmlns="clr-namespace:xamlser;assembly=xamlser" /> 其中 是 NULL 的 unicode char fileformat.info/info/unicode/char/0000/index.htm
  • 你可以把你的逻辑放在CanConvertToString方法中,这样它就更健壮了。就像根据您的规则分析您的字符串值一样。
  • @Adolfo Perez:好的,我很高兴使用 Unicode 转义序列(或任何其他合适的转义序列)转义空字符,但我看不出如何修改 CanConvertToString 中的序列化值.您介意使用代码 sn-p 发布答案吗?如果它像你描述的那样有效,我会接受它。谢谢。

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


【解决方案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 覆盖来控制翻译,我猜由于您的属性已经是一个字符串,它甚至没有到达那里,但不确定。

您是否尝试过将您的属性更改为“对象”类型并查看您的ValueSerializer 中的ConvertToString 方法是否被执行?

【讨论】:

  • 感谢您的回答。这里有 2 个非常有前途的想法:[1] 使用您描述的技术绕过序列化验证和 [2] 将属性类型转换为“对象”,希望它会强制调用 ValueSerializer 或 TypeConverter 的所有方法。不幸的是,尽管 [1] 有效,但它在反序列化期间失败,因为显然 XamlReader 和 XamlServices 都不知道如何处理 。选项 [2] 也不起作用 - 两种机制都将其视为字符串。 :( 很遗憾 - 我们真的很接近解决方案,但还没有完全解决。
  • 我接受您的回答,因为它提供了一些很好的灵感,最终使我找到了一个可行的解决方案(我将作为单独的答案记录下来)。再次感谢您的帮助。
  • 我很想听听你最终是如何解决它的。
【解决方案2】:

我已经接受了 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()

【讨论】:

  • 不错的解决方案!它看起来非常坚固。很高兴我能提供帮助。
  • 我认为有必要注意的是,即使您只想序列化您的对象而不关心反序列化,您仍然需要同时覆盖CanConvertToCanConvertFrom。我猜 XAML 序列化程序会检查当序列化为 string 时,该值是否可能被反序列化。
猜你喜欢
  • 1970-01-01
  • 2012-02-18
  • 1970-01-01
  • 1970-01-01
  • 2017-01-08
  • 1970-01-01
  • 2015-10-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多