I have a server\client application which needs to request two different remote proxies via an Activator.
I ran into a situation where if you request a remote object through Activator.GetObject
without creating a channel and then later on create a channel and try to get a seperate object, then the second object will fail. Let me show a sample application to demonstrate the issue:
Here are the two remote objects:
namespace HelloRemoteObject
{
public class FirstObject : MarshalByRefObject
{
public String FirstObjectMethod()
{
return "FirstObjectMethod";
}
}
public class SecondaryObject : MarshalByRefObject
{
public String SecondObjectMethod()
{
return "Secondary object method return string";
}
}
}
Here is the server, which just registers an http server channel on a specific port and then waits:
public static int Main(string[] args)
{
// register the channel
IDictionary properties = new Hashtable
{
{"timeout", 1000},
{"port", 9099}
};
var serverChannel = new HttpServerChannel(properties, new BinaryServerFormatterSinkProvider());
ChannelServices.RegisterChannel(serverChannel, false);
// register two known types
RemotingConfiguration.RegisterWellKnownServiceType(typeof(FirstObject), "FirstObjectUri", WellKnownObjectMode.Singleton);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(SecondaryObject), "SecondaryObjectUri", WellKnownObjectMode.Singleton);
Console.WriteLine("Hit <enter> to exit...");
Console.ReadLine();
return 0;
}
And here is the client. If you pass in any argument then it will register a named channel first. Then it will get the two remote proxies and call a single method on them. If you don't give it any arguments it will first get the proxy to the first object, then create a channel for the second proxy, and then try and act on the two.
static void Main(string[] args)
{
var registerChannelFirst = args.Length > 0;
if(registerChannelFirst)
{
Console.WriteLine("Registering channel first");
RegisterHttpChannel();
}
else
{
Console.WriteLine("Letting Activator.GetObject register an http client channel by default");
}
var firstRemoteObject = (FirstObject)Activator.GetObject(typeof(FirstObject), "http://127.0.0.1:9099/FirstObjectUri");
if(!registerChannelFirst)
{
RegisterHttpChannel();
}
var secondRemoteObject = (SecondaryObject)Activator.GetObject(typeof(SecondaryObject), "http://127.0.0.1:9099/SecondaryObjectUri", );
Console.WriteLine(firstRemoteObject.FirstObjectMethod());
Console.WriteLine(secondRemoteObject.SecondObjectMethod());
}
private static void RegisterHttpChannel()
{
// register the channel
IDictionary properties = new Hashtable
{
{"name" , "ChannelName"}
};
var clientChannel = new HttpClientChannel(properties, new BinaryClientFormatterSinkProvider());
ChannelServices.RegisterChannel(clientChannel, false);
}
If you register a channel first, before activating then everything works fine.
Letting the activator go first and then getting a second object and you get an exception.
If you don't try and activate two objects and just let the activator get an object then that also works. I'm curious to know why this is?
I'm curious because lets say I don't care about channel properties then I can just as easily create a single channel and be done with it, however, if one object needs to utilize a timeout on the channel, but the other object does not, then I need to create two separate channels. Is there then a way to bind an object to a channel?
I'm also aware that remoting has gone the way of wcf, but thats not an option for me right now.
So I think the solution here is just to always register a channel name. When I register a channel name the output looks like this (ChannelName
is the output of ChannelServices.RegisteredChannels
's names)
Registering channel first
ChannelName
FirstObjectMethod
Secondary object method return string
If I register the channel after the activator has run, it looks to have created its own internal default channel
Letting Activator.GetObject register an http client channel by default
http client, ChannelName
Unhandled Exception: System.Runtime.Remoting.RemotingException: System.ArgumentNullException: No message was deserialized prior to calling the DispatchChannelSink.
Parameter name: requestMsg
at System.Runtime.Remoting.Channels.DispatchChannelSink.ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, IMessage& responseMsg, ITransportHeaders& responseHeaders, Stream& responseStream)
at System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, IMessage& responseMsg, ITransportHeaders& responseHeaders, Stream& responseStream)
at System.Runtime.Remoting.Channels.Http.HttpServerTransportSink.ServiceRequest(Object state)
at System.Runtime.Remoting.Channels.SocketHandler.ProcessRequestNow()
Server stack trace:
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at HelloRemoteObject.FirstObject.FirstObjectMethod()
at HelloClient.HelloClient.Main(String[] args) in
So something is getting confused with having two registered http channels. I would've stepped through the .net source but I was having issues.