Ниже приведен очень короткий модульный тест 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;
}
}