在UWP,.NET Native问题上出现DataContractSerializer + DataContractResolver问题?

时间:2016-08-29 07:40:03

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

下面是一个非常简短的UWP单元测试,它尝试使用DataContractSerializer序列化然后反序列化一个名为Car的类。我计划在UWP应用程序中使用此类代码,以便在应用程序暂停时保存会话状态。因为我不想将所有类型添加到KnownTypes集合中,所以我从msdn博客中偷了一个简单的自定义DataContractResolver;它应该在序列化和反序列化在同一个应用程序中发生时工作(从而共享类型和程序集)。当代码与完整的.NET Framework 4.6.2一起运行时,它完美地运行。但是,WEIRD THING是完全相同的代码失败它是通用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的新实例进行反序列化,因为新的解析器在序列化期间构建状态/信息。这些评论解释了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;
    }
}

1 个答案:

答案 0 :(得分:0)

这是由于.NETCore 5.2.2中的一个错误。我认为它已在5.2.3中修复。团队中的工程师帮助了我。当我下载测试版的程序集时,它似乎有效。

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