|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionConsider writing a .NET remoting based server application that needs to serve every client session in a separate AppDomain. You could take a simple and straightforward approach by allocating multiple AppDomains and configuring each AppDomain for .NET remoting. Each AppDomain, however, must be configured so that the server's listening channels are assigned a different port. TCP will not permit more than one listener per port. On the client side, every application must know which endpoint (hostname:port) to address for a session to be established in a designated AppDomain. This concept of binding a session to a designated AppDomain and directing the client application via a specific endpoint there can certainly be realized, but it is neither practical nor conventional. A client typically requests to be served at a commonly known endpoint, irregardless in which AppDomain the server wishes to host the session. This article is about hosting client sessions in separate AppDomains but allowing all clients to connect to a commonly known endpoint. The idea is that the server receives a client request in the default AppDomain but then intercepts and forwards it on to a different AppDomain. How to intercept and marshal a client's request from one AppDomain to anotherA remote method invocation is all about Message objects passing through a chain of sinks from the client's end to the server's end. A method call is transformed into a serializable object, passed into a sink chain on the client side, serialized into a stream, and transmitted across the network. On the server's side, it is received, deserialized into the Message object, and passed up a sink chain to be finally reconstructed into a call to the service. We want to intercept the Message object before it reaches the point of reconstruction into the proper method call. Once intercepted, we can redirect the Message object to the appropriate AppDomain where the reconstruction to the final method call will take place. Fortunately, the .NET remoting infrastructure provides us with a facility to make these interceptions. The key here is the Of course, the clients can never directly connect to the actual service objects that are instantiated in separate AppDomains. The object they will connect to must be some sort of a cross domain marshaller that can intercept a method call and forward it to the appropriate AppDomain. A // this define the interceptor
[CrossDomainContextAttribute]
class CrossDomainMarshaller : ContextBoundObject
{
// code ommitted for brevity
}
// this installs the interceptor in the server's default AppDomain
CrossDomainMarshaller marshaller = new CrossDomainMarshaller();
RemotingServices.Marshal(marshaller, "PrintService.rem");
The string url = "tcp://myhost:1234/PrintService.rem"
IPrintService service =
(IPrintService)Activator.GetObject(typeof(IPrintService), url);
service.PrintMessage("Hello World");
The The important matter is that implementing a class MessageSink : IMessageSink
{
// other methods ommitted for brevity
// here is where we must intercept redirect the client's method call
public IMessage SyncProcessMessage(IMessage msg)
{
// if we just let this stand the msg object will just be
// dispatched to the CrossDomainMarshaller
// which does not have an implementation
// of the IPrintService
return this.nextSink.SyncProcessMessage(msg);
}
}
As I have earlier mentioned, a Message travels along a chain of message sinks until it arrives on the server's side to be converted into a method call. As the code snippet indicates, the method invocation is bound to the The problem is one of finding the correct AppDomain for the presently calling client. The string url = "tcp://myhost:1234/PrintService.rem"
// IPrintService service =
// (IPrintService)Activator.GetObject(typeof(IPrintService), url);
IPrintService service = (IPrintService)new CustomProxy(url,
typeof(IPrintService)).GetTransparentProxy();
service.PrintMessage("Hello World");
This is not an intolerable imposition. But the client must implement a class CustomProxy : RealProxy
{
string url;
string clientID;
IMessageSink messageSink;
public CustomProxy(string url, Type type) : base(type)
{
this.url = url;
// create a unique client identifier
this.clientID = Guid.NewGuid().ToString();
foreach (IChannel channel in ChannelServices.RegisteredChannels)
{
if (channel is IChannelSender)
{
IChannelSender sender = (IChannelSender)channel;
if (string.Compare(sender.ChannelName, "tcp") == 0)
{
string objectUri;
this.messageSink =
sender.CreateMessageSink(this.url,
null, out objectUri);
if (this.messageSink != null)
break;
}
}
}
if (this.messageSink == null)
throw new Exception("No channel found for " + this.url);
}
public override IMessage Invoke(IMessage msg)
{
msg.Properties["__Uri"] = this.url;
// pass the client's id as part of the call context
LogicalCallContext callContext =
(LogicalCallContext)msg.Properties["__CallContext"];
callContext.SetData("__ClientID", this.clientID);
return this.messageSink.SyncProcessMessage(msg);
}
}
The class MessageSink : IMessageSink
{
// other methods ommitted for brevity
// here is where we must intercept redirect the client's method call
public IMessage SyncProcessMessage(IMessage msg)
{
LogicalCallContext callContext =
(LogicalCallContext)msg.Properties["__CallContext"];
string clientID = (string)callContext.GetData("__ClientID");
if(clientID != null)
return CrossDomainMarshaller.GetService(clientID).Marshal(msg);
else
return new ReturnMessage(new ApplicationException("No __ClientID"),
(IMethodCallMessage)msg);
}
}
The line [CrossDomainContextAttribute]
class CrossDomainMarshaller : ContextBoundObject
{
public static Dictionary<string, ICrossDomainService>
Dictionary = new Dictionary<string, ICrossDomainService>();
public static ICrossDomainService GetService(string clientID)
{
// we created an AppDomain per client only once
if (Dictionary.ContainsKey(clientID))
return Dictionary[clientID];
AppDomain appDomain = AppDomain.CreateDomain(clientID);
ICrossDomainService service =
(ICrossDomainService)appDomain.CreateInstanceAndUnwrap(
"ContextBoundRemoting.Service",
"ContextBoundRemoting.PrintService");
Dictionary.Add(clientID, service);
return service;
}
}
You can readily see that we create an AppDomain per client only once, and not per every call. We, then, instantiate the public class PrintService :
CrossDomainService,
IPrintService
{
public string PrintMessage(string msg)
{
Console.WriteLine("{0} in AppDomain {1}", msg,
AppDomain.CurrentDomain.FriendlyName);
return "Ok " + msg;
}
}
You can see that the public class CrossDomainService :
MarshalByRefObject,
ICrossDomainService
{
class Proxy : RealProxy
{
string uri;
public Proxy(MarshalByRefObject obj)
{
this.uri = RemotingServices.Marshal(obj).URI;
}
public override IMessage Invoke(IMessage msg)
{
msg.Properties["__Uri"] = this.uri;
return ChannelServices.SyncDispatchMessage(msg);
}
}
Proxy proxy;
public CrossDomainService()
{
this.proxy = new Proxy(this);
}
public IMessage Marshal(IMessage msg)
{
return this.proxy.Invoke(msg);
}
}
The ConclusionThe
The list of possibilities is limited only by your imagination. I hope that this article meets your appreciation.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||