Click here to Skip to main content
15,861,168 members
Articles / Programming Languages / C#
Article

Using Web Services for Remoting over the Internet.

Rate me:
Please Sign up or sign in to vote.
4.76/5 (38 votes)
15 Feb 2002CPOL8 min read 370.7K   4.2K   222   36
This article describes a design and implementation (C#) of the Remoting over Internet using the Web Service as a gateway into the Remoting infrastructure.

Contents

Introduction
Usage
Concept and Design
Implementation
Test
Conclusion

Introduction

This article describes a design and implementation (C#) of the Remoting over Internet using the Web Service as a gateway into the Remoting infrastructure. The Web Service Gateway (Custom Remoting Channel) allows to enhance the remoting channel over Internet and its chaining with another heterogeneous channel. Consuming a remote object over Internet is full transparently and it doesn't require any special implementation from the remoting via intranet. The Web Service Gateway enables to create a logical model of the connectivity between the different platforms and languages. Before than we will go to its implementation details, let's start it with usage and configuration issue. For some demonstration purpose I will use a MSMQ Custom Remoting Channel (MSMQChannelLib.dll), which I described in my previously article [1]. I am assuming that you have a knowledge of the .Net Remoting and Web Service.

Usage

Consuming a remote object over Internet using the Web Service Gateway is very straightforward and it actually requires only to install the following assemblies:

  • WebServiceChannelLib , this is a Custom Remoting Channel on the client side to forward a remoting message to the Web Service Gateway over Internet (outgoing message).
  • WebServiceListener, this is a Web Service (gateway) to listen an incoming message from the client side and forward it to the local remoting infrastructure (incoming message).

Note that the above assemblies have to be installed (into the GAC) both on the server and client sides when a remote callback is used.

The next step is to configure a server and client host sides. Their configuration are depended from the actually application. Let me assume, we want to call a remote object driven by MSMQ custom channel over internet. Their config files might look like the following snippets:

server.exe.config

XML
 <configuration>
 <system.runtime.remoting>
  <application >
   <service>
    <wellknown mode="Singleton" type="MyRemoteObject.RemoteObject, MyRemoteObject" 
               objectUri="endpoint" />
   </service>
   <channels>
   <channel type="RKiss.MSMQChannelLib.MSMQReceiver, MSMQChannelLib" 
            listener=".\ReqChannel"/>
   <channel type="System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting" 
            port="8090" />
   </channels>
  </application>
 </system.runtime.remoting>
</configuration>

The above server config file will register two channels to listen an incoming message for the remote well known singleton object.

client.exe.config

This is an example of the client config file to register our Custom Remoting Channel.

XML
<configuration>
 <system.runtime.remoting>
  <application>
   <client >
    <wellknown type="MyRemoteObject.RemoteObject, RemoteObject" 
               url="ws://localhost/WebServiceListener/Listener.asmx; 
               tcp://localhost:8090/endpoint/RemoteObject" />
   </client>
   <channels>
    <channel type="RKiss.WebServiceChannelLib.Sender, WebServiceChannelLib" mode="soap"/>
   </channels>
  </application>
 </system.runtime.remoting>
</configuration>

The ws is a Custom Remoting Client channel to dispatch an IMessage over internet using a binary respectively soap mode formatter. Note that the mode is a CustomChannelProperty and its default value is binary.

web.config

This is a Web Service config file. The following snippet is its part. The Web Service gateway is also a local remoting client, therefore a client (sender) channel is requested to be registered. The following snippet shows a configuration of the two channels - Tcp and MSMQ.

XML
<system.runtime.remoting>
 <application >
  <channels>
   <channel type="System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting"/>
   <channel type="RKiss.MSMQChannelLib.MSMQSender, MSMQChannelLib"                  
            respond=".\RspChannel" admin=".\AdminChannel" timeout="30" priority="10"/>
  </channels>
 </application>
</system.runtime.remoting>

Activating a remote object

The well known remote object (WKO) is activated by its consumer using the GetObject method mechanism. The proxy is created based on the remote object metadata assembly installed in the GAC (see an argument objectType). The remoting channel is selected by the objectUrl argument. The url address in this solution has two parts :

  • connectivity to the Web Service gateway over internet
  • connectivity to the Remote object over intranet within the Web Service gateway

Between the primary and secondary addresses is a semicolon delimiter as it is shown the below:

C#
string objectUrl = @"ws://localhost/WebServiceListener/Listener.asmx; msmq://./reqchannel/endpoint"; 

Using this objectUrl design pattern allows an easy selection of the web service gateways on the Internet. Note that the ws custom remoting channel will trim this primary address and forward only its secondary part. In this solution, the objectUrl represents a physical path of the logical connectivity between the consumer and remote object regardless of how many channels are needed. In this example, the Web Service gateway resides on the localhost and it should be replaced by the real machine name.

Finally, the following code snippet shows an activation of the remote object:

// activate a remote object

Type objectType = typeof(MyRemoteObject.RemoteObject);
string objectUrl = @"ws://localhost/WebServiceListener/Listener.asmx; msmq://./reqchannel/endpoint";
RemoteObject ro = (RemoteObject)Activator.GetObject(objectType, objectUrl);

Note that a metadata (assembly) of the remote object must be installed into the GAC in the places such as client, Web Service gateway and server host.

That's all for the client/remoteObject plumbing issue over the Internet.

The following pictures shows this connectivity:

Image 1

Image 2

Now, to understand how the message flows between the heterogeneous channels over the Internet, have a look at the following paragraphs:

Concept and Design

Concept of the Remoting over Internet is based on dispatching a remoting message over Internet using the Web Service features as a transport layer. The following picture shows this solution:

Image 3

Client activates a remote WKO to obtain its transparent proxy. During this process the custom remoting channel (ws) is initiated and inserted into the client channel sink chain. Invoking a method on this proxy, the IMessage is created which it represents a runtime image of the method call at the client side. This IMessage is passed into the channel sink. In our solution to the custom client (sender) channel ws. This channel sink has a responsibility to convert IMessage to the SoapMessage in the text/xml format pattern and send it over Internet to the Web Service gateway. The following picture shows this:

Image 4

The Web Service gateway has two simply WebMethods, one for the SoapMessage format and the other one for a binary format encoded by base64 into the text string. The first one method enables to use a call from an unknown client, as opposite in the binary formatting message for .Net client.

Lets continue with the IMessage/SoapMessage flows on the Web Service gateway side as it is shown on the above picture. The text/xml formatted SoapMessage sent over Internet might look like the following snippet:

Text/XML formatted SoapMessage Request.

XML
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
xmlns:a3="http://schemas.microsoft.com/clr/nsassem/MyRemoteObject/MyRemoteObject, 
Version=1.0.772.24659, Culture=neutral, PublicKeyToken=ec0dd5142ae7a19b" 
xmlns:a1="http://schemas.microsoft.com/clr/ns/System.Runtime.Remoting.Messaging">
<SOAP-ENV:Body>
<System.Runtime.Remoting.Messaging.MethodCall id="ref-1">
<__Uri id="ref-2" xsi:type="SOAP-ENC:string">msmq://./reqchannel/endpoint</__Uri>

<__MethodName id="ref-3" xsi:type="SOAP-ENC:string">get_Id</__MethodName>

<__TypeName id="ref-4" xsi:type="SOAP-ENC:string">MyRemoteObject.RemoteObject, 
MyRemoteObject, Version=1.0.772.24659, Culture=neutral, 
PublicKeyToken=ec0dd5142ae7a19b</__TypeName>

<__Args href="#ref-5"/> 
<__CallContext href="#ref-6"/> 
</System.Runtime.Remoting.Messaging.MethodCall>
<SOAP-ENC:Array id="ref-5" SOAP-ENC:arrayType="xsd:ur-type[0]">
</SOAP-ENC:Array> 
<a1:LogicalCallContext id="ref-6">
<User href="#ref-8"/> 
</a1:LogicalCallContext>
<a3:User id="ref-8"> 
<FirstName id="ref-9" xsi:type="SOAP-ENC:string">Roman</FirstName>
<LastName id="ref-10" xsi:type="SOAP-ENC:string">Kiss</LastName>
</a3:User> 
</SOAP-ENV:Body> 
</SOAP-ENV:Envelope> 

This string request has to be de-serialized back to the SoapMessage object, which is a clone object of the sender's origin. After that, we have an enough information to perform a Method call on the remote object. Conversion of the SoapMessage to IMessage needs to use some trick, therefore there is no class in .Net namespaces to do it. The trick is based on creating a RealProxy wrapper using the remote object type and its endpoint url address and overriding an Invoke method of the base RealProxy class. Using the Reflection (late binding) to invoke the remote method, the RealProxy wrapper will catch the IMessage before its processing in the channel sink. Now, the Invoke method can perform updating the IMessage by original images such as LocicalCallContext and url address. After that, the IMessage is the same like on the client side over Internet. Now it's easy to forward this IMessage to the Message Sink calling its SyncProcessMessage method. The rest is done by a remoting paradigm.

The SyncProcessMessage returns an IMessage which it represents a ReturnMessage from the remote method. Now the process is going to reverse into the text/xml format of the SoapMessage response . I will skip it this process for its simplicity and I will continue on the client custom channel (ws) where a response message has been returned. Before that, have a look the text/xml formatted SoapMessage response how it has been sent back to the custom channel over Internet:

Text/XML formatted SoapMessage Response.

XML
<SOAP-ENV:Envelope xmlns:xsi="<a href="%22%3C/span">http://www.w3.org/2001/XMLSchema-instance">http://www.w3.org/2001/XMLSchema-instance 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:SOAP-ENC="<a href="%22%3C/span">http://schemas.xmlsoap.org/soap/encoding/">

The Web Service Client Proxy changes:

C#
[System.Web.Services.WebServiceBindingAttribute(Name="ServiceSoap", 
Namespace="http://tempuri.org/")]
public class Service : System.Web.Services.Protocols.SoapHttpClientProtocol {

   [System.Diagnostics.DebuggerStepThroughAttribute()]
      public Service(string uri) {
      this.Url = uri;
   }
   ...
}   

WebServiceListener

This is a Web Service gateway to listen a MethodCall formatted into the string request. There are two different WebMethods for this process: SyncProcessMessage and SyncProcessSoapMessage. The functions have a simple logic divided into tree steps:

  • Decoding and de-serializing of the request message
  • Invoking the Remote Method
  • Encoding and serialization of the response message
C#
[WebMethod]
public string SyncProcessMessage(string request)
{
   // Request: decoding and deserializing 

   byte[] reqbyteArray = Convert.FromBase64String(request);
   MemoryStream reqstream = new MemoryStream();
   reqstream.Write(reqbyteArray, 0, reqbyteArray.Length);
   reqstream.Position = 0;
   BinaryFormatter bf = new BinaryFormatter();
   IMessage reqmsg = (IMessage)bf.Deserialize(reqstream);
   reqmsg.Properties["__Uri"] = reqmsg.Properties["__Uri2"]; // work around!!

   reqstream.Close();

   // Action: invoke the Remote Method 

   string[] stype = reqmsg.Properties["__TypeName"].ToString().Split(new Char[]{','}); // split typename

   Assembly asm = Assembly.Load(stype[1].TrimStart(new char[]{' '})); // load type of the remote object

   Type objectType = asm.GetType(stype[0]);                           // type

   string objectUrl = reqmsg.Properties["__Uri"].ToString();          // endpoint

   object ro = RemotingServices.Connect(objectType, objectUrl);       // create proxy

   TraceIMessage(reqmsg);
   IMessage rspmsg = RemotingServices.ExecuteMessage((MarshalByRefObject)ro, 
                                                     (IMethodCallMessage)reqmsg);
   TraceIMessage(rspmsg);

   // Response: encoding and serializing 

   MemoryStream rspstream = new MemoryStream();
   bf.Serialize(rspstream, rspmsg);
   rspstream.Position = 0;
   string response = Convert.ToBase64String(rspstream.ToArray());
   rspstream.Close();

   return response;
}

[WebMethod]
public string SyncProcessSoapMessage(string request)
{
   IMessage retMsg = null;
   string response;

   try
   {
      Trace.WriteLine(request);

      // Request: deserialize string into the SoapMessage

      SoapFormatter sf = new SoapFormatter();
      sf.TopObject = new SoapMessage();
      StreamWriter rspsw = new StreamWriter(new MemoryStream());
      rspsw.Write(request);
      rspsw.Flush();
      rspsw.BaseStream.Position = 0;
      ISoapMessage soapmsg = (ISoapMessage)sf.Deserialize(rspsw.BaseStream);
      rspsw.Close();

      // Action: invoke the Remote Method 

      object[] values = soapmsg.ParamValues;
      string[] stype = values[2].ToString().Split(new Char[]{','});
      Assembly asm = Assembly.Load(stype[1].TrimStart(new char[]{' '}));
      Type objectType = asm.GetType(stype[0]);
      string objectUrl = values[0].ToString(); 
      RealProxyWrapper rpw = new RealProxyWrapper(objectType, objectUrl, 
                                                  soapmsg.ParamValues[4]);
      object ro = rpw.GetTransparentProxy();
      MethodInfo mi = objectType.GetMethod(values[1].ToString());
      object retval = mi.Invoke(ro, values[3] as object[]);
      retMsg = rpw.ReturnMessage;
   }
   catch(Exception ex) 
   {
      retMsg = new ReturnMessage((ex.InnerException == null) ? 
                                           ex : ex.InnerException, null);
   }
   finally 
   {
      // Response: serialize IMessage into string

      Stream rspstream = new MemoryStream();
      SoapFormatter sf = new SoapFormatter();
      RemotingSurrogateSelector rss = new RemotingSurrogateSelector();
      rss.SetRootObject(retMsg);
      sf.SurrogateSelector = rss;
      sf.AssemblyFormat = FormatterAssemblyStyle.Full;
      sf.TypeFormat = FormatterTypeStyle.TypesAlways;
      sf.TopObject = new SoapMessage();
      sf.Serialize(rspstream, retMsg);
      rspstream.Position = 0;
      StreamReader sr = new StreamReader(rspstream);
      response = sr.ReadToEnd();
      rspstream.Close();
      sr.Close();
   }

   Trace.WriteLine(response);
   return response;
}

The implementation of the steps are depended from the type of formatter such as SoapFormatter or BinaryFormatter. The first and last steps are straightforward using the Remoting namespace classes. The second one (action) for the SoapFormatter message needed to create the following class to obtain IMessage of the MethodCall:

C#
public class RealProxyWrapper : RealProxy
{
   string _url;
   string _objectURI;
   IMessageSink _messageSink;
   IMessage _msgRsp;
   LogicalCallContext _lcc;

   public IMessage ReturnMessage { get { return _msgRsp; }}
   public RealProxyWrapper(Type type, string url, object lcc) : base(type)
   {
      _url = url;
      _lcc = lcc as LogicalCallContext;

      foreach(IChannel channel in ChannelServices.RegisteredChannels)
      {
         if(channel is IChannelSender)
         {
            IChannelSender channelSender = (IChannelSender)channel;
            _messageSink = channelSender.CreateMessageSink(_url, null, out _objectURI);
            if(_messageSink != null)
               break;
         }
      }

      if(_messageSink == null)
      {
         throw new Exception("A supported channel could not be found for url:"+ _url);
      }
   }
   public override IMessage Invoke(IMessage msg)
   {
      msg.Properties["__Uri"] = _url; // endpoint

      msg.Properties["__CallContext"] = _lcc; // caller's callcontext

      _msgRsp = _messageSink.SyncProcessMessage(msg);

      return _msgRsp;
   }
}// RealProxyWrapper

Test

I built the following package to test functionality of the WebServiceListener and WebServiceChannelLib assemblies. Note that this package has only test purpose. Here is what you downloaded it:

  • ConsoleClient, the test console program to invoke the call over Internet - client machine
  • ConsoleServer, the host process of the MyRemoteObject - server machine
  • MyRemoteObject, the remote object - server machine
  • WebServiceChannelLib, the custom client channel
  • WebServiceListener, the Web Service listener - server machine

To recompile a package and its deploying in your environment follow these notes:

  • The folder WebServiceListener has to be moved to the virtual directory (inetpub\wwwroot).
  • The MyRemoteObject assembly has to be install into the GAC on the server machine
  • The WebServiceChannelLib assembly has to be install into the GAC on the client machine
  • (option) The MSMQChannelLib assembly [1] has to be install into the GAC on the server machine
  • The solution can be tested also using the same machine (Win2k/Adv Server)
  • Use the Echo WebMethod on the test page of the WebServiceListener to be sure that this service will be work

The test case is very simple. First step is to launch the ConsoleServer program. Secondly open the ConsoleClient program and follow its prompt text. If everything is going right you will see a response from the remote object over Internet. I am recommending to make a first test on the same machine and then deploying over Internet.

Conclusion

In this article has been shown one simple way how to implement a solution for remoting over Internet. I used the power of .Net Technologies such as SOAP, Remoting, Reflection and Web Service. The advantage of this solution is a full transparency between the consumer and remote object. This logical connectivity can be mapped into the physical path using the config files, which they can be administratively changed.

[1] http://www.codeproject.com/csharp/msmqchannel.asp

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralHttp Basic authentication(RFC 2617). Pin
kunaltilak2-May-10 20:42
kunaltilak2-May-10 20:42 
GeneralSame Code in Framework 2.0 Pin
mitragotri20-Jun-07 1:13
mitragotri20-Jun-07 1:13 
GeneralOne call at time to web method. Pin
Ashok Bhawar7-Jun-07 12:03
Ashok Bhawar7-Jun-07 12:03 
GeneralHi Roman Pin
Christopher G. Lasater13-Dec-06 6:49
Christopher G. Lasater13-Dec-06 6:49 
GeneralMSMQ Remoting Channel simplified. Pin
Richard Beacroft15-Dec-05 4:24
Richard Beacroft15-Dec-05 4:24 
GeneralRe: MSMQ Remoting Channel simplified. Pin
Roman Kiss15-Dec-05 6:33
Roman Kiss15-Dec-05 6:33 
Generalis this bidirectional Pin
Mayank Chauhan6-Sep-05 0:28
Mayank Chauhan6-Sep-05 0:28 
GeneralServer Pin
Vedderk27-Jul-05 6:10
sussVedderk27-Jul-05 6:10 
GeneralRe: Server Pin
Roman Kiss28-Jul-05 9:51
Roman Kiss28-Jul-05 9:51 
General&quot;Getting Error on publishing Tmodel on uddi server programatically&quot; Pin
Member 197389619-May-05 23:40
Member 197389619-May-05 23:40 
General&quot;Getting Error while publishing on uddi server programatically&quot; Pin
Member 197389619-May-05 23:39
Member 197389619-May-05 23:39 
GeneralDeploying interfaces-only assembly on virtual client machine Pin
Lazzaro12-Apr-05 8:21
Lazzaro12-Apr-05 8:21 
Generalinvoke webmethod Pin
Anonymous24-Feb-05 2:50
Anonymous24-Feb-05 2:50 
GeneralHi Pin
sanjit_rath19-Dec-04 6:30
sanjit_rath19-Dec-04 6:30 
GeneralRe: Hi Pin
Ehsan Golkar1-Jun-07 18:42
Ehsan Golkar1-Jun-07 18:42 
Questionwhy can not download the source code? Pin
Member 151901825-Nov-04 14:25
Member 151901825-Nov-04 14:25 
GeneralVery Good Code Pin
kevin.yee22-Jul-04 18:41
kevin.yee22-Jul-04 18:41 
Generaladd a Web Reference at runtime Pin
Member 6866223-May-04 21:14
Member 6866223-May-04 21:14 
GeneralMultiple Remoting Objects Pin
Edward Smoljanovic19-Apr-04 9:16
Edward Smoljanovic19-Apr-04 9:16 
GeneralImpressive...but... Pin
Anonymous24-Feb-04 9:33
Anonymous24-Feb-04 9:33 
GeneralRe: Impressive...but... Pin
nls24-Feb-04 10:17
nls24-Feb-04 10:17 
GeneralEvent Generator by WebService Pin
Majid Shahabfar10-Feb-04 19:30
Majid Shahabfar10-Feb-04 19:30 
Generalwebservice &amp; com+ transaction Pin
kevinasp5-Jan-04 21:02
kevinasp5-Jan-04 21:02 
how can I use transaction both in webservice for remote DBserver and .net remoting for local DBServer?Smile | :)
Questionclient in different language? Pin
Anonymous22-Dec-03 11:42
Anonymous22-Dec-03 11:42 
GeneralGreat Example Pin
solidstore17-Dec-03 0:58
solidstore17-Dec-03 0:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.