【问题标题】:Problems with DataContractSerializer + DataContractResolver on UWP, .NET Native issue?UWP 上的 DataContractSerializer + DataContractResolver 问题,.NET Native 问题?
【发布时间】:2016-08-29 07:40:02
【问题描述】:

下面是一个非常简短的 UWP 单元测试,它尝试使用 DataContractSerializer 序列化然后反序列化名为 Car 的类。我计划在 UWP 应用程序中使用这种类型的代码来在应用程序暂停时保存会话状态。因为我不想将每种类型都添加到 KnownTypes 集合中,所以我从 msdn 博客中偷了一个简单的自定义 DataContractResolver;当序列化和反序列化发生在同一个应用程序中时,它应该可以工作(因此共享类型和程序集)。当代码使用完整的 .NET Framework 4.6.2 运行时,这一切都可以完美运行。但奇怪的是,完全相同的代码失败了,它是通用 Windows 项目的一部分,除非我还打开“使用 .NET Native 工具链编译”。

为什么不使用 .NET Native 工具链就不能在 UWP 应用上运行完全相同的代码? .NET Native 应该会导致序列化复杂化,所以我的代码在使用 .NET Native 时只能在 UWP 上运行似乎很奇怪。如何在不使用 .NET Native 的情况下让它在 UWP 应用程序上工作 - 打开时,我的 DEBUG 版本的编译速度会显着减慢。

这里是包含两个单元测试的完整解决方案的 GitHub 链接。 https://github.com/jmagaram/CustomResolver

单元测试代码如下:

using System;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using System.Runtime.Serialization;
using System.Xml;
using System.Reflection;
using System.Collections.Generic;
using System.IO;

namespace ResolverTest {
    [TestClass]
    public class SerializerTestUniversal {
        [TestMethod]
        public void CanRoundtripComplexTypeWithNoKnownTypesAndCustomResolver() {
            // prepare object for serialization
            var car = new Car { Year = 2000, Model = "Ford" };
            var rootToSerialize = new Dictionary<string, object> { ["car"] = car };

            // serialize with DataContractSerializer and NO known types
            // hopefully the custom DataContractResolver will make it work
            var serializer = new DataContractSerializer(
                typeof(Dictionary<string, object>),
                new DataContractSerializerSettings { DataContractResolver = new SharedTypedResolver() });
            var memoryStream = new MemoryStream();
            serializer.WriteObject(memoryStream, rootToSerialize);

            // deserialize
            memoryStream.Position = 0;
            var output = (Dictionary<string, object>)(serializer.ReadObject(memoryStream));
            var outputCar = (Car)output["car"];

            // check that the data got roundtripped correctly
            Assert.AreEqual(car.Year, outputCar.Year);
            Assert.AreEqual(car.Model, outputCar.Model);
        }

        public class Car {
            public int Year { get; set; }
            public string Model { get; set; }
        }

        // To be used when serializing and deserializing on same machine with types defined in a shared assembly
        // Intended to used for suspend/resume serialization in UWP apps
        // Code from https://blogs.msdn.microsoft.com/youssefm/2009/06/05/configuring-known-types-dynamically-introducing-the-datacontractresolver/
        public class SharedTypedResolver : DataContractResolver {
            public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver) {
                return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null) ?? Type.GetType($"{typeName}, {typeNamespace}");
            }

            public override bool TryResolveType(Type dataContractType, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace) {
                if (!knownTypeResolver.TryResolveType(dataContractType, declaredType, null, out typeName, out typeNamespace)) {
                    XmlDictionary dictionary = new XmlDictionary();
                    typeName = dictionary.Add(dataContractType.FullName);
                    typeNamespace = dictionary.Add(dataContractType.GetTypeInfo().Assembly.FullName);
                }
                return true;
            }
        }
    }
}

这是 UWP 所需的完整 rd.xml 文件内容,以便在打开 .NET Native 时使其工作。

<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
  <Application>
    <Assembly Name="*Application*" Dynamic="Required All" />
    <Type Name="ResolverTest.SerializerTestUniversal.Car" Browse="Required Public" DataContractSerializer="Required All"/>
  </Application>
