|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
ContentsIntroductionThis 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:
I am assuming that you have a knowledge of the .Net Remoting and Web Service included WSE -DIME. UsageConsuming 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:
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. ConfigurationsThe WSDimeChannel can be configured using the standard and custom properties:
machine.configThis 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 ( <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
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): // activate a remote object 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 Upload/Download binary files
The Upload/Download binary files between the remote object and its
consumer over Internet is based on the //AttachmentTicket - Client side AttachmentTicket at = new AttachmentTicket(Guid.NewGuid().ToString(), "senderName"); //create ticket //upload test at.Attach("UploadFile", "FileToUpload.bin"); //AttachmentTicket - Server side AttachmentTicket at = new AttachmentTicket(); //pick-up ticket if(at.Items.Contains("UploadFile")) { int filesize = at.Detach("UploadFile", "UploadedFile.bin"); }
The client side requires initiating the
The download files works in the same manner. In this case the sender side
(server) is attaching a file to the
Note that the Concept and DesignConcept 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 (
The Server side is represented by the
As you can see above flowchart, there is no necessary to make any
stream/text/stream conversions between the 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
In my solution there is only one
attachment such as binary formatted The following code snippets show an advanced feature of the WSE - DIME Attachments at the both sides: //DIME Request - client side DimeAttachment reqDA = new DimeAttachment("text/plain", TypeFormatEnum.MediaType, reqstream); webservice.RequestSoapContext.Attachments.Clear(); webservice.RequestSoapContext.Attachments.Add(reqDA); //DIME Request - Server side Stream stream = HttpSoapContext.RequestContext.Attachments[0].Stream; Implementation
The implementation is divided into the following assemblies ( 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: // IMessageSink (MethodCall) public virtual IMessage SyncProcessMessage(IMessage msgReq) { IMessage msgRsp = null; Service webservice = null; try { //ws proxy webservice = new Service(); webservice.Url = m_outpath; // workaround! msgReq.Properties[OBJECTURI] = m_ObjectUri; // serialize IMessage BinaryFormatter bf = new BinaryFormatter(); MemoryStream reqstream = new MemoryStream(); bf.Serialize(reqstream, msgReq); reqstream.Position = 0; //DIME Request DimeAttachment reqDA = new DimeAttachment("text/plain", TypeFormatEnum.MediaType, reqstream); webservice.RequestSoapContext.Attachments.Clear(); webservice.RequestSoapContext.Attachments.Add(reqDA); // call Web Service webservice.SyncProcessMessage(); reqstream.Close(); //DIME Response if(webservice.ResponseSoapContext.Attachments.Count == 0) throw new Exception("Missing the Remoting DIME Message."); DimeAttachment rspDA = webservice.ResponseSoapContext.Attachments[0]; //serializing the stream to the IMessage msgRsp = (IMessage)bf.Deserialize(rspDA.Stream); //clean up rspDA.Stream.Close(); //work around to bring back the following properties 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
This is a Web Service gateway to pick-up a DIME Message. The remoting
The response
The following code snippet shows an implementation of the [WebMethod] public void SyncProcessMessage() { IMessage iMsgRsp = null; IMessage iMsgReq = null; BinaryFormatter bf = new BinaryFormatter(); DimeAttachment reqDA = null; try { //Dime Message - request 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(); //Dispatcher iMsgRsp = Dispatcher(iMsgReq); } catch(Exception ex) { //remoting exception iMsgRsp = new ReturnMessage(ex, (IMethodCallMessage)iMsgReq); } finally { //serialize response MemoryStream rspStream = new MemoryStream(); bf.AssemblyFormat = FormatterAssemblyStyle.Full; bf.TypeFormat = FormatterTypeStyle.TypesAlways; bf.Serialize(rspStream, iMsgRsp); rspStream.Position = 0; //DIME Message DimeAttachment attachment = new DimeAttachment("text/plain", TypeFormatEnum.MediaType, rspStream); HttpSoapContext.ResponseContext.Attachments.Clear(); HttpSoapContext.ResponseContext.Attachments.Add(attachment); //cleanup if(reqDA != null) HttpSoapContext.RequestContext.Attachments.Remove(reqDA); } }
The following code snippet shows a helper function of the WebMethod to
dispatch the private IMessage Dispatcher(IMessage iMsgReq) { IMessage iMsgRsp = null; if(iMsgReq.Properties["__Uri"] != null) { //parse url address string strObjectUrl = iMsgReq.Properties["__Uri"].ToString(); string[] urlpath = strObjectUrl.Split(';'); string[] s = urlpath[0].Split('/'); //check endpoint if(urlpath.Length == 1 && s.Length == 1) { //this is an end channel Trace.WriteLine("Endpoint: " + strObjectUrl); iMsgRsp = ChannelServices.SyncDispatchMessage(iMsgReq); } else { //this is a chained channel string strDummy = null; IMessageSink iMessageSink = null; //find a properly channel foreach(IChannel ch in ChannelServices.RegisteredChannels) { if(ch is IChannelSender) { IChannelSender iChannelSender = (IChannelSender)ch; iMessageSink = iChannelSender.CreateMessageSink(strObjectUrl, null, out strDummy); if(iMessageSink != null) { //this is a next channel Trace.WriteLine("Chained channel is " + ch.ChannelName + ", url=" + strObjectUrl); break; } } } if(iMessageSink == null) { //no channel found it 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; }
This is a helper assembly for Upload/Download binary files using the namespace RKiss.WebServiceRemoting { [Serializable] public class AttachmentTicket : ILogicalThreadAffinative { const string m_TicketName = "_AttachmentTicket"; readonly string m_Id; //ticket Id readonly string m_Source; //initiator's Id readonly Hashtable m_Items; //items // public string Id { get {return m_Id; }} public string Source { get {return m_Source; }} public Hashtable Items { get {return m_Items; } } //constructor for Response CallContext 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"); } } //constructor for Request 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); } //stream 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; } //file 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; } //generic serialized object 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); } } //interface contract public interface IWSDimeRemoting { string Echo(int id, string msg); [OneWay] void EchoOneWay(int id, string msg); } }
InstallationI am assuming that the Web Services Enhancements 1.0 has been installed on your machine. Here are the installation steps:
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.TestI built the following package to test functionality of the Remoting Custom Channel over Internet:
The steps to perform the Remoting Upload/Download test over Internet:
When the button Echo has been pressed, the following remote method is going to be performed at the remote object: //IWSDimeRemoting public string Echo(int id, string msg) { string response = ""; int filesize = 0; AttachmentTicket at = null; //exception test if(msg.IndexOf("throw") >= 0) throw new Exception("This is an Exception test"); //Attachment 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); } //request to download file 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 ConclusionIn 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
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||