Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mef : "Cannot serialize" when trying to load an ApiController from another appdomain

I'm trying to find a way to update an Asp.net web api (.Net framework 4.5) at runtime (without recycling the main appdomain) by adding new ApiController (downloaded by another service).

I tried to use Mef and was able to load the new ApiController in the current appdomain, but I got stuck when trying to update an existing plugin (the assembly is already added to the appdomain, so I can't add the new one). So I decided to load the plugin containing the ApiController in a separate appdomain and use MarshalByRefObject to load it from the main appdomain but it turns out that ApiController cannot be serialized.

Do you know how I could serialize it? Do you know an alternative?

Edit:

I managed to load different versions of an assembly (in the same appdomain) if the assembly is signed, but it doesn't match my requirements.

like image 519
sanjar Avatar asked Sep 01 '25 05:09

sanjar


1 Answers

I haven't used MEF (because it is as easy to implement its functionality from scratch, in contradiction to MAF), but this way I have some experience with bare AppDomains.

It is hard to tell much without seeing your code, but from what you wrote, it seems to me that you are confusing some things.

As you probably know and you already pointed out too, you can't actually update an already loaded assembly. Loading another version of it (having a different signature) means that you have two different assemblies loaded. The types within them will have different strong names. You could actually handle this if you want. The only way to unload an assembly is to unload the appdomain that contains it.

My problem is with this sentence:

... load the plugin containing the ApiController in a separate appdomain and use MarshalByRefObject to load it from the main appdomain

Type (class) definition+code and instance data are two different things. Loading an assembly into an appdomain means you are loading type definition and code. Serialization comes into view when you want to transfer instance data across appdomain borders. You can't load type definition and code from an other appdomain as you wrote (actually you could but I doubt you need to). To be able to transfer instance data both sides need to have knowledge about the type definition of the instance being transferred. The serialization and the transferred in this case is managed by the .net remoting runtime. You have two choices: either move all the instance data and have it serialized all the time or you choose MarshalByObjRef way as you said you did. Let's stay with this. To be able to work with an instance in an other appdomain, you will need to instantiate the type in the other appdomain using the activator (you can't use the new operator in this case), and get a reference to it which will be a proxy based on the type you know (that can be an interface or a base class too, not only the exact type). Reflection is somewhat limited in such a situation, even less is prepared asp.net to figure out methods of a remote object - but you could help it with proper interfaces.

So, let's imagine you have created an instance of the controller in the other appdomain, and you have a remoting reference on it assignable to an interface type that defines all methods you need to expose to asp.net. Now serialization will come into view when are trying to access the members of the controller class. Each method parameter and method return type needs to be serializable. But not the class itself, as it is a MashalByObjRefdescendant and will not be mashalled as an instance. And MashalByObjRef has nothing to do with how you are loading the assembly into the appdomain.

But wait! Both MarshalByObjRef and ApiController are abstract classes. How do you want to derive your actual controller class from both? You can't. Thus I don't think you can directly use apicontrollers from an other appdomain directly.

I could imagine two things:

1) Stay with loading the new signed version into the same assembly and make customize the routing mechanism to direct requests to the latest version (might not be still valid, but could be still a good starting point: https://www.strathweb.com/2013/08/customizing-controller-discovery-in-asp-net-web-api/). Of course, on restart, you should load only the latest one if you don't need to have multiple versions in parallel.

2) Make a slightly complex infrastructure:

  • define an interface for the controller logic
  • create the apicontroller versionless and logicless, but capable of creating and unloading appdomains, loading assemblies into them, keep reference to the instances implementing the interface from above created in them, and directing the requests to those
  • be aware that you won't be able to pass some things (like controller context) to the logic in the other appdomain, you will have to extract what you need or recreate on the other side
  • this way you can have the logic MarshalByObjRef descendant in the "remote" appdomain and your controller ApiController descendant in the main appdomain.
  • I would create an interim abstract class extending ApiController with the capability of handling the above separation on its own. The rest of the application wouldn't be aware of this.
  • be aware of the lifetime services involved in remoting, which you can handle either by using a sponsor or overriding some methods of MarshalByObjRef.

Neither is simple approach, you will be facing some further challenges...

like image 128
ZorgoZ Avatar answered Sep 02 '25 19:09

ZorgoZ