Contents
- Introduction
- Usage
Concept and
Design
- Implementation
- Test
- Conclusion
This article describes a design and implementation of the Remoting over
Internet using the Web Services Enhancements - DIME feature as
a mechanism to flow the remoting binary messages between the server and client
sides. The concept of the Remoting over Internet has been described in my
previously article [1] based on the remoting custom channel (client side) and
Web Service as a gateway (server side) to convert messages into the remoting
infrastructure using the base64 encoded/decoded SOAP formatted messages. This conversion has been necessary
(included its performance penalty) to use when the messages crossing the firewall. Using the recently released the Microsoft Web Services
Enhancements 1.0 - DIME technology (Direct Internet Message
Encapsulation) allows to eliminate this performance overhead and creating
a "binary through" between the client and server remoting infrastructures over
the web server. This article shows in details how to use the DIME Attachment for
Remoting over Internet included uploading/downloading binary files. As I
mentioned early, this article is a update of the Remoting over Internet using
the newest Microsoft technology - WSE, so I will be concentrated only for
parts related with the DIME usage. Basically, this update will allow to use a
custom remoting channel for the following features:
- binary formatted remoting over Internet
- upload binary files to the remote object over Internet
- download binary files from remote object over Internet
- chaining remoting channels (forwarding messages to the
different channels)
I am assuming that you have a knowledge of the .Net Remoting
and Web Service included WSE -DIME.
Consuming a remote object over Internet using the Web
Service Gateway is very
straightforward and full transparently and it requires to install the following
assemblies into the GAC on the both sides such as client and
server:
The Web Server requires to install the following:
- WebServiceRemoting, this is a Web
Service (gateway) to listen an incoming remoting message from the client side and forward
it to the local remoting infrastructure (incoming message).
Note that the remote object can be hosted by any process
and using the chaining channel feature the remoting message can be re-routed to
the properly channel published by its host process, for instance, Tcp channel of
the windows service.
Configurations
The WSDimeChannel can be configured using the
standard and custom properties:
-
id
specifies a unique name of the channel that can be used when
referring to it (e.g. id="wsdime")
-
type
is a full assembly name of the channel that will be loaded
-
ref
is the channel template being referenced (e.g. ref="wsdime")
-
name
specifies the name of the channel. Each channel has a unique name which is
used for properly message routing. Default name is wsdime.
-
priority
specifies a preference for the channel. The default value is 1. Higher numbers
mean higher priority. So the messages are queued based on this number.
-
ssl
specifies a
bool value of the security communication protocol such as http or https. The
default value is false for http.
This file contains common - machine driven
configurations. Inserting the following (wsdime) custom channel into the
<channels> remoting section will simplify its referencing.
<system.runtime.remoting>
<application>
...
</application>
<channels>
...
<channel id="wsdime" type="RKiss.WebServiceRemoting.Sender, WSDimeChannel,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=a850c3d2d71811e0" />
</channels>
<channelSinkProviders>
...
</channelSinkProviders>
</system.runtime.remoting>
server.exe.config
This is a standard configuration file of the server host process. There is no special
requirement related to our custom channel. The following example configures a
well-known remote object (RemoteObject
) on the tcp channel, port 8222.
<configuration>
<system.runtime.remoting>
<application >
<service>
<wellknown mode="SingleCall" type="MyRemoteObject.RemoteObject,
MyRemoteObject, Version=1.0.0.0,
Culture=neutral, PulicKeyToken=212418570a471efb"
objectUri="endpoint" />
</service>
<channels>
<channel ref="tcp" port="8222" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
client.exe.config
This is an example of the client config file to register our Custom Remoting
Channel. The host process has been configured for tcp and wsdime
outgoing channels.
<configuration>
<system.runtime.remoting>
<application>
<channels>
<channel ref="tcp" />
<channel ref="wsdime" ssl="false" desc="remoting over internet" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
web.config
This is a WebServiceRemoting config file. The following snippet is its
part. The remoting section shows a publishing example of the remote object
hosted by the Web Server with two outgoing channels - tcp
and wsdime. Note that the length of the attachments has been configured
for 128MB.
<!-- Remoting part -->
<system.runtime.remoting>
<application >
<service>
<wellknown mode="SingleCall"
type="MyRemoteObject.RemoteObject, MyRemoteObject,
Version=1.0.0.0, Culture=neutral,
PulicKeyToken=212418570a471efb" objectUri="endpoint" />
</service>
<channels>
<channel ref="tcp" />
<channel ref="wsdime" ssl="false" desc="remoting over internet" />
</channels>
</application>
</system.runtime.remoting>
<!-- WSE-DIME Attachments part -->
<system.web>
<webServices>
<soapExtensionTypes>
<add type="Microsoft.Web.Services.WebServicesExtension,
Microsoft.Web.Services,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
priority="1" group="0"/>
</soapExtensionTypes>
</webServices>
<!-- set maximum length of the Http Request (128MB) -->
<httpRuntime maxRequestLength="128000" />
Activating a remote object
The well-known remote object (WKO) is activated by its consumer using the GetObject
method mechanism. The client proxy is created based on the Interface
contract metadata assembly installed in the GAC (see an argument interfaceType
).
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 (see
the following code snippet):
string objectUrl =
@"wsdime://localhost/WebServiceRemoting/Service.asmx; " +
@"tcp://localhost:8222/endpoint";
The delimiter indicates a request to forward the Remoting Messages to the
next remoting channel (chained channel) until the target channel is reached.
Note that the wsdime 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
based on its published interface contract (for instance: IWSDimeRemoting):
Type interfaceType = typeof(IWSDimeRemoting);
string objectUrl = @"wsdime://localhost/WebServiceRemoting/Service.asmx; " +
@"tcp://localhost:8222/endpoint";
IWSDimeRemoting ro = (IWSDimeRemoting)Activator.GetObject(interfaceType,
objectUrl);
Now, the client can invoked the remote method using the remote
object transparent proxy (ro). Note that the interfaceType
and objectUrl
in the above example are hard-coded (programmatically
setup), but they can be configured administratively in the config file using the
application key/value design pattern.
Upload/Download binary files
The Upload/Download binary files between the remote object and its
consumer over Internet is based on the CallContext
object. This object
flows through the Remoting infrastructure in bi-directional manner within the
IMessage
stream. For this purpose the special object
AttachmentTicket
has been created and packaged in the separate assembly
WSDimeRemotingInterface
. Using the
AttachmentTicket
class does the
upload/download process easy at the both sides as it is shown in the
following code snippet:
AttachmentTicket at = new AttachmentTicket(Guid.NewGuid().ToString(),
"senderName");
at.Attach("UploadFile", "FileToUpload.bin");
AttachmentTicket at = new AttachmentTicket();
if(at.Items.Contains("UploadFile"))
{
int filesize = at.Detach("UploadFile", "UploadedFile.bin");
}
The client side requires initiating the AttachmentTicket
using the request constructor and attaching the uploaded file with its unique
logical name. On the other side - server, the uploaded file is detached from the
current AttachmentTicket
(located in the IMessage) by its unique name.
The download files works in the same manner. In this case the sender side
(server) is attaching a file to the AttachmentTicket
object.
Note that the AttachmentTicket
class has been derived from the ILogicalThreadAffinative
and its
implementation is not depended from the WSE-DIME technology.
Concept of the Remoting over Internet is based on dispatching a remoting
message over Internet using the Web Services Enhancements - DIME functions as a transport
layer. The following picture shows this solution:
Client activates a remote WKO to obtain its transparent proxy.
During this process the custom remoting channel (wsdime
) 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 this is a custom client (sender) channel - wsdime. This
channel sink has a responsibility to attach/detach the IMessage
stream
to/from the SOAP DIME Message.
The Server side is represented by the WebServiceRemoting
web service. This is a Gateway to the Remoting infrastructure using a simple
WebMethod
without any arguments to pass the DIME attachment. Its responsibility
is to detach, dispatch and attach the
IMessage
stream. The Dispatcher is parsing the
object
url address to decide where
IMessage
is
going to be
targeted. There are two kinds of
the ways: forwarding the
IMessage
to either the current channel endpoint
or the next channel.
As you can see above flowchart, there is no necessary to make any
stream/text/stream conversions between the WebMethod
and its consumer. The DIME
technology does all magic glue for flowing the binary
formatted IMessage
over Internet.
Direct Internet Message Encapsulation (DIME)
This is a
specification of the multiple binary records with SOAP messages. It allows to
package any kind of data including images files, SOAP messages or DIME messages
into the records outside of the SOAP envelop within the standard transport
protocols such as HTTP, TCP and SMTP. Using the same transmission design
pattern, the SOAP DIME message is sent to the Web Server, where can be easy
targeted. DIME specifies an efficient encapsulation mechanism - method for
attachments to SOAP messages.
Based on the DIME specification, the binary
formatted remoting IMessage
can be easy packaged into the SOAP DIME
message and flowed through the Web Server. The Web Server has a capability to
identify what is the SOAP message and what is an attachment. The other word, the
DIME is used as a bridge between the heterogeneous infrastructures using the
loosely coupled design pattern.
In my solution there is only one
attachment such as binary formatted IMessage
stream in the DIME package. Note
that the IMessage
stream can contained a CallContext object as a
"vehicle" of the binary images needed for upload/download process. This
mechanism is full transparently to the remoting infrastructure.
The
following code snippets show an advanced feature of the WSE - DIME Attachments
at the both sides:
DimeAttachment reqDA = new DimeAttachment("text/plain",
TypeFormatEnum.MediaType, reqstream);
webservice.RequestSoapContext.Attachments.Clear();
webservice.RequestSoapContext.Attachments.Add(reqDA);
Stream stream = HttpSoapContext.RequestContext.Attachments[0].Stream;
The implementation is divided into the following assemblies (CustomChannel
,
WebService
and Interface):
WSDimeChannel
This is a Remoting Custom Client Channel assembly to process an outgoing remoting
message over Internet. The following code snippet shows a major function of the
Sender channel:
public virtual IMessage SyncProcessMessage(IMessage msgReq)
{
IMessage msgRsp = null;
Service webservice = null;
try
{
webservice = new Service();
webservice.Url = m_outpath;
msgReq.Properties[OBJECTURI] = m_ObjectUri;
BinaryFormatter bf = new BinaryFormatter();
MemoryStream reqstream = new MemoryStream();
bf.Serialize(reqstream, msgReq);
reqstream.Position = 0;
DimeAttachment reqDA = new DimeAttachment("text/plain",
TypeFormatEnum.MediaType, reqstream);
webservice.RequestSoapContext.Attachments.Clear();
webservice.RequestSoapContext.Attachments.Add(reqDA);
webservice.SyncProcessMessage();
reqstream.Close();
if(webservice.ResponseSoapContext.Attachments.Count == 0)
throw new Exception("Missing the Remoting DIME Message.");
DimeAttachment rspDA = webservice.ResponseSoapContext.Attachments[0];
msgRsp = (IMessage)bf.Deserialize(rspDA.Stream);
rspDA.Stream.Close();
msgRsp.Properties["__Uri"] = msgReq.Properties["__Uri"];
}
catch(Exception ex)
{
Trace.WriteLine(string.Format("Client:SyncProcessMessage error = {0}",
ex.Message));
msgRsp = new ReturnMessage(ex, (IMethodCallMessage)msgReq);
}
finally
{
if(webservice != null)
webservice.Dispose();
}
return msgRsp;
}
The binary serialized IMessage
is attached to the RequestSoapMessage
before
invoking the WebMethod
using the DIME design pattern. The same mechanism is used
also for a Response message.
WebServiceRemoting
This is a Web Service gateway to pick-up a DIME Message. The remoting IMethodCallMessage
can be obtained after detaching and
de-serializing a binary attachment from HttpSoapContext.RequestContext
.
To forward a remoting message to the target endpoint is done by calling the
helper function Dispatcher.
The response IMethodReturnMessage
from the target endpoint is returned back to the client
side using the same manner - the DIME attachment via the HttpSoapContext.ResponseContext
object.
The following code snippet shows an implementation of the WebMethod
to
process the Remoting Request/Response messages:
[WebMethod]
public void SyncProcessMessage()
{
IMessage iMsgRsp = null;
IMessage iMsgReq = null;
BinaryFormatter bf = new BinaryFormatter();
DimeAttachment reqDA = null;
try
{
if(HttpSoapContext.RequestContext.Attachments.Count == 0)
throw new Exception("Mising DIME Attachment (IMessage)");
reqDA = HttpSoapContext.RequestContext.Attachments[0];
iMsgReq = (IMessage)bf.Deserialize(reqDA.Stream);
iMsgReq.Properties["__Uri"] = iMsgReq.Properties[OBJECTURI];
reqDA.Stream.Close();
iMsgRsp = Dispatcher(iMsgReq);
}
catch(Exception ex)
{
iMsgRsp = new ReturnMessage(ex, (IMethodCallMessage)iMsgReq);
}
finally
{
MemoryStream rspStream = new MemoryStream();
bf.AssemblyFormat = FormatterAssemblyStyle.Full;
bf.TypeFormat = FormatterTypeStyle.TypesAlways;
bf.Serialize(rspStream, iMsgRsp);
rspStream.Position = 0;
DimeAttachment attachment = new DimeAttachment("text/plain",
TypeFormatEnum.MediaType, rspStream);
HttpSoapContext.ResponseContext.Attachments.Clear();
HttpSoapContext.ResponseContext.Attachments.Add(attachment);
if(reqDA != null)
HttpSoapContext.RequestContext.Attachments.Remove(reqDA);
}
}
The following code snippet shows a helper function of the WebMethod to
dispatch the IMessage
to the properly target endpoint. In the case of the
chaining channel, the IMessage
is forwarded to the next channel. Note that the
chained channel (sender channel) has to be registered in the host process such
as a Web Server.
private IMessage Dispatcher(IMessage iMsgReq)
{
IMessage iMsgRsp = null;
if(iMsgReq.Properties["__Uri"] != null)
{
string strObjectUrl = iMsgReq.Properties["__Uri"].ToString();
string[] urlpath = strObjectUrl.Split(';');
string[] s = urlpath[0].Split('/');
if(urlpath.Length == 1 && s.Length == 1)
{
Trace.WriteLine("Endpoint: " + strObjectUrl);
iMsgRsp = ChannelServices.SyncDispatchMessage(iMsgReq);
}
else
{
string strDummy = null;
IMessageSink iMessageSink = null;
foreach(IChannel ch in ChannelServices.RegisteredChannels)
{
if(ch is IChannelSender)
{
IChannelSender iChannelSender = (IChannelSender)ch;
iMessageSink = iChannelSender.CreateMessageSink(strObjectUrl,
null, out strDummy);
if(iMessageSink != null)
{
Trace.WriteLine("Chained channel is " + ch.ChannelName +
", url=" + strObjectUrl);
break;
}
}
}
if(iMessageSink == null)
{
string strError = string.Format("WSRemoting: A supported channel could not " +
be found for {0}", strObjectUrl);
iMsgRsp = new ReturnMessage(new Exception(strError),
(IMethodCallMessage)iMsgReq);
Trace.WriteLine(strError);
}
else
{
//check for an oneway attribute
IMethodCallMessage mcm = iMsgReq as IMethodCallMessage;
if(RemotingServices.IsOneWay(mcm.MethodBase) == true)
iMsgRsp = (IMessage)iMessageSink.AsyncProcessMessage(iMsgReq, null);
else
iMsgRsp = iMessageSink.SyncProcessMessage(iMsgReq);
}
}
}
else
{
//exception
Exception ex = new Exception("WSRemoting: The Uri address is null");
iMsgRsp = new ReturnMessage(ex, (IMethodCallMessage)iMsgReq);
}
return iMsgRsp;
}
WSDimeRemotingInterface
This is a helper assembly for Upload/Download binary files using the
CallContext
object. The wrapper
AttachmentTicket
class hides all
mechanism to attach and detach binary stream to/from
CallContext
object. Here is
its full implementation:
namespace RKiss.WebServiceRemoting
{
[Serializable]
public class AttachmentTicket : ILogicalThreadAffinative
{
const string m_TicketName = "_AttachmentTicket";
readonly string m_Id;
readonly string m_Source;
readonly Hashtable m_Items;
public string Id { get {return m_Id; }}
public string Source { get {return m_Source; }}
public Hashtable Items { get {return m_Items; } }
public AttachmentTicket()
{
object obj = CallContext.GetData(m_TicketName);
if(obj != null && obj is AttachmentTicket)
{
AttachmentTicket at = obj as AttachmentTicket;
m_Id = at.Id;
m_Source = at.Source;
m_Items = at.Items;
}
else
{
throw new Exception("No AttachmentTicket in the CallContext");
}
}
public AttachmentTicket(string id, string source)
{
m_Id = id;
m_Source = source;
m_Items = new Hashtable();
if(CallContext.GetData(m_TicketName) != null)
CallContext.FreeNamedDataSlot(m_TicketName);
CallContext.SetData(m_TicketName, this);
}
public int Attach(string key, Stream stream)
{
int length = 0;
try
{
stream.Position = 0;
length = (int)stream.Length;
byte[] buffer = new byte[length];
stream.Read(buffer, 0, buffer.Length);
Items[key] = buffer;
}
catch(Exception ex)
{
throw ex;
}
finally
{
if(stream != null)
stream.Close();
}
return length;
}
public int Detach(string key, Stream stream)
{
int length = 0;
try
{
byte[] buffer = Items[key] as byte[];
length = buffer.Length;
stream.Write(buffer, 0, length);
Items.Remove(key);
}
catch(Exception ex)
{
throw ex;
}
return length;
}
public int Attach(string key, string path)
{
FileStream fs = new FileStream(path, System.IO.FileMode.Open);
return Attach(key, fs);
}
public int Detach(string key, string path)
{
FileStream fs = null;
int filesize = 0;
try
{
fs = new FileStream(path, System.IO.FileMode.Create);
filesize = Detach(key, fs);
}
catch(Exception ex)
{
throw ex;
}
finally
{
if(fs != null)
fs.Close();
}
return filesize;
}
public void Attach(string key, object obj)
{
Items[key] = obj;
}
public void Detach(string key, out object obj)
{
obj = Items[key];
}
public void Close()
{
CallContext.FreeNamedDataSlot(m_TicketName);
}
}
public interface IWSDimeRemoting
{
string Echo(int id, string msg);
[OneWay]
void EchoOneWay(int id, string msg);
}
}
Installation
I am assuming that the Web Services Enhancements 1.0 has been installed on
your machine. Here are the installation steps:
- install WSDimeChannel assembly into the GAC
- modify machine.config file inserting the
wsdime channel
- install WSDimeRemotingInterface assembly into the GAC
- create Virtual Directory for WebServiceRemoting web service and
setup its access for ASPNET account (only for server side)
Note that the assemblies need to be installed into the GAG on the both sides
such as client and server. It can be done easy using the drag&drop mechanism
between the \bin and %Windows%\assembly folders.
The Web Service can be tested invoking the Echo WebMethod on the test page.
Now, it's a time to make a test of the remoting over internet including
upload/download binary test files. This step is described in the following
chapter.
I built the following package to test functionality of the Remoting Custom
Channel over Internet:
-
WindowsClient, Client host process - Windows Form program
-
ConsoleServer,
Server host process - Console program
-
MyRemoteObject, Remote test object
The steps to perform the Remoting Upload/Download test over Internet:
- Install MyRemoteObject assembly into the GAC
- Launch the ConsoleServer program
- Launch the WindowsClient program
- Select Upload or Download checkBox
- Press button Echo
- See the response messages on the Form and Console screen
When the button Echo has been pressed, the following remote method is going
to be performed at the remote object:
public string Echo(int id, string msg)
{
string response = "";
int filesize = 0;
AttachmentTicket at = null;
if(msg.IndexOf("throw") >= 0)
throw new Exception("This is an Exception test");
try
{
at = new AttachmentTicket();
if(at.Items.Contains("UploadFile"))
{
filesize = at.Detach("UploadFile", "UploadedFile.bin");
}
response = string.Format("Echo: id={0}, msg={1}; upload[{2}bytes]",
id, msg, filesize);
}
catch(Exception ex)
{
response = string.Format("Echo: id={0}, msg={1}", id, msg);
}
if(msg.IndexOf("download") >= 0)
{
if(at == null)
{
at = new AttachmentTicket(Guid.NewGuid().ToString(), "RemoteObject");
}
at.Attach("DownloadFile", "FileToDownload.bin");
}
Console.WriteLine(response);
return response;
}
The above code snippet shows usage of the AttachmentTicket
object for
Upload/Download binary files to/from its consumer. Note that the remote object
is full transparently to its consumer regardless of where the consumer
physically located included over Internet.
In this article has been shown a usage of WSE - DIME Attachment to pass
a binary formatted stream between the remoting infrastructures at the both sides
- remote object and its consumer. This technology allows to make a
plug&play remoting ends over Internet in the loosely coupled design manner
including uploading and downloading binary files.
[1]
http://www.codeproject.com/cs/webservices/remotingoverinternet.asp
[2]
http://msdn.microsoft.com/webservices/building/wse/default.aspx
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.