更好的 IXmlSerializable 格式? [英] Better IXmlSerializable format?

查看:41
本文介绍了更好的 IXmlSerializable 格式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个接口 IInput 阻止 XmlSerializer 本地序列化类(因为它不喜欢接口).我发现了一个 hack/workaround,它试图在反序列化时只创建底层实现,然后将其转换回接口.解串器知道底层实现,因为它被编码为属性 AssemblyQualifiedName

I have an interface IInput that is preventing the XmlSerializer from serializing the class natively (because it doesnt like interfaces). I found a hack/workaround that attempts to just create the underlying implementation when deserializing and then casts that back to the interface. The deserializer knows the underlying implementation because its encoded as a attribute AssemblyQualifiedName

为了利用这项技术,我必须实现 IXmlSerializable,但只有 1 个属性确实需要帮助(IInput Input),我希望所有其他属性都像正常一样.这是我的课程,它按预期工作但似乎是获取普通 XMLserializer 可以序列化以符合 IXmlSerialiable 接口的类型的一种非常混乱的方式.

In order to take advantage of this technique I have to implement IXmlSerializable, but only 1 property really needs help (IInput Input), I wish for all the other ones to act as if they were normal. Here is my class, it works as expected but seems like a very messy way of getting types of which the normal XMLserializer can serialize to conform to an IXmlSerialiable interface.

是否有某种本机序列化除 x 之外的所有属性"?如果不是,我可以通过哪种方式使这本书更具可读性和/或更少的复制和粘贴

Is there some sort of "Serialize all properties natively except x"? If not what is one way I can make this more readable and/or less copy and pasted

public class JobInput : IJobInput, IXmlSerializable
    {
        public int AgencyId { get; set; }
        public Guid ExternalId { get; set; }
        public string Requester { get; set; }
        public IInput Input { get; set; }

        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            reader.MoveToContent();
            reader.ReadStartElement();

            if (!reader.IsEmptyElement) 
            {
                reader.ReadStartElement("AgencyId");
                var xmlSerializer = new XmlSerializer(AgencyId.GetType());
                AgencyId = ((int)xmlSerializer.Deserialize(reader));
                reader.ReadEndElement();

                reader.ReadStartElement("ExternalId");
                xmlSerializer = new XmlSerializer(ExternalId.GetType());
                ExternalId = ((Guid)xmlSerializer.Deserialize(reader));
                reader.ReadEndElement();

                reader.ReadStartElement("Requester");
                xmlSerializer = new XmlSerializer(typeof(string));
                Requester = ((string)xmlSerializer.Deserialize(reader));
                reader.ReadEndElement();

                var type = Type.GetType(reader.GetAttribute("AssemblyQualifiedName"), true);
                reader.ReadStartElement("IInput");
                xmlSerializer = new XmlSerializer(type);
                Input = ((IInput)xmlSerializer.Deserialize(reader));
                reader.ReadEndElement();
                reader.ReadEndElement();
            }
        }

        public void WriteXml(XmlWriter writer)
        {
            writer.WriteStartElement("AgencyId");
            var xmlSerializer = new XmlSerializer(AgencyId.GetType());
            xmlSerializer.Serialize(writer, AgencyId);
            writer.WriteEndElement();

            writer.WriteStartElement("ExternalId");
            xmlSerializer = new XmlSerializer(ExternalId.GetType());
            xmlSerializer.Serialize(writer, ExternalId);
            writer.WriteEndElement();

            writer.WriteStartElement("Requester");
            xmlSerializer = new XmlSerializer(Requester.GetType());
            xmlSerializer.Serialize(writer, Requester);
            writer.WriteEndElement();


            writer.WriteStartElement("IInput");
            writer.WriteAttributeString("AssemblyQualifiedName", Input.GetType().AssemblyQualifiedName);
            xmlSerializer = new XmlSerializer(Input.GetType());
            xmlSerializer.Serialize(writer, Input);
            writer.WriteEndElement();
        }
    }

是否有可能有一个通用函数来检测所有具体类型的类型并适当地序列化/反序列化.我想类似的东西

Is it possible to have a generic function that can just detect the type for all the concrete types and serialize/deserialize appropriately. I would like something like

public void WriteXml(XmlWriter writer) {
    GenericSerialize("AgencyId", AgencyId, writer);
    GenericSerialize("ExternalId", ExternalId, writer);
    GenericSerialize("Requester", Requester, writer);

    writer.WriteStartElement("IInput");
    writer.WriteAttributeString("AssemblyQualifiedName", Input.GetType().AssemblyQualifiedName);
    xmlSerializer = new XmlSerializer(Input.GetType());
    xmlSerializer.Serialize(writer, Input);
    writer.WriteEndElement();
}

推荐答案

您可以使用 [XmlAnyElement] 将一个 XElement []-valued 属性添加到处理无法自动序列化的属性的序列化和反序列化,如下所示:

You can use [XmlAnyElement] to add an XElement []-valued property to your class that handles serialization and deserialization of properties that cannot be automatically serialized, like so:

