Custom pick a Remoting channel based on Remote object Uri
One common question I see on Remoting forum is how to configure a proxy to use a particular channel from the set of registered channels for communicating when creating a proxy. For instance consider an Remoting application that exposes both a secure and non secure TcpChannel and expects clients for local box to use the unsecured channel and clients from other machine to use the secured one. Usually each channel has a priority based on which they are sorted in the app domain. When you register multiple channels with the same priority then they are added in the same order the user configured them. When a sink is to be prepared for a proxy request, Remoting will walk the registered list in descending order and find the first one that can serve the Uri scheme. So if you have multiple channels registered for the same transport medium you really don't have an option of choosing between them.
I thought about this and decided to write a small wrapper channel that will help me in achieving this. The solution is to register all your usual list of channels with this channel wrapper instead of registering them with ChannelServices. Then you register this wrapper channel with ChannelServices and so each time a Sink is to be prepared Remoting will call in to our channel wrapper and it will pick the channel based on user preference.
Lets take a look at this class which I call RemotingCompositeChannel.
public
classRemotingCompositeChannel : IChannelSender, ITrackingHandler
{
//Implements IChannelSender to provide IMessageSink when requested
//Implements ITrackingHandler so we can wire up when an CAO proxy is unmarshalled
public
RemotingCompositeChannel()
:
this(UriComparisonMode.Exact)
{
}
public RemotingCompositeChannel(UriComparisonMode mode)
{
}
CompositeChannel can be created in two Uri comparison modes, Exact and WildcardMatch and their function is to match a Uri exactly or do pattern matches. You first create an instance of the composite channel and register all of your regular Remoting client channels to this composite channel. Finally register the CompositeChannel with ChannelServices.
RemotingCompositeChannel compositeChannel = newRemotingCompositeChannel(UriComparisonMode.WildcardMatch);HttpClientChannel httpChannel = newHttpClientChannel("http", null);TcpClientChannel tcpChannel = newTcpClientChannel("tcp", null);IpcClientChannel ipcChannel = newIpcClientChannel("ipc", null);TcpClientChannel tcpSecuredChannel = newTcpClientChannel("tcpSecured", null);
tcpSecuredChannel.IsSecured =
true;
//Wildcard pattern match mode
compositeChannel.Add(
@"https://.*", httpChannel);
compositeChannel.Add(
"tcp://localhost:8889.*", tcpChannel);
compositeChannel.Add(
"tcp://localhost:8891.*", tcpSecuredChannel);
compositeChannel.Add(
"ipc://.*", ipcChannel);//Register that single CompositeChannelConsole.WriteLine("Registered a compositechannel that contains multiple channels");ChannelServices.RegisterChannel(compositeChannel, false);
RemotingCompositeChannel is set between IPCChannel Priority and CrossAppdomianChannel's priority so it sits on top of all other registered cross process/machine channels. Then in your client do your regular CAO/SAO activation stuff and you should see the right channel being picked.
Lets see a sample MarshalByRefObject definition
publicinterfaceIRemoteService
{
string SayHello(String s); string SayAuthenticatedHello(String s);
}
publicclassMyRemoteService : MarshalByRefObject, IRemoteService { }
public
classMyAuthenticatedRemoteService : MyRemoteService { }
public virtual string SayAuthenticatedHello(string s)
{
IPrincipal principal = Thread.CurrentPrincipal;
Console.WriteLine("Is User authenticated: " + principal.Identity.IsAuthenticated);
if (principal.Identity.IsAuthenticated)
{
Console.WriteLine("Authenticated user name: " + principal.Identity.Name);
Console.WriteLine("Authentication type: " + principal.Identity.AuthenticationType);
return "AuthenticatedSayHello: " + s;
}
else
{
return "NotAuthenticated";
}
}
SayAuthenticatedHello will print the authenticated username of the incoming connection if any.
Sample client usage:
//Register that single CompositeChannelConsole.WriteLine("Registered a compositechannel that contains multiple channels");ChannelServices.RegisterChannel(compositeChannel, false);Console.WriteLine("Point MyRemoteService activation to port 8889 which is unsecured.");RemotingConfiguration.RegisterActivatedClientType(typeof(MyRemoteService), "tcp://localhost:8889/MyServer");Console.WriteLine("Point MyAuthenticatedRemoteService activation to port 8891 which is secured.");RemotingConfiguration.RegisterActivatedClientType(typeof(MyAuthenticatedRemoteService), "tcp://localhost:8891/MyServer");
MyRemoteService server = newMyRemoteService();Console.WriteLine("Type of proxy:" + server);Console.WriteLine("Calling anonymous method");Console.WriteLine(server.SayHello("Hello"));try
{
Console.WriteLine("Calling authenticated method"); Console.WriteLine(server.SayAuthenticatedHello("Hello"));
}
catch (Exception e)
{
Console.WriteLine("Exception calling authenticated method." + e);
}
MyAuthenticatedRemoteService server1 = newMyAuthenticatedRemoteService();Console.WriteLine("Type of proxy:" + server1);Console.WriteLine("Calling anonymous method");Console.WriteLine(server1.SayHello("Hello"));try
{
Console.WriteLine("Calling authenticated method"); Console.WriteLine(server1.SayAuthenticatedHello("Hello"));
}
catch (Exception e)
{
Console.WriteLine("Exception calling authenticated method." + e);
}
So ideally we want server object use the unauthenticated channel and the server1 object use the authenticated channel.
Output from the server:
Server started. Press any key to terminate
AnonymousSayHello : Hello
Is User authenticated: False
AnonymousSayHello : Hello
Is User authenticated: True
Authenticated user name: [MyDomainRemovedForSecuritySake]\mahjayar ==>Success. Used the authenticated channel
Authentication type: NTLM
Output From Client:
Registered a compositechannel that contains multiple channels
Point MyRemoteService activation to port 8889 which is unsecured.
Point MyAuthenticatedRemoteService activation to port 8891 which is secured.
Unmarshalled object Server.MyRemoteService
IS transparent Proxy
Type of proxy:Server.MyRemoteService
Calling anonymous method
Hello
Calling authenticated method
NotAuthenticated
Unmarshalled object Server.MyAuthenticatedRemoteService
IS transparent Proxy
Type of proxy:Server.MyAuthenticatedRemoteService
Calling anonymous method
Hello
Calling authenticated method
AuthenticatedSayHello: Hello
As you can see the first client's call to AuthenticatedSayHello comes back as unauthorized and the second client (of type MyAuthenticatedRemoteService) uses the authorized channel.
PS: The usual disclaimer applies the RemotingCompositeChannel.cs class attached to the post. Its my own code and not tested by Microsoft or clarifiers for support from Microsoft. Use them in your code as you wish and if you find any bugs or have any suggestions for improvement then please post back. The code has enough comments to explain the reasoning for each method. This should work for both CAO and SAO objects. I will leave the SAO object verification up to the user.
Update: Added a link to the file from my Skydrive account.
https://cid-1020651b1389d347.skydrive.live.com/self.aspx/Public/RemotingCompositeChannel.cs
Comments
Anonymous
September 15, 2007
PingBack from http://msdnrss.thecoderblogs.com/2007/09/15/custom-pick-a-remoting-channel-based-on-remote-object-uri/Anonymous
September 18, 2007
This sounds like it's exactly what I've been looking for. Amazed that you were writing this article on the same day that I was struggling to solve this exact problem ! Excuse me if I am wrong but I think you may have missed out the implementation for the RemotingCompositeChannel.. Am I mistaken ?? -JasonAnonymous
September 18, 2007
Jason, The implementation is attached to the blog post as an attachment. I also added a link to the file at the end of the post.