c# - WCF:具有多个模块的数据协定序列化程序

标签 c# .net wcf serialization datacontractserializer

在我的一个 C# 项目中,我使用 WCF 数据协定序列化程序序列化为 XML。然而,该框架由多个扩展模块组成,这些模块可以加载或不加载,具体取决于某些启动配置(我使用 MEF 以防万一)。将来模块列表可能会增长,我担心这种情况有一天可能会对特定于模块的数据造成问题。据我所知,我可以实现一个数据协定解析器来双向帮助序列化程序定位类型,但是如果项目包含由于未加载关联模块而无法解释的数据,会发生什么情况?

我正在寻找一种解决方案,允许我在未加载(或什至不可用)完整模块集的情况下保留现有的序列化数据。我认为这是一种告诉反序列化器的方式“如果你不明白你得到的是什么,那么不要尝试序列化它,但请将数据保存在某个地方,以便你可以在序列化下一个时把它放回去时间”。我认为我的问题与 round-tripping 有关,但我(还)没有很成功地找到有关如何处理在序列化操作之间可能添加或删除复杂类型的情况的提示。

最小示例: 假设我使用可选模块 A、B 和 C 启动我的应用程序并生成以下 XML(AData、BData 和 CData 在一个集合中并且可能全部派生自公共(public)基类):

<Project xmlns="http://schemas.datacontract.org/2004/07/TestApplication" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Data>
        <ModuleData i:type="AData">
            <A>A</A>
        </ModuleData>
        <ModuleData i:type="BData">
            <B>B</B>
        </ModuleData>
        <ModuleData i:type="CData">
            <C>C</C>
        </ModuleData>
    </Data>
</Project>

如果我跳过模块 C(包含 CData 的定义)并加载同一个项目,那么序列化程序会失败,因为它不知道如何处理 CData。如果我能以某种方式设法说服框架保留数据并保持不变,直到有人再次使用模块 C 打开项目,那么我就赢了。当然,我可以实现用于存储扩展数据的动态数据结构,例如键值树,但在扩展模块中也使用现有的序列化框架会很整洁。非常感谢有关如何实现这一目标的任何提示!

生成上述输出的示例代码如下:

using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace TestApplication
{
    // common base class
    [DataContract]
    public class ModuleData : IExtensibleDataObject
    {
        public virtual ExtensionDataObject ExtensionData { get; set; }
    }

    [DataContract]
    public class AData : ModuleData
    {
        [DataMember]
        public string A { get; set; }
    }

    [DataContract]
    public class BData : ModuleData
    {
        [DataMember]
        public string B { get; set; }
    }

    [DataContract]
    public class CData : ModuleData
    {
        [DataMember]
        public string C { get; set; }
    }

    [DataContract]
    [KnownType(typeof(AData))]
    [KnownType(typeof(BData))]
    public class Project
    {
        [DataMember]
        public List<ModuleData> Data { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // new project object
            var project1 = new Project()
            {
                Data = new List<ModuleData>()
                {
                    new AData() { A = "A" },
                    new BData() { B = "B" },
                    new CData() { C = "C" }
                }
            };

            // serialization; make CData explicitly known to simulate presence of "module C"
            var stream = new MemoryStream();
            var serializer1 = new DataContractSerializer(typeof(Project), new[] { typeof(CData) });
            serializer1.WriteObject(stream, project1);

            stream.Position = 0;
            var reader = new StreamReader(stream);
            Console.WriteLine(reader.ReadToEnd());

            // deserialization; skip "module C"
            stream.Position = 0;
            var serializer2 = new DataContractSerializer(typeof(Project));
            var project2 = serializer2.ReadObject(stream) as Project;
        }
    }
}

我还上传了一个VS2015的解决方案here .

最佳答案

你的问题是你有一个polymorphic known type hierarchy ,并且您想使用 round-tripping mechanismDataContractSerializer读取和保存“未知”已知类型,特别是带有 xsi:type 的 XML 元素类型提示指的是当前未加载到您的应用程序域中的类型。

不幸的是,这个用例根本没有通过往返机制实现。该机制旨在在 ExtensionData 中缓存未知数据 成员对象,前提是数据契约对象本身可以成功反序列化并实现 IExtensibleDataObject .不幸的是,在您的情况下,无法准确构造数据协定对象,因为无法识别多态子类型;而是抛出以下异常:

System.Runtime.Serialization.SerializationException occurred
Message="Error in line 4 position 6. Element 'http://www.Question45412824.com:ModuleData' contains data of the 'http://www.Question45412824.com:CData' data contract. The deserializer has no knowledge of any type that maps to this contract. Add the type corresponding to 'CData' to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding it to the list of known types passed to DataContractSerializer."

即使我尝试创建一个标有 [CollectionDataContract] 的自定义通用集合实现 IExtensibleDataObject缓存具有无法识别的契约(Contract)的项目,会引发相同的异常。

一个解决方案 是利用您的问题比往返问题稍微简单的事实。您(软件架构师)实际上知道所有可能的多态子类型。 您的软件 没有,因为它并不总是加载包含它们的程序集。因此,您可以做的是加载轻量级虚拟类型,而不是在不需要真实类型时加载真实类型。只要虚拟类型实现 IExtensibleDataObject并且具有相同的数据协定命名空间和名称以及真实类型,它们的数据协定将与多态集合中的“真实”数据协定可互换。

因此,如果您按如下方式定义类型,添加一个 Dummies.CData虚拟占位符:

public static class Namespaces
{
    // The data contract namespace for your project.
    public const string ProjectNamespace = "http://www.Question45412824.com"; 
}