[XmlRoot(Namespace = JobInput.XmlNamespace)]
[XmlType(Namespace = JobInput.XmlNamespace)]
public class JobInput
{
    const string XmlNamespace = "";

    public int AgencyId { get; set; }
    public Guid ExternalId { get; set; }
    public string Requester { get; set; }

    [XmlIgnore]
    public IInput Input { get; set; }

    [XmlAnyElement]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public XElement[] XmlCustomElements
    {
        get
        {
            var list = new List<XElement>();
            if (Input != null)
                list.Add(Input.SerializePolymorphicToXElement(XName.Get("Input", XmlNamespace)));
            // Add others as needed.
            return list.ToArray();
        }
        set
        {
            if (value == null)
                return;
            this.Input = value.DeserializePolymorphicEntry<IInput>(XName.Get("Input", XmlNamespace));
            // Add others as needed.
        }
    }
}

您的标准属性现在将自动序列化,您的自定义属性可以通过嵌套调用使用适当的类型对 XmlSerializer 进行半自动序列化.需要以下扩展方法:

Your standard properties will now get automatically serialized and your custom properties can be semi-automatically serialized through nested calls to XmlSerializer using the appropriate type. The following extension methods are required:

public static class XObjectExtensions
{
    public static XmlSerializerNamespaces NoStandardXmlNamespaces()
    {
        var ns = new XmlSerializerNamespaces();
        ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
        return ns;
    }

    public static object Deserialize(this XContainer element, Type type, XmlSerializer serializer)
    {
        using (var reader = element.CreateReader())
        {
            return (serializer ?? new XmlSerializer(type)).Deserialize(reader);
        }
    }

    public static XElement SerializeToXElement<T>(this T obj, XmlSerializerNamespaces ns, XmlSerializer serializer)
    {
        var doc = new XDocument();
        using (var writer = doc.CreateWriter())
            (serializer ?? new XmlSerializer(obj.GetType())).Serialize(writer, obj, ns);
        var element = doc.Root;
        if (element != null)
            element.Remove();
        return element;
    }

    const string TypeAttributeName = "AssemblyQualifiedName";

    public static T DeserializePolymorphicEntry<T>(this XElement[] arrayValue, XName name)
    {
        var element = arrayValue.Where(e => e.Name == name).FirstOrDefault();
        return element.DeserializePolymorphic<T>(name);
    }

    public static T DeserializePolymorphic<T>(this XElement value, XName name)
    {
        if (value == null)
            return default(T);
        var typeName = (string)value.Attribute(TypeAttributeName);
        if (typeName == null)
            throw new InvalidOperationException(string.Format("Missing AssemblyQualifiedName for \"{0}\"", value.ToString()));
        var type = Type.GetType(typeName, true); // Throw on error
        return (T)value.Deserialize(type, XmlSerializerFactory.Create(type, name));
    }

    public static XElement SerializePolymorphicToXElement<T>(this T obj, XName name)
    {
        if (obj == null)
            return null;
        var element = obj.SerializeToXElement(XObjectExtensions.NoStandardXmlNamespaces(), XmlSerializerFactory.Create(obj.GetType(), name));
        // Remove namespace attributes (they will be added back by the XmlWriter if needed)
        foreach (var attr in element.Attributes().Where(a => a.IsNamespaceDeclaration).ToList())
            attr.Remove();
        element.Add(new XAttribute("AssemblyQualifiedName", obj.GetType().AssemblyQualifiedName));
        return element;
    }
}

public static class XmlSerializerFactory
{
    static readonly object padlock;
    static readonly Dictionary<Tuple<Type, XName>, XmlSerializer> serializers;

    // An explicit static constructor enables fairly lazy initialization.
    static XmlSerializerFactory()
    {
        padlock = new object();
        serializers = new Dictionary<Tuple<Type, XName>, XmlSerializer>();
    }

    /// <summary>
    /// Return a cached XmlSerializer for the given type and root name.
    /// </summary>
    /// <param name="type"></param>
    /// <param name="name"></param>
    /// <returns>a cached XmlSerializer</returns>
    public static XmlSerializer Create(Type type, XName name)
    {
        // According to https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=vs.110%29.aspx
        // XmlSerializers created with new XmlSerializer(Type, XmlRootAttribute) must be cached in a hash table 
        // to avoid a severe memory leak & performance hit.
        if (type == null)
            throw new ArgumentNullException();
        if (name == null)
            return new XmlSerializer(type);
        lock (padlock)
        {
            XmlSerializer serializer;
            var key = Tuple.Create(type, name);
            if (!serializers.TryGetValue(key, out serializer))
                serializers[key] = serializer = new XmlSerializer(type, new XmlRootAttribute { ElementName = name.LocalName, Namespace = name.NamespaceName });
            return serializer;
        }
    }
}

这样做可以让您的类看起来更简单,并减少由于实施 IXmlSerializable 时的错误而导致错误的可能性 - 但它确实需要一些可重用的基础设施.

Doing it this way makes your class look simpler and reduces the possibility of bugs from mistakes in implementing IXmlSerializable - but it does require a little bit of reusable infrastructure.

原型 fiddle.

这篇关于更好的 IXmlSerializable 格式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