【问题标题】:Using custom DataContractResolver with multiple assemblies将自定义 DataContractResolver 与多个程序集一起使用
【发布时间】:2015-09-18 09:28:04
【问题描述】:

我在 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;
    }
}

所以我的问题是:在序列化和反序列化 DataContracts 时存储和获取程序集名称的好方法是什么?

【问题讨论】:

  • @EhsanSajjad,这不起作用。我不想获取正在执行的程序集,我想确定序列化对象的类型所在的程序集。
  • 如果你有Type对象,你可以使用FullyQualifiedName属性:msdn.microsoft.com/en-us/library/…
  • @EhsanSajjad,不,我没有Type 对象。我只有一个简短的类型名称及其名称空间作为字符串。请仔细阅读问题。
  • @dbc,实际上我可以随意更改 XML,因为它只会由我的反序列化器处理。当然,XML 应该是有效的。目前我倾向于将程序集信息嵌入到 XML 中的解决方案。

标签: c# xml xml-serialization .net-assembly datacontract


【解决方案1】:

为什么在序列化时不使用 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);
    }
}

【讨论】:

  • 这是一个好点,我正在尝试那个。唯一的问题是生成的 XML 看起来很“奇怪”,因为属性具有不同的值,具体取决于默认类型解析器是否可以解析该类型。
  • 那么谁会去看呢? :) 它适用于机器,而不是人类(在大多数情况下)。我自己不使用 xml 序列化,除非我必须(尤其是使用 DataContractSerializer),但是当我有选择时更喜欢 Protobuf(紧凑、快速)或 json(可读)之类的东西 - 有时你不会。
【解决方案2】:

您将需要遍历执行程序集的所有引用程序集(无论是否已加载)并查找可从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 
        // https://stackoverflow.com/questions/851248/c-sharp-reflection-get-all-active-assemblies-in-a-solution
        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:
        // https://stackoverflow.com/questions/1316518/how-did-microsoft-create-assemblies-that-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&lt;Type&gt;) 构造函数来代替合约解析器。

老实说,性能不是很好,因为代码会加载根程序集引用的所有程序集,包括 Microsoft DLL 和第 3 方 DLL。您可能想开发一些方法来减少要加载的程序集的数量,方法是在加载之前检查 name,例如,如果基类来自您自己的 codebase,则跳过 Microsoft 程序集。

【讨论】:

  • 感谢您的回答。我已经尝试过了,但是反序列化性能很差(因为程序集“扫描”)。我赞成你的回答,因为它确实提供了一个可以接受的解决方案,但我不会接受它,但希望有一个更快的解决方案。
  • 您可以尝试将引用的 dll 限制为与您的 EntryAssembly 或 ExecutingAssembly 位于同一位置的那些引用的 dll。
【解决方案3】:

数百年前,我也遇到过类似的情况 - 不是 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( );
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    • 1970-01-01
    • 2011-12-13
    • 1970-01-01
    • 2020-12-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多