1

Motivation. I have a client-server application. At some point the server side creates a new type dynamically based on certain metadata, unavailable to the client. The server needs to send an instance of the type to the client. However, the client will fail to deserialize the instance, because its type is unknown.

One solution is to bundle together both the metadata and the data, transmit to the client and let it recreate the dynamic type and the instance.

Things get messy when the particular instance is deeply nested within an object graph. What I would like to do is send the object graph as is to the client, let the deserialization code fire the AppDomain.AssemblyResolved event and recreate the respective dynamic type there. Alas! I cannot do it, because I do not know how to make the metadata available to the event handler.

I tried using CallContext, but it did not work.

Here is an entire sample code I used to look for the solution, which I did not succeed at:

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Remoting.Messaging;
using System.Security;
using System.Security.Permissions;

namespace DynamicTypes
{
  [Serializable]
  public class LogicalCallContextData : ILogicalThreadAffinative
  {
    public string DynamicAssemblyName { get; private set; }
    public string DynamicTypeName { get; private set; }

    public LogicalCallContextData(string dynamicAssemblyName, string dynamicTypeName)
    {
      DynamicAssemblyName = dynamicAssemblyName;
      DynamicTypeName = dynamicTypeName;
    }
  }

  class Program
  {
    private static string DynamicAssemblyName;
    private static string DynamicTypeName;
    private static Type m_type;

    static void CreateDynamicType()
    {
      if (m_type == null)
      {
        var assemblyName = new AssemblyName(DynamicAssemblyName);
        var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);
        var typeBuilder = moduleBuilder.DefineType(DynamicTypeName, TypeAttributes.Public | TypeAttributes.Serializable, typeof(object));
        var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
        var ilGenerator = constructorBuilder.GetILGenerator();
        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.Emit(OpCodes.Call, typeof(object).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null));
        ilGenerator.Emit(OpCodes.Ret);
        m_type = typeBuilder.CreateType();
      }
    }

    static void AppDomainInitialize(string[] args)
    {
      AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
    }

    static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
    {
      var data = (LogicalCallContextData)CallContext.GetData("test data");
      if (data != null)
      {
        DynamicAssemblyName = data.DynamicAssemblyName;
        DynamicTypeName = data.DynamicTypeName;

        CreateDynamicType();
        if (m_type.Assembly.FullName == args.Name)
        {
          return m_type.Assembly;
        }
      }
      return null;
    }

    [Serializable]
    private class CrossAppDomain
    {
      private object m_obj;
      public CrossAppDomain()
      {
        CreateDynamicType();
        m_obj = Activator.CreateInstance(m_type);
      }

      public void DoIt()
      {
      }
    }

    [PermissionSet(SecurityAction.LinkDemand)]
    static void Main(string[] args)
    {
      DynamicAssemblyName = Guid.NewGuid().ToString("N");
      DynamicTypeName = Guid.NewGuid().ToString("N");

      var data = new LogicalCallContextData(DynamicAssemblyName, DynamicTypeName);
      CallContext.SetData("test data", data);

      AppDomainInitialize(null);
      var appDomainSetup = new AppDomainSetup();
      appDomainSetup.AppDomainInitializer = AppDomainInitialize;
      var appDomain = AppDomain.CreateDomain("second", null, appDomainSetup);
      appDomain.DoCallBack(new CrossAppDomain().DoIt);
    }
  }
}

The data returned in the OnAssemblyResolve event handler is null.

Does anyone know how to do it?

EDIT: It is possible to do it in two round trips - pass the metadata in the first, pass the object itself in the second. I would like to find a one round trip solution.

EDIT: 2 I have come up with an absolutely crazy solution. It works, but I am wondering about the performance implications. What if I create exactly one dynamic assembly per dynamic type and encode the type's metadata in the name of that assembly? I checked this approach and it seems to be working. I got up to 500 character long assembly names. Each assembly define the single module "DynamicModule" and the single type - "DynamicType". Still I am looking forward a better solution.

mark
  • 59,016
  • 79
  • 296
  • 580

1 Answers1

0

You can register a non-static method as AppDomain.AssemblyResolve event handler. Then you have access to the members of the instance, which method was registered. It's quite like the AssemblyResolver class I present here:

Need to hookup AssemblyResolve event when DisallowApplicationBaseProbing = true

On deserialization you can store the metadata at the AssemblyResolver instance before the AssemblyResolve event gets fired. The interesting point is "when" do you store the metadata to the AssemblyResolver. Insisting on a single deserialization run requires you to implement the deserialization of metadata in the object that holds the object of dynamic type. Maybe you can put the dynamic object in a sort of wrapper for convenience. Have the wrapper bring along the metadata and the dynamic-typed object (the latter serialized either to string or to byte[], depending on your serialization). Customize the wrapper's deserialization process to first push the metadata to the AssemblyResolver. Then deserialize the dynamic-typed object from string or byte[] member of the wrapper.

Probably the simplest solution for accessing the AssemblyResolver is the singleton pattern, although many vote for dependency injection instead.

In fact you will have recursive deserialization runs "locally" for parts of your object structure. However, I don't see any implications on high level object structure deserialization. Be aware that this solution needs some extra work to get thread-safe, because you'd need to block the AssemblyResolver before pushing the metadata. Problems will arise, if there is a dynamic-typed object that holds another dynamic-typed object, because then you need to release the AssemblyResolver at the end of AssemblyResolve event handling.

Community
  • 1
  • 1
No answer
  • 907
  • 1
  • 9
  • 10