</Directives>

最后,这是关闭 .NET Native 时发生的异常:

Result Message: Test method ResolverTest.SerializerTestUniversal.CanRoundtripComplexTypeWithNoKnownTypesAndCustomResolver threw exception: 
System.Runtime.Serialization.SerializationException: Type 'ResolverTest.SerializerTestUniversal+Car' with data contract name 'SerializerTestUniversal.Car:http://schemas.datacontract.org/2004/07/ResolverTest' is not expected. Add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.

== 更新 ==

我能够让它与不同的 DataContractResolver 一起工作。请参阅下面的代码。我还更改了测试以使用 DataContractSerializer 的新实例进行反序列化,因为新的解析器会在序列化期间建立状态/信息。 cmets 解释了 UWP 和 .NET 4.6.2 使用 DataContractResolver 的方式。除非打开 .NET Native,否则我仍然不知道为什么原始代码会失败。

public class SharedTypeResolver : DataContractResolver {
    Type _mostRecentResolvedType = null;

    // When an object is serialized using the Universal Windows Platform (as of version
    // 5.2.2), the ResolveName method is called for each type it encounters immediately after
    // calling TryResolveType. The Microsoft API specification says the ResolveName method is
    // used to 'map the specified xsi:type name and namespace to a data contract type during
    // deserialization', so it is a bit surprising this method is called during
    // serialization. If ResolveName does not return a valid type during serialization,
    // serialization fails. This behavior (and the failure) seems to be unique to the UWP.
    // ResolveName is not called during serialization on the .Net Framework 4.6.2.
    //
    // During serialization it is difficult to force ResolveName to return a valid type
    // because the typeName and typeNamespace do not include the assembly, and
    // Type.GetType(string) can only find a type if it is in the currently executing assembly
    // or it if has an assembly-qualified name. Another challenge is that the typeName and
    // typeNamespace parameters are formatted differently than Type.FullName, so string
    // parsing is necessary. For example, the typeNamespace parameter looks like
    // http://schemas.datacontract.org/2004/07/namespace and the typeName parameter is
    // formatted as className+nestedClassName. Type.FullName returns a single string like
    // namespace.class+nestedClass. But even worse, generic types show up in ResolveName
    // during serialization with names like 'StackOfint'. So the HACK approach I've taken
    // here is to cache the last Type seen in the TryResolveType method. Whenever a
    // typeNamespace appears in ResolveName that does not look like a real assembly name,
    // return the cached type.
    //
    // During deserialization it is very easy for this method to generate a valid type name because the XML
    // file that was generated contains the full assembly qualified name.
    public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver) {
        if (typeNamespace.StartsWith("http://schemas.datacontract.org")) {
            // Should only happen on UWP when serializing, since ResolveName is called
            // immediately after TryResolveType.
            return _mostRecentResolvedType;
        }
        else {
            // Should happen when deserializing and should work with all types serialized
            // with thie resolver.
            string assemblyQualifiedTypeName = $"{typeName}, {typeNamespace}";
            return Type.GetType(assemblyQualifiedTypeName);
        }
    }

    public override bool TryResolveType(Type dataContractType, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace) {
        _mostRecentResolvedType = dataContractType;
        XmlDictionary dictionary = new XmlDictionary();
        typeName = dictionary.Add(dataContractType.FullName);
        typeNamespace = dictionary.Add(dataContractType.GetTypeInfo().Assembly.FullName);
        return true;
    }
}

【问题讨论】:

    标签: c# win-universal-app uwp datacontractserializer .net-native


    【解决方案1】:

    这是由于 .NETCore 5.2.2 中的错误造成的。我认为它已在 5.2.3 中修复。团队中的一位工程师帮助我解决了这个问题。当我下载程序集的测试版时,它似乎可以工作。

    https://github.com/dotnet/corefx/issues/10155

    【讨论】:

    • 感谢报告此问题并在此处分享详细信息:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-22
    • 2011-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-09
    相关资源
    最近更新 更多