We have a plugin system where the plugin code runs on a separate AppDomain from the main process, using .NET remoting for the objects to communicate.
One class is similar to HttpContext.Current (which also suffers from the problem) (edit, the actual implementation):
public class MyClass
{
public static MyClass Instance
{
get
{
if(HttpContext.Current != null)
return HttpContext.Current.Items["MyClassInstance"];
}
set
{
if(HttpContext.Current != null)
HttpContext.Current.Items["MyClassInstance"] = value;
}
}
}
Then, we have a communicating object which inherits from MarshalByRefObject:
public class CommunicatingClass : MarshalByRefObject, ICommunicatingClass
{
public void DoSomething()
{
MyClass.Instance.DoSomething();
}
}
The CommunicatingClass is created on the main AppDomain, and works fine. Then, there's the plugin class, which is created on its AppDomain, and given an instance of the CommunicatingClass:
public class PluginClass
{
public void DoSomething(ICommunicatingClass communicatingClass)
{
communicatingClass.DoSomething();
}
}
The problem is, that even though CommunicatingClass resides on the main appdomain (verified with the Immediate Window), all of the static data such as MyClass.Instance and HttpContext.Current have disappeared and are null. I have a feeling that MyClass.Instance is somehow being retrieved from the plugin AppDomain, but am unsure how to resolve this.
I saw another question that suggested RemotingServices.Marshal
, but that did not seem to help, or I used it incorrectly. Is there a way that the CommunicatingClass can access all static methods and properties like any other class in the main AppDomain?
Edit:
The PluginClass is given an instance like this:
public static PluginClass Create()
{
var appDomain = GetNewAppDomain();
var instance = (PluginClass)appDomain.CreateInstanceAndUnwrap(assembly, type);
instance.Communicator = new CommunicatingClass();
return instance;
}
Edit 2:
Might have found the source of the problem. MyClass.Instance is stored in HttpContext.Current.Items (see above edit).
Is there any way at all that HttpContext.Current can access the correct HttpContext? I'm still wondering why, even though it is being run in HttpContext.Current's AppDomain, CommunicatingClass.DoSomething, when calling MyClass.Instance, retrieves stuff from PluginClass' AppDomain (if that makes any sense).
So my co-worker and I worked this out, finally, with a bunch of help from Reflector.
The main problem is that HttpContext.Current is null when accessed via a remoting call.
The HttpContext.Current property is stored in an interesting manor. A few nested setters down, you reach CallContext.HostContext
. This is a static object property on a CallContext
.
When the CallContext is set, it first checks if the value is an ILogicalThreadAffinitive
.
LogicalCallContext
.IllogicalCallContext
.HttpContext
is not an ILogicalThreadAffinitive
, so it is stored in the IllogicalCallContext
.
Then, there's remoting.
We didn't dig too far in to its source, but what it does was inferred from some other functions.
When a call is made to a remote object from a different AppDomain, the call is not directly proxied to the original thread, running in the exact same execution context.
First, the ExecutionContext
of the original thread (the one containing HttpContext.Current
) is captured, via ExecutionContext.Capture
(more in this in a bit).
Then, the ExecutionContext
returned from Capture
is passed as the first argument to ExecutionContext.Run
, esentially forming the code:
Delegate myRemoteCall; //Assigned somewhere else in remoting
ExecutionContext.Run(ExecutionContext.Capture(), x => { myRemoteCall() }, null);
Then, completely transparently, your code in the remote object is accessed.
Unfortunately, HttpContext.Current
is not captured in ExecutionContext.Capture()
.
Here lies the essential difference between an IllogicalCallContext
and a LogicalCallContext
.
Capture
creates a brand-new ExecutionContext
, essentially copying all of the members (such as the LogicalCallContext
) in to the new object. But, it does not copy the IllogicalCallContext
.
So, since HttpContext
is not an ILogicalThreadAffinative
, it cannot be captured by ExecutionContext.Capture
.
The solution?
HttpContext is not a MarshalByRefObject or [Serializable] (probably for good reason), so it cannot be passed in to the new AppDomain.
But, it can cross ExecutionContext
s without problem.
So, in the main AppDomain's MarshalByRefObject which is given as a proxy to the other AppDomain, in the constructor give it the instance of HttpContext.Current
.
Then, in each method call of the new object (unfortunately), run:
private HttpContext _context;
private void SyncContext()
{
if(HttpContext.Current == null)
HttpContext.Current = _context;
}
And it will be set without problem. Since HttpContext.Current is tied to the IllogicalCallContext
of the ExecutionContext
, it will not bleed in to any other threads that ASP.NET might create, and will be cleaned up when the copy ExecutionContext
is disposed.
(I could be wrong about much of this, though. It's all speculation and reflection)
I believe you'll need to derive MyClass from MarshalByRefObject as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With