我正在尝试使用XAML序列化/反序列化某些自定义(非WPF / UI)信息,并希望强制要求某些属性。
默认情况下,XAML反序列化只是使用默认构造函数创建每个对象,然后在元素的属性或属性元素语法中设置它找到的任何属性。未被序列化的XAML中指定的基础对象的任何属性都保持原样,即它们在构造之后获得的任何值。
我想知道指定XAML中必须存在某个属性的最佳方法 - 如果没有,则反序列化失败。
我期待某种属性,但我找不到任何东西。
WPF中有某些类型确实表现出这种行为,但可能WPF使用自己的自定义方式来强制执行此操作。例如,如果你有......
<Setter Property="Height" ></Setter>
..设计师会抱怨'财产“价值”缺失'。
我可以想到一些非常复杂的方法:
让每个属性setter以某种方式记录它被调用,然后在反序列化后运行自定义代码,检查所有“必需”属性是否实际设置。
在任何地方使用可空属性,然后在反序列化后检查是否有任何“必需”属性仍然为空。 (当然,如果null是设置内容的有效内容,这将无法工作!)
也许有一种编写自定义XamlObjectWriter的方法可以检查对象属性的某些[Required]属性,如果XamlReader找不到这些属性,则会失败。
这些听起来比我希望的要多得多 - 而且我确信有更明显的方法。有没有人有这个问题的任何其他想法或经验(也许是解决方案)?
答案 0 :(得分:0)
我最近遇到了类似的问题。在找不到任何简单的方法后,我决定加入XamlObjectWriter
的事件,为此添加自定义支持。这基本上就是你在第3点提出的建议,除非结果并不那么复杂。
基本上它的工作原理如下:字典保存在每个反序列化对象映射到一组剩余必需属性的位置。 BeforePropertiesHandler
为当前对象填充此集合,其所有属性均为RequiredAttribute
。 XamlSetValueHandler
从集合中删除当前属性。最后,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...
}
}
Who
和What
是必需的,但是第二个条目中缺少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标记编辑器中: