将自定义 DataContractResolver 与多个程序集一起使用
Using custom DataContractResolver with multiple assemblies
我在 MEF 应用程序中有以下设置:
程序集MyBaseAssembly:
namespace My.Namespace
{
[DataContract]
public class Container
{
[DataMember]
public Data Item { get; set; }
}
[DataContract]
public class Data
{
[DataMember]
public string Foo { get; set; }
}
}
Assembly SecondAssembly,引用 MyBaseAssembly:
namespace My.Another.Namespace
{
[DataContract]
public class SecondData : Data
{
[DataMember]
public string Bar { get; set; }
}
}
我在应用程序深处的某个地方创建了一个 Container
对象:
Container container = new Container();
container.Item = new SecondData { Bar = "test" };
我想序列化和反序列化 container
对象。由于 SecondAssembly 是一个 MEF 模块,我需要动态检测和解析数据协定中的类型,因此 KnownTypeAttribute
不是一个好的解决方案。
我创建了一个自定义DataContractResolver
,但我不知道如何获取反序列化的程序集信息。
在序列化时,我得到以下 XML:
<d4p1:SecondData
xmlns:d6p1="http://schemas.datacontract.org/2004/07/My.Another.Namespace"
i:type="d7p1:My.Another.Namespace.SecondData">
...
</d4p1:SecondData>
这是默认的DataContract
序列化行为:我们得到了类型名称和类型命名空间,但是没有(显式的)程序集信息!
试图反序列化此 XML,我无法确定要使用哪个程序集来解析类型:
class SerializationTypeResolver : DataContractResolver
{
...
public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
{
Type result = knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);
if (result == null)
{
// Here, I cannot rely on the declaredType parameter,
// because it contains the declared type which is Data from MyBaseAssembly.
// But I need the SecondData from the SecondAssembly!
string assemblyName = ???; // How do I get this assembly name?
string fullTypeName = typeName + ", " + assemblyName;
result = Type.GetType(fullTypeName);
}
return result;
}
}
所以我的问题是:在序列化和反序列化 DataContract
时存储和获取程序集名称的好方法是什么?
您将需要遍历执行程序集的所有引用程序集(无论是否已加载)并查找可从 declaredType
分配的类型。答案C# Reflection: Get all active assemblies in a solution?给出了起点
class SerializationTypeResolver : DataContractResolver
{
public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
{
Type result = knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);
if (result == null)
{
foreach (var derivedType in declaredType.DerivedTypes())
{
XmlDictionaryString derivedTypeName;
XmlDictionaryString derivedTypeNamespace;
// Figure out if this derived type has the same data contract name and namespace as the incoming name & namespace.
if (knownTypeResolver.TryResolveType(derivedType, derivedType, null, out derivedTypeName, out derivedTypeNamespace))
{
if (derivedTypeName.Value == typeName && derivedTypeNamespace.Value == typeNamespace)
{
return derivedType;
}
}
}
}
return result;
}
}
public static class TypeExtensions
{
public static IEnumerable<Type> DerivedTypes(this Type baseType)
{
// TODO: Optimization: check if baseType is private or internal.
var assemblies = baseType.Assembly.GetReferencingAssembliesAndSelf();
Debug.Assert(assemblies.Count() == assemblies.Distinct().Count());
return assemblies
.SelectMany(a => a.GetTypes())
.Where(t => baseType.IsAssignableFrom(t));
}
// Not sure which of the two versions of this method give better performance -- you might want to test yourself.
public static IEnumerable<Type> DerivedTypesFromAllAssemblies(this Type baseType)
{
// TODO: Optimization: check if baseType is private or internal.
var assemblies = AssemblyExtensions.GetAllAssemblies();
Debug.Assert(assemblies.Count() == assemblies.Distinct().Count());
return assemblies
.SelectMany(a => a.GetTypes())
.Where(t => baseType.IsAssignableFrom(t));
}
}
public static class AssemblyExtensions
{
public static IEnumerable<Assembly> GetAllAssemblies()
{
// Adapted from
//
return Assembly.GetEntryAssembly().GetAllReferencedAssemblies();
}
public static IEnumerable<Assembly> GetAllReferencedAssemblies(this Assembly root)
{
// WARNING: Assembly.GetAllReferencedAssemblies() will optimize away any reference if there
// is not an explicit use of a type in that assembly from the referring assembly --
// And simply adding an attribute like [XmlInclude(typeof(T))] seems not to do
// the trick. See
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/17f89058-5780-48c5-a43a-dbb4edab43ed/getreferencedassemblies-not-returning-complete-list?forum=netfxbcl
// Thus if you are using this to, say, discover all derived types of a base type, the assembly
// of the derived types MUST contain at least one type that is referenced explicitly from the
// root assembly, directly or indirectly.
var list = new HashSet<string>();
var stack = new Stack<Assembly>();
stack.Push(root);
do
{
var asm = stack.Pop();
yield return asm;
foreach (var reference in asm.GetReferencedAssemblies())
if (!list.Contains(reference.FullName))
{
stack.Push(Assembly.Load(reference));
list.Add(reference.FullName);
}
}
while (stack.Count > 0);
}
public static IEnumerable<Assembly> GetReferencingAssemblies(this Assembly target)
{
if (target == null)
throw new ArgumentNullException();
// Assemblies can have circular references:
//
// So a naive algorithm isn't going to work.
var done = new HashSet<Assembly>();
var root = Assembly.GetEntryAssembly();
var allAssemblies = root.GetAllReferencedAssemblies().ToList();
foreach (var assembly in GetAllAssemblies())
{
if (target == assembly)
continue;
if (done.Contains(assembly))
continue;
var refersTo = (assembly == root ? allAssemblies : assembly.GetAllReferencedAssemblies()).Contains(target);
done.Add(assembly);
if (refersTo)
yield return assembly;
}
}
public static IEnumerable<Assembly> GetReferencingAssembliesAndSelf(this Assembly target)
{
return new[] { target }.Concat(target.GetReferencingAssemblies());
}
}
顺便说一下,您可以使用 DataContractSerializer(Type, IEnumerable<Type>)
构造函数代替合同解析器。
老实说,性能不是很好,因为代码加载了根程序集引用的所有程序集,包括 Microsoft DLL 和第 3 方 DLL。您可能想开发一些方法来通过检查 name before loading, for instance by skipping Microsoft assemblies if the base class comes from your own codebase.
来减少要加载的程序集的数量
为什么序列化时不使用 AssemblyQualifiedName?像这样:
internal class SerializationTypeResolver : DataContractResolver {
public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace) {
// not necessary to hardcode some type name of course, you can use some broader condition
// like if type belongs to another assembly
if (type.Name == "SecondData") {
XmlDictionary dictionary = new XmlDictionary();
// use assembly qualified name
typeName = dictionary.Add(type.AssemblyQualifiedName);
typeNamespace = dictionary.Add("http://tempuri.org"); // some namespace, does not really matter in this case
return true;
}
return knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
}
public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver) {
if (typeNamespace == "http://tempuri.org") {
return Type.GetType(typeName); // assembly qualified already
}
return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);
}
}
数百年前,我遇到过类似的情况 - 它不是 MEF,而是我们手工制作的类似 MEF 的架构。 (MEF 在那些日子里的名声不好。)我们对数据合同序列化程序的创建进行了真正的集中管理,因此很容易插入代理提供程序。
它本身不是合同解析器 - 但最终以类似的方式工作,并在与解析器相同的时间和地点插入序列化管道。
我是凭记忆勾勒的,这么多年过去了,这很容易出错,但事情是这样的。我不记得的一个细节是 AssemblyAwareSurrogate 序列化的是字节数组还是字符串。我想可以走任何一条路。
public class AssembyAwareSurrogateProvider: IDataContractSurrogate
{
[DataContract]
class AssemblyAwareSurrogate
{
[DataMember]
public string AssemblyName { get; set; }
[DataMember]
public string TypeName { get; set; }
[DataMember]
public byte[ ] Object { get; set; }
public AssemblyAwareSurrogate( object obj )
{
this.AssemblyName = obj.GetType( ).Assembly.FullName;
this.TypeName = obj.GetType( ).FullName;
var serializer = new DataContractSerializer( obj.GetType( ) );
using ( var stream = new MemoryStream( ) )
{
serializer.WriteObject( stream, obj );
stream.Flush( );
Object = stream.ToArray( );
}
}
}
public Type GetDataContractType( Type type )
{
if ( SatisifesConstraints( type ) ) return typeof( AssemblyAwareSurrogate );
return type;
}
private bool SatisifesConstraints( Type type )
{
//--> er - whatever types you're insterested in...
return type != typeof( AssemblyAwareSurrogate );
}
public object GetDeserializedObject( object obj, Type targetType )
{
var surrogate = obj as AssemblyAwareSurrogate;
if ( surrogate != null )
{
var assy = Assembly.Load( new AssemblyName( surrogate.AssemblyName ) );
var serializer = new DataContractSerializer( assy.GetType( surrogate.TypeName ) );
using ( var stream = new MemoryStream( surrogate.Object ) )
{
return serializer.ReadObject( stream );
}
}
return obj;
}
public object GetObjectToSerialize( object obj, Type targetType )
{
if ( SatisifesConstraints( obj.GetType( ) ) )
{
return new AssemblyAwareSurrogate( obj );
}
return obj;
}
public object GetCustomDataToExport( Type clrType, Type dataContractType )
{
return null;
}
public object GetCustomDataToExport( MemberInfo memberInfo, Type dataContractType )
{
return null;
}
public void GetKnownCustomDataTypes( Collection<Type> customDataTypes )
{
throw new NotImplementedException( );
}
public Type GetReferencedTypeOnImport( string typeName, string typeNamespace, object customData )
{
throw new NotImplementedException( );
}
public CodeTypeDeclaration ProcessImportedType( CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit )
{
throw new NotImplementedException( );
}
}
我在 MEF 应用程序中有以下设置:
程序集MyBaseAssembly:
namespace My.Namespace
{
[DataContract]
public class Container
{
[DataMember]
public Data Item { get; set; }
}
[DataContract]
public class Data
{
[DataMember]
public string Foo { get; set; }
}
}
Assembly SecondAssembly,引用 MyBaseAssembly:
namespace My.Another.Namespace
{
[DataContract]
public class SecondData : Data
{
[DataMember]
public string Bar { get; set; }
}
}
我在应用程序深处的某个地方创建了一个 Container
对象:
Container container = new Container();
container.Item = new SecondData { Bar = "test" };
我想序列化和反序列化 container
对象。由于 SecondAssembly 是一个 MEF 模块,我需要动态检测和解析数据协定中的类型,因此 KnownTypeAttribute
不是一个好的解决方案。
我创建了一个自定义DataContractResolver
,但我不知道如何获取反序列化的程序集信息。
在序列化时,我得到以下 XML:
<d4p1:SecondData
xmlns:d6p1="http://schemas.datacontract.org/2004/07/My.Another.Namespace"
i:type="d7p1:My.Another.Namespace.SecondData">
...
</d4p1:SecondData>
这是默认的DataContract
序列化行为:我们得到了类型名称和类型命名空间,但是没有(显式的)程序集信息!
试图反序列化此 XML,我无法确定要使用哪个程序集来解析类型:
class SerializationTypeResolver : DataContractResolver
{
...
public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
{
Type result = knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);
if (result == null)
{
// Here, I cannot rely on the declaredType parameter,
// because it contains the declared type which is Data from MyBaseAssembly.
// But I need the SecondData from the SecondAssembly!
string assemblyName = ???; // How do I get this assembly name?
string fullTypeName = typeName + ", " + assemblyName;
result = Type.GetType(fullTypeName);
}
return result;
}
}
所以我的问题是:在序列化和反序列化 DataContract
时存储和获取程序集名称的好方法是什么?
您将需要遍历执行程序集的所有引用程序集(无论是否已加载)并查找可从 declaredType
分配的类型。答案C# Reflection: Get all active assemblies in a solution?给出了起点
class SerializationTypeResolver : DataContractResolver
{
public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
{
Type result = knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);
if (result == null)
{
foreach (var derivedType in declaredType.DerivedTypes())
{
XmlDictionaryString derivedTypeName;
XmlDictionaryString derivedTypeNamespace;
// Figure out if this derived type has the same data contract name and namespace as the incoming name & namespace.
if (knownTypeResolver.TryResolveType(derivedType, derivedType, null, out derivedTypeName, out derivedTypeNamespace))
{
if (derivedTypeName.Value == typeName && derivedTypeNamespace.Value == typeNamespace)
{
return derivedType;
}
}
}
}
return result;
}
}
public static class TypeExtensions
{
public static IEnumerable<Type> DerivedTypes(this Type baseType)
{
// TODO: Optimization: check if baseType is private or internal.
var assemblies = baseType.Assembly.GetReferencingAssembliesAndSelf();
Debug.Assert(assemblies.Count() == assemblies.Distinct().Count());
return assemblies
.SelectMany(a => a.GetTypes())
.Where(t => baseType.IsAssignableFrom(t));
}
// Not sure which of the two versions of this method give better performance -- you might want to test yourself.
public static IEnumerable<Type> DerivedTypesFromAllAssemblies(this Type baseType)
{
// TODO: Optimization: check if baseType is private or internal.
var assemblies = AssemblyExtensions.GetAllAssemblies();
Debug.Assert(assemblies.Count() == assemblies.Distinct().Count());
return assemblies
.SelectMany(a => a.GetTypes())
.Where(t => baseType.IsAssignableFrom(t));
}
}
public static class AssemblyExtensions
{
public static IEnumerable<Assembly> GetAllAssemblies()
{
// Adapted from
//
return Assembly.GetEntryAssembly().GetAllReferencedAssemblies();
}
public static IEnumerable<Assembly> GetAllReferencedAssemblies(this Assembly root)
{
// WARNING: Assembly.GetAllReferencedAssemblies() will optimize away any reference if there
// is not an explicit use of a type in that assembly from the referring assembly --
// And simply adding an attribute like [XmlInclude(typeof(T))] seems not to do
// the trick. See
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/17f89058-5780-48c5-a43a-dbb4edab43ed/getreferencedassemblies-not-returning-complete-list?forum=netfxbcl
// Thus if you are using this to, say, discover all derived types of a base type, the assembly
// of the derived types MUST contain at least one type that is referenced explicitly from the
// root assembly, directly or indirectly.
var list = new HashSet<string>();
var stack = new Stack<Assembly>();
stack.Push(root);
do
{
var asm = stack.Pop();
yield return asm;
foreach (var reference in asm.GetReferencedAssemblies())
if (!list.Contains(reference.FullName))
{
stack.Push(Assembly.Load(reference));
list.Add(reference.FullName);
}
}
while (stack.Count > 0);
}
public static IEnumerable<Assembly> GetReferencingAssemblies(this Assembly target)
{
if (target == null)
throw new ArgumentNullException();
// Assemblies can have circular references:
//
// So a naive algorithm isn't going to work.
var done = new HashSet<Assembly>();
var root = Assembly.GetEntryAssembly();
var allAssemblies = root.GetAllReferencedAssemblies().ToList();
foreach (var assembly in GetAllAssemblies())
{
if (target == assembly)
continue;
if (done.Contains(assembly))
continue;
var refersTo = (assembly == root ? allAssemblies : assembly.GetAllReferencedAssemblies()).Contains(target);
done.Add(assembly);
if (refersTo)
yield return assembly;
}
}
public static IEnumerable<Assembly> GetReferencingAssembliesAndSelf(this Assembly target)
{
return new[] { target }.Concat(target.GetReferencingAssemblies());
}
}
顺便说一下,您可以使用 DataContractSerializer(Type, IEnumerable<Type>)
构造函数代替合同解析器。
老实说,性能不是很好,因为代码加载了根程序集引用的所有程序集,包括 Microsoft DLL 和第 3 方 DLL。您可能想开发一些方法来通过检查 name before loading, for instance by skipping Microsoft assemblies if the base class comes from your own codebase.
来减少要加载的程序集的数量为什么序列化时不使用 AssemblyQualifiedName?像这样:
internal class SerializationTypeResolver : DataContractResolver {
public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace) {
// not necessary to hardcode some type name of course, you can use some broader condition
// like if type belongs to another assembly
if (type.Name == "SecondData") {
XmlDictionary dictionary = new XmlDictionary();
// use assembly qualified name
typeName = dictionary.Add(type.AssemblyQualifiedName);
typeNamespace = dictionary.Add("http://tempuri.org"); // some namespace, does not really matter in this case
return true;
}
return knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
}
public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver) {
if (typeNamespace == "http://tempuri.org") {
return Type.GetType(typeName); // assembly qualified already
}
return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);
}
}
数百年前,我遇到过类似的情况 - 它不是 MEF,而是我们手工制作的类似 MEF 的架构。 (MEF 在那些日子里的名声不好。)我们对数据合同序列化程序的创建进行了真正的集中管理,因此很容易插入代理提供程序。
它本身不是合同解析器 - 但最终以类似的方式工作,并在与解析器相同的时间和地点插入序列化管道。
我是凭记忆勾勒的,这么多年过去了,这很容易出错,但事情是这样的。我不记得的一个细节是 AssemblyAwareSurrogate 序列化的是字节数组还是字符串。我想可以走任何一条路。
public class AssembyAwareSurrogateProvider: IDataContractSurrogate
{
[DataContract]
class AssemblyAwareSurrogate
{
[DataMember]
public string AssemblyName { get; set; }
[DataMember]
public string TypeName { get; set; }
[DataMember]
public byte[ ] Object { get; set; }
public AssemblyAwareSurrogate( object obj )
{
this.AssemblyName = obj.GetType( ).Assembly.FullName;
this.TypeName = obj.GetType( ).FullName;
var serializer = new DataContractSerializer( obj.GetType( ) );
using ( var stream = new MemoryStream( ) )
{
serializer.WriteObject( stream, obj );
stream.Flush( );
Object = stream.ToArray( );
}
}
}
public Type GetDataContractType( Type type )
{
if ( SatisifesConstraints( type ) ) return typeof( AssemblyAwareSurrogate );
return type;
}
private bool SatisifesConstraints( Type type )
{
//--> er - whatever types you're insterested in...
return type != typeof( AssemblyAwareSurrogate );
}
public object GetDeserializedObject( object obj, Type targetType )
{
var surrogate = obj as AssemblyAwareSurrogate;
if ( surrogate != null )
{
var assy = Assembly.Load( new AssemblyName( surrogate.AssemblyName ) );
var serializer = new DataContractSerializer( assy.GetType( surrogate.TypeName ) );
using ( var stream = new MemoryStream( surrogate.Object ) )
{
return serializer.ReadObject( stream );
}
}
return obj;
}
public object GetObjectToSerialize( object obj, Type targetType )
{
if ( SatisifesConstraints( obj.GetType( ) ) )
{
return new AssemblyAwareSurrogate( obj );
}
return obj;
}
public object GetCustomDataToExport( Type clrType, Type dataContractType )
{
return null;
}
public object GetCustomDataToExport( MemberInfo memberInfo, Type dataContractType )
{
return null;
}
public void GetKnownCustomDataTypes( Collection<Type> customDataTypes )
{
throw new NotImplementedException( );
}
public Type GetReferencedTypeOnImport( string typeName, string typeNamespace, object customData )
{
throw new NotImplementedException( );
}
public CodeTypeDeclaration ProcessImportedType( CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit )
{
throw new NotImplementedException( );
}
}