<!--------------------------------------------------------------------------->
<!-- INTRODUCTION
The Code Project article submission template (HTML version)
Using this template will help us post your article sooner. To use, just
follow the 3 easy steps below:
1. Fill in the article description details
2. Add links to your images and downloads
3. Include the main article text
That's all there is to it! All formatting will be done by our submission
scripts and style sheets.
-->
<!--------------------------------------------------------------------------->
<!-- IGNORE THIS SECTION -->
<html>
<head>
<title>The Code Project</title>
<Style>
BODY, P, TD { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt }
H2,H3,H4,H5 { color: #ff9900; font-weight: bold; }
H2 { font-size: 13pt; }
H3 { font-size: 12pt; }
H4 { font-size: 10pt; color: black; }
PRE { BACKGROUND-COLOR: #FBEDBB; FONT-FAMILY: "Courier New", Courier, mono; WHITE-SPACE: pre; }
CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; }
</style>
<link rel="stylesheet" type=text/css href="http://www.codeproject.com/styles/global.css">
</head>
<body bgcolor="#FFFFFF" color=#000000>
<!--------------------------------------------------------------------------->
<!------------------------------- STEP 1 --------------------------->
<!-- Fill in the details (CodeProject will reformat this section for you) -->
<pre>Title: .NET Remoting and cross domain marshalling
Author: Wytek Szymanski
Email: wytek@szymanski.com
Environment: .NET, Visual Studio 2005
Keywords: .NET remoting, AppDomain, ContextBound objects
Level: advanced"
Description: An article abou how to marshal a remote client request from one AppDomain to another.
</pre>
<!------------------------------- STEP 2 --------------------------->
<!-- Include download and sample image information. -->
<ul class=download>
<li><a href="Article_src.zip">Download source - XXX Kb</a></li>
</ul>
<!------------------------------- STEP 3 --------------------------->
<!-- Add the article text. Please use simple formatting (<h2>, <p> etc) -->
<h2>Introduction</h2>
<p>
Consider writing a .NET remoting based server application that needs to serve every client session in a separate AppDomain. You could take a simple
and straight forward 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.
</p>
<p>
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.</p>
<p>
This article is about hosting client sessions in separate AppDomains but allowing
all clients 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.
</p>
<h2>How to intercept and marshal a client's request from one AppDomain to another</h2>
<p>
A 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 accross the network. On the server's side it is received, deserialized into the a 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.
</p>
<p>
Fortunately, the .NET remoting infrastructure provides us with a facility to make these interception. The key here is the <code>ContextBoundObject</code>. Instead of deriving
a remote service object from <code>MarshalByRefObject</code> you need to derive
from ContextBoundObject.
</p>
<p>
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 <code>CrossDomainMarshaller</code> that is a ContextBoundObject must be installed as an
interceptor.
</p>
<pre>// 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");
</pre>
<p>
The CrossDomainMarshaller assumes the object uri <code>"PrintService.rem"</code>
of the actual service that the clients are interested in. The connection code on
the the client side would appear normal, like so.
</p>
<pre>string url = "tcp://myhost:1234/PrintService.rem"
IPrintService service = (IPrintService)Activator.GetObject(typeof(IPrintService), url);
service.PrintMessage("Hello World");
</pre>
<p>
The CrossDomainMarshaller does not implement the <code>IPrintService</code> interface at all. So, how can this possibly work?
The answer is with the ContextBoundObject. I will not explain how to implement a
ContextBoundObjet but refer you instead to MSDN or an excellent book about .NET Remoting, such as "Advanced .NET Remoting", written by Ingo Rammer et all,
and published by Apress. This article also includes source code that you may download and inspect.
</p>
<p>
The important matter is that implementing a CrossDomainMarshaller as a ContextBoundObject requires us also to implement
a <code>MessageSink</code> object. That offers us
the opportunity to intercept the Message before it enters the point of reconstruction
to the ultimate method call. </p>
<pre>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);
}
}
</pre>
<p>
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 IPrintService interface and the
CrossDomainMarshaller does not implement
one. So, passing it off to the next sink will result in failure. We need to redirect the call to the appropriate
service object in the appropriate AppDomain.
</p>
<p>
The problem is one of finding the correct AppDomain for the presently calling client.
The CrossDomainMarshaller intercepts all messages
send by all clients but we have
no means to identify each client. The
Message object contains no particular information
as to the client's identity. To improve upon this we o need to engage the client
into cooperation. The client agrees to connect via a <code>CustomProxy</code>. Here is how the client code would look like.
</p>
<pre>string url = "tcp://myhost:1234/PrintService.rem"
// IPrintService service = (IPrintService)Activator.GetObject(typeof(IPrintService), url);
IPrintService service = (IPrintService)<b>new CustomProxy(url, typeof(IPrintService)).GetTransparentProxy()</b>;
service.PrintMessage("Hello World");
</pre>
<p>
This is not an intolerable imposition. But the client must implement a <code>CutomProxy</code> that must look like so.
</p>
<pre>class CustomProxy : RealProxy
{
string url;
<b>string clientID;</b>
IMessageSink messageSink;
public CustomProxy(string url, Type type) : base(type)
{
this.url = url;
// create a unique client identifier
<b>this.clientID = Guid.NewGuid().ToString();</b>
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
<b>LogicalCallContext callContext = (LogicalCallContext)msg.Properties["__CallContext"];
callContext.SetData("__ClientID", this.clientID);</b>
return this.messageSink.SyncProcessMessage(msg);
}
}
</pre>
<p>
The CustomProxy allows to create, and then transmit a unique client identifier with every method call. The
Message object allows to pass along additional out of band data as part of its <code>LogicalCallContext</code>,
providing the means to identify each client on the server's side.
</p>
<pre>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)
<b>return CrossDomainMarshaller.GetService(clientID).Marshal(msg);</b>
else
return new ReturnMessage(new ApplicationException("No __ClientID"), (IMethodCallMessage)msg);
}
}
</pre>
<p>
The line
<code>return CrossDomainMarshaller.GetService(clientID).Marshal(msg);</code> is a short way of saying get the
correct service based on the client identifier and invoke its <code>Marshal</code> method.
Let us look at the implementation of <code>CrossDomainMarshaller.GetService</code>.
</p>
<pre>[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;
}
}
</pre>
<p>
You can readily see that we create an AppDomain per client only once, and not per every call.
We, than instantiate the PrintService in that AppDomain and than store it so it can be retrieved for any subsequent call.
What we return, however, is not
some interface to a PrintService but an
<code>ICrossAppDomainService</code> interface which allows us to
marshal the <code>Message</code> acrros the AppDomain. So, that <code>PrintService</code> also needs
to implement the <code>ICrossDomainService</code> interface. The remaining and interesting question is how passing the Message object to the PrintService can possibly result
in a proper method call on the PrintService. The short answer lies with another
CustomProxy. Take a quick look at the complete implementation
of the PrintService class.
</p>
<pre>public class PrintService :
<b>CrossDomainService,</b>
IPrintService
{
public string PrintMessage(string msg)
{
Console.WriteLine("{0} in AppDomain {1}", msg, AppDomain.CurrentDomain.FriendlyName);
return "Ok " + msg;
}
}
</pre>
<p>
You can see that the <code>PrintService</code> class extends the <code>CrossDomainService</code> class.
The secrete of converting the Message to a proper call on the PrintService
is hidden there. Examine the CrossDomainService.</p>
<pre>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()
{
<b>this.proxy = new Proxy(this);</b>
}
<b>
public IMessage Marshal(IMessage msg)
{
return this.proxy.Invoke(msg);
}</b>
}
</pre>
<p>
The CrossDomainService implements the ICrossDomainService interface method
<code>Marshal(IMessage msg)</code>. The Message object is forwarded into a custom proxy which
finally resolves the call to <code>IPrintService.PrintMessage</code>. The implementation of the custom proxy is not that
complicated.</p>
<h2>Conclusion</h2>
<p>
The ContextBoundObject is rather powerful means to intercept method calls. If you are interested in application
server technologies and ever wanted to build an application server yourself than you are in luck. Application servers are all about intercepting
and interfering with method calls.</p>
<p>
</p>
<li>
You can provide your own authentication and authorisation scheme.<br />
</li>
<li>
You can rewrite the Message object to redirect the call into an alternate object/method.
This is one useful technique if you ever
re-deploy your server but cannot update your clients with the new
interface definitions that are normally kept in a shared assembly. <br />
</li>
<li>
You can selectively apply encryption/decryption to the granularity of a single method parameter.<br />
</li>
<li>
You can monitor anything you consider worthwile by counting and timing a method call.<br />
</li>
<p>
The list of possibilities is limited only by your imagination. I hope that this article meets your appreciation.</p>
</body>
</html>