Проблемы с DataContractSerializer + DataContractResolver в UWP, .NET Собственная проблема?
Ниже приведен очень короткий модульный тест UWP, который пытается сериализовать, а затем десериализовать класс с именем Car, используя DataContractSerializer. Я планирую использовать этот тип кода в приложении UWP для сохранения состояния сеанса, когда приложение приостановлено. Поскольку я не хочу добавлять каждый тип в коллекцию KnownTypes, я украл простой пользовательский DataContractResolver из блога msdn; он должен работать, когда сериализация и десериализация происходят в одном приложении (и, следовательно, совместно использовать типы и сборки). Все это прекрасно работает, когда код работает с полной версией.NET Framework 4.6.2. Но странная вещь состоит в том, что точно такой же код терпит неудачу, он является частью универсального проекта Windows, ЕСЛИ Я также не включаю "Компилировать с помощью.NET Native tool chain".
Почему точно такой же код не работает в приложении 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;
}
}
1 ответ
Это было связано с ошибкой в .NETCore 5.2.2. Я думаю, что это исправлено в 5.2.3. Инженер в команде помог мне с этим. Казалось, работает, когда я скачал бета-версию сборок.