Проблемы с DataContractSerializer + DataContractResolver в UWP, проблема с .NET Native?

Ниже приведен очень короткий модульный тест UWP, который пытается сериализовать, а затем десериализовать класс с именем Car с помощью DataContractSerializer. Я планирую использовать этот тип кода в приложении UWP для сохранения состояния сеанса, когда приложение приостановлено. Поскольку я не хочу добавлять каждый тип в коллекцию KnownTypes, я украл простой настраиваемый DataContractResolver из блога msdn; он должен работать, когда сериализация и десериализация происходят в одном приложении (и, таким образом, имеют общие типы и сборки). Все это отлично работает, когда код работает с полной версией .NET Framework 4.6.2. Но СТРАННАЯ ВЕЩЬ заключается в том, что тот же самый код не работает, он является частью универсального проекта Windows, ЕСЛИ я не включил «Компилировать с помощью цепочки инструментов .NET Native».

Почему тот же самый код не будет работать в приложении UWP БЕЗ использования цепочки инструментов .NET Native? .NET Native должен вызывать сложности при сериализации, поэтому кажется очень странным, что мой код работает только в UWP, когда используется .NET Native. Как заставить его работать в приложении UWP БЕЗ использования .NET Native - компиляция в моих сборках 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;
            }
        }
    }
}

Вот полное содержимое файла rd.xml, необходимое для UWP, чтобы он работал, когда .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 для десериализации, поскольку новый преобразователь создает состояние / информацию во время сериализации. В комментариях объясняется, как кажется, что DataContractResolver по-разному используется в UWP и .NET 4.6.2. Я до сих пор не знаю, почему исходный код не удался, если не был включен .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;
    }
}

person JustinM    schedule 29.08.2016    source источник


Ответы (1)


Это произошло из-за ошибки в .NETCore 5.2.2. Думаю, это исправлено в 5.2.3. В этом мне помог инженер из команды. Вроде заработало, когда скачал бета-версию сборок.

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

person JustinM    schedule 01.09.2016
comment
Спасибо, чтобы сообщить об этой проблеме и поделиться подробностями здесь :) - person Franklin Chen - MSFT; 02.09.2016