XAML序列化 - 需要指定属性

时间:2015-03-12 01:36:56

标签: c# xaml serialization

我正在尝试使用XAML序列化/反序列化某些自定义(非WPF / UI)信息,并希望强制要求某些属性。

默认情况下,XAML反序列化只是使用默认构造函数创建每个对象,然后在元素的属性或属性元素语法中设置它找到的任何属性。未被序列化的XAML中指定的基础对象的任何属性都保持原样,即它们在构造之后获得的任何值。

我想知道指定XAML中必须存在某个属性的最佳方法 - 如果没有,则反序列化失败。

我期待某种属性,但我找不到任何东西。

WPF中有某些类型确实表现出这种行为,但可能WPF使用自己的自定义方式来强制执行此操作。例如,如果你有......

<Setter Property="Height" ></Setter>

..设计师会抱怨'财产“价值”缺失'。

我可以想到一些非常复杂的方法:

  1. 让每个属性setter以某种方式记录它被调用,然后在反序列化后运行自定义代码,检查所有“必需”属性是否实际设置。

  2. 在任何地方使用可空属性,然后在反序列化后检查是否有任何“必需”属性仍然为空。 (当然,如果null是设置内容的有效内容,这将无法工作!)

  3. 也许有一种编写自定义XamlObjectWriter的方法可以检查对象属性的某些[Required]属性,如果XamlReader找不到这些属性,则会失败。

  4. 这些听起来比我希望的要多得多 - 而且我确信有更明显的方法。有没有人有这个问题的任何其他想法或经验(也许是解决方案)?

2 个答案:

答案 0 :(得分:0)

我最近遇到了类似的问题。在找不到任何简单的方法后,我决定加入XamlObjectWriter的事件,为此添加自定义支持。这基本上就是你在第3点提出的建议,除非结果并不那么复杂。

基本上它的工作原理如下:字典保存在每个反序列化对象映射到一组剩余必需属性的位置。 BeforePropertiesHandler为当前对象填充此集合,其所有属性均为RequiredAttributeXamlSetValueHandler从集合中删除当前属性。最后,AfterPropertiesHandler确保在当前对象上没有设置任何必需的属性,否则抛出异常。

class RequiredAttribute : Attribute
{
}

public T Deserialize<T>(Stream stream)
{
    var requiredProperties = new Dictionary<object, HashSet<MemberInfo>>();

    var writerSettings = new XamlObjectWriterSettings
    {
        BeforePropertiesHandler = (sender, args) =>
        {
            var thisInstanceRequiredProperties = new HashSet<MemberInfo>();

            foreach(var propertyInfo in args.Instance.GetType().GetProperties())
            {
                if(propertyInfo.GetCustomAttribute<RequiredAttribute>() != null)
                {
                    thisInstanceRequiredProperties.Add(propertyInfo);
                }
            }

            requiredProperties[args.Instance] = thisInstanceRequiredProperties;
        },

        XamlSetValueHandler = (sender, args) =>
        {
            if(!requiredProperties.ContainsKey(sender))
            {
                return;
            }

            requiredProperties[sender].Remove(args.Member.UnderlyingMember);
        },

        AfterPropertiesHandler = (sender, args) =>
        {
            if(!requiredProperties.ContainsKey(args.Instance))
            {
                return;
            }

            var propertiesNotSet = requiredProperties[args.Instance];

            if(propertiesNotSet.Any())
            {
                throw new Exception("Required property \"" + propertiesNotSet.First().Name + "\" not set.");
            }

            requiredProperties.Remove(args.Instance);
        }
    };

    var readerSettings = new XamlXmlReaderSettings
    {
        LocalAssembly = Assembly.GetExecutingAssembly(),
        ProvideLineInfo = true
    };

    using(var reader = new XamlXmlReader(stream, readerSettings))
    using(var writer = new XamlObjectWriter(reader.SchemaContext, writerSettings))
    {
        XamlServices.Transform(reader, writer);
        return (T)writer.Result;
    }
}

答案 1 :(得分:0)

我知道这很老了,但是我在想出一个办法之前就碰到了这个问题,正在寻找一种方法。也许会帮助别人。幸运的是,这很容易:实现ISupportInitialize。我正在使用WPF,但是它应该可以在任何地方使用。

public class Hero : ISupportInitialize
{
    public string Who
    { get; set; } = string.Empty;

    public string What
    { get; set; } = string.Empty;

    public string Where
    { get; set; } = string.Empty;

    public void BeginInit()
    {
        // set a flag here if your property setters
        // have dependencies on other properties
    }

    public void EndInit()
    {
        if (string.IsNullOrWhiteSpace(Who))
            throw new Exception($"The property \"Who\" is missing");
        if (string.IsNullOrWhiteSpace(What))
            throw new Exception($"The property \"What\" is missing");
        // Where is optional...
    }
}

WhoWhat是必需的,但是第二个条目中缺少What

<Window.Resources>
    <local:Hero x:Key="Badguy" Who="Vader" What="Sith" Where="Death Star"/>
    <local:Hero x:Key="Goodguy" Who="HanSolo" Where="Millenium Falcon"/>
</Window.Resources>

在VS2017 XAML标记编辑器中:

enter image description here