// common base class
[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class ModuleData : IExtensibleDataObject
{
    public ExtensionDataObject ExtensionData { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class AData : ModuleData
{
    [DataMember]
    public string A { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class BData : ModuleData
{
    [DataMember]
    public string B { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
[KnownType(typeof(AData))]
[KnownType(typeof(BData))]
public class Project
{
    [DataMember]
    public List<ModuleData> Data { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class CData : ModuleData
{
    [DataMember]
    public string C { get; set; }
}

namespace Dummies
{
    [DataContract(Namespace = Namespaces.ProjectNamespace)]
    public class CData : ModuleData
    {
    }
}

您将能够反序列化您的 Project使用“真实”的对象 CData或“虚拟”版本,如下面的测试所示:

class Program
{
    static void Main(string[] args)
    {
        new TestClass().Test();
    }
}

class TestClass
{
    public virtual void Test()
    {
        // new project object
        var project1 = new Project()
        {
            Data = new List<ModuleData>()
            {
                new AData() { A = "A" },
                new BData() { B = "B" },
                new CData() { C = "C" }
            }
        };

        // serialization; make CData explicitly known to simulate presence of "module C"
        var extraTypes = new[] { typeof(CData) };
        var extraTypesDummy = new[] { typeof(Dummies.CData) };

        var xml = project1.SerializeXml(extraTypes);

        ConsoleAndDebug.WriteLine(xml);

        // Demonstrate that the XML can be deserialized with the dummy CData type.
        TestDeserialize(project1, xml, extraTypesDummy);

        // Demonstrate that the XML can be deserialized with the real CData type.
        TestDeserialize(project1, xml, extraTypes);

        try
        {
            // Demonstrate that the XML cannot be deserialized without either the dummy or real type.
            TestDeserialize(project1, xml, new Type[0]);
            Assert.IsTrue(false);
        }
        catch (AssertionFailedException ex)
        {
            Console.WriteLine("Caught unexpected exception: ");
            Console.WriteLine(ex);
            throw;
        }
        catch (Exception ex)
        {
            ConsoleAndDebug.WriteLine(string.Format("Caught expected exception: {0}", ex.Message));
        }
    }

    public void TestDeserialize<TProject>(TProject project, string xml, Type[] extraTypes)
    {
        TestDeserialize<TProject>(xml, extraTypes);
    }

    public void TestDeserialize<TProject>(string xml, Type[] extraTypes)
    {
        var project2 = xml.DeserializeXml<TProject>(extraTypes);

        var xml2 = project2.SerializeXml(extraTypes);

        ConsoleAndDebug.WriteLine(xml2);

        // Assert that the incoming and re-serialized XML are equivalent (no data was lost).
        Assert.IsTrue(XNode.DeepEquals(XElement.Parse(xml), XElement.Parse(xml2)));
    }
}

public static partial class DataContractSerializerHelper
{
    public static string SerializeXml<T>(this T obj, Type [] extraTypes)
    {
        return obj.SerializeXml(new DataContractSerializer(obj == null ? typeof(T) : obj.GetType(), extraTypes));
    }

    public static string SerializeXml<T>(this T obj, DataContractSerializer serializer)
    {
        serializer = serializer ?? new DataContractSerializer(obj == null ? typeof(T) : obj.GetType());
        using (var textWriter = new StringWriter())
        {
            var settings = new XmlWriterSettings { Indent = true };
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
            {
                serializer.WriteObject(xmlWriter, obj);
            }
            return textWriter.ToString();
        }
    }

    public static T DeserializeXml<T>(this string xml, Type[] extraTypes)
    {
        return xml.DeserializeXml<T>(new DataContractSerializer(typeof(T), extraTypes));
    }

    public static T DeserializeXml<T>(this string xml, DataContractSerializer serializer)
    {
        using (var textReader = new StringReader(xml ?? ""))
        using (var xmlReader = XmlReader.Create(textReader))
        {
            return (T)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(xmlReader);
        }
    }
}

public static class ConsoleAndDebug
{
    public static void WriteLine(object s)
    {
        Console.WriteLine(s);
        Debug.WriteLine(s);
    }
}

public class AssertionFailedException : System.Exception
{
    public AssertionFailedException() : base() { }

    public AssertionFailedException(string s) : base(s) { }
}

public static class Assert
{
    public static void IsTrue(bool value)
    {
        if (value == false)
            throw new AssertionFailedException("failed");
    }
}

另一种解决方案 是替换您的 List<ModuleData>使用实现 IXmlSerializable 的自定义集合并完全手动处理多态序列化,在未知元素列表中缓存未知多态子类型的 XML。然而,我不建议这样做,因为即使是 IXmlSerializable 的直接实现也是如此。可能非常复杂,如图所示 here和,例如,here .

关于c# - WCF:具有多个模块的数据协定序列化程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45412824/

相关文章:

c# - Newtonsoft.Json.JsonSerializationException 未由用户代码处理

.net - 如何在 Viewbox 中拉伸(stretch) Grid

wcf - 使用Delphi Client和Windows身份验证访问WCF

wcf - FTP 425 无法在 IIS 后打开数据连接

.net - 使用 WCF 的契约优先 SOA

c# - 使用 AES/CBC/NoPadding 算法解密字符串

c# - 合并两个 IEnumerable<T>

c# - 在EntityFramework中保存多对多

c# - 是否有类似(值或错误)可区分联合的 .NET 类型?

c# - 有没有更好的方法来处理 LINQ to SQL 中的验证?