Dear ladies and sirs.
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.