当根类型相同但内部XML可以表示不同类型时,反序列化XML

时间:2019-04-12 13:56:23

标签: c# xml

当发送回XML时,我必须在应用程序上工作。因为根始终是“ Reply”,但是属性不同,所以反序列化不能基于1个对象类型。

我现在编写了将Xml首先加载到新XmlDocument中的代码,读取了Name属性,并基于该属性,我尝试对其进行反序列化。在那儿 更好的方法?

我可以期望的xml示例:

<Reply Name="GetModulesList" Result="yes"><ModuleName="xxxxxx.exe" Path="\Debug\xxxxxxx.exe" Order="1"/></Reply>

<Reply Name="OpenRecipe" Result="yes"/>

您该如何解决?

                XmlDocument doc = new XmlDocument();
                doc.LoadXml(trimmedPart);
                if (doc.DocumentElement?.Attributes != null)
                {
                    XmlAttribute name = doc.DocumentElement.Attributes.Cast<XmlAttribute>().SingleOrDefault(a => String.Compare(a.Name, "name", StringComparison.OrdinalIgnoreCase) == 0);
                    replyName = name?.Value;
                }

                if (!string.IsNullOrEmpty(replyName))
                {
                    XmlSerializer serializer = new XmlSerializer(GetSerializerObjectType(replyName));
                    using (StringReader reader = new StringReader(trimmedPart))
                    {
                        object obj = serializer.Deserialize(reader);
                        if (obj != null) returnList.Add(obj);
                    }
                }

1 个答案:

答案 0 :(得分:0)

我最近处理了类似的情况。这是一个非常粗略的概述,它是根据我的具体情况专门针对我的情况而改编的。

我的假设是,您将使用不同的类型来做不同的事情。如果是OpenRecipe,您将做一件事;如果是GetModuleList,您将做其他事情。

最大的区别是,我使用不同类型的每个“处理程序”都可能处理完全不同类型的数据-XML,JSON甚至Excel,因此处理程序以字节数组的形式接收内容并负责反序列化它。 (写内部版本的Biztalk并不是我的主意。)

在这种情况下,如果始终为XML,则可以

  • 从“名称”属性确定内部XML的类型
  • 将内部XML反序列化为该类型
  • 将其传递给强类型的类

此类与反序列化Reply(未知内部内容除外)有关,并将其传递给将以更强类型化的方式处理该内部内容的内容:

public class XmlReplyRouter
{
    private readonly IReplyTypeMapper _replyTypeMapper;
    private readonly IHandlerFactory _handlerFactory;

    private readonly XmlSerializer _serializer = new XmlSerializer(typeof(Reply));

    public XmlReplyRouter(
        IReplyTypeMapper replyTypeMapper,
        IHandlerFactory handlerFactory)
    {
        _replyTypeMapper = replyTypeMapper;
        _handlerFactory = handlerFactory;
    }

    public void RouteReply(string replyXml)
    {
        using (var reader = new StringReader(replyXml))
        {
            var reply = (Reply)_serializer.Deserialize(reader);
            var replyType = _replyTypeMapper.GetReplyType(reply.Name);
            var handler = _handlerFactory.GetHandler(replyType);
            handler.HandleReply(reply);
        }
    }
}

public class Reply
{
    [XmlAttribute]
    public string Name { get; set; }

    [XmlAttribute]
    public string Result { get; set; }

    [XmlAnyElement]
    public XmlNode InnerXml { get; set; }

    public string XmlContent => InnerXml?.OuterXml;
}

这就是所有丑陋之处。
XmlAnyElement属性允许我们反序列化而不知道该内部内容的内容。一旦知道要使用哪种类型,就可以分别反序列化。

IReplyTypeMapperIHandlerFactory的实现可以是任何东西。就我而言,我不能使用DI容器,因此它包含一个Dictionary<string, IReplyHandler>并根据名称选择了正确的容器。 (我使用的术语不同,但是概念相同。)

第一个确定类型(通过“名称”属性),第二个返回该类型的处理程序。

public interface IReplyTypeMapper
{
    Type GetReplyType(string replyName);
}

public interface IHandlerFactory
{
    IReplyHandler GetHandler(Type contentType);
}

最后,这是回复处理程序的接口和基类。 如您所见,基类实际上并没有做任何事情。它只是弥合object与泛型类型之间的鸿沟,以便我们可以编写强类型的处理程序。

public interface IReplyHandler
{
    void HandleReply(object content);
}

public abstract class BaseHandler<T> : IReplyHandler
{
    private readonly XmlSerializer _serializer = new XmlSerializer(typeof(T));

    public void HandleReply(object content)
    {
        HandleReplyContent((T)content);
    }

    protected abstract void HandleReplyContent(T content);
}

如您所见,它与您所做的非常相似。不过,这样做的目的是,一旦我获得了最少的初始代码,其他所有东西都是像这样的强类型类:

public class SomeSpecificReplyHandler : BaseHandler<SomeSpecificReply>
{
    // Maybe these have dependencies of their own. That's easiest if 
    // everything gets resolved from an IoC container.
    protected override void HandleReplyContent(SomeSpecificReply content)
    {
        // do whatever
    }
}

我不知道这是否对您的需求来说是过大的。我的意图是从一个数据几乎可以进入任何地方的入口点开始,然后迅速走到那里,所有代码都经过严格类型化且易于测试。所有这些类都是可测试的(我在键入时写了一些),各个处理程序也很容易测试。

This post包含有关在没有IoC容器的情况下实现工厂的其他一些详细信息。就我而言,我无法使用IoC容器,因此我不得不“手动”编写所有类。工厂的内部实现只是字典。这样,如果以后可以选择切换到IoC容器,则可以用DI注册代替整个内容。