Implementing the Client Channel

Before looking into the implementation of the client-side SMTPClientChannel, I show you how it will later be used in a configuration file:

 <channel name="smtpclient" type="SMTPChannel.SMTPClientChannel, SMTPChannel" senderEmail="client_1@localhost" smtpServer="localhost" pop3Server="localhost" pop3User="client_1" pop3Password="client_1" pop3PollInterval="1" > 

All of these parameters are mandatory, and I explain their meanings in Table 10-2.

Table 10-2: Parameters for SMTPClientChannel




Unique name for this channel.


The value for the e-mail's From: header. The server will reply to the address specified here.


Your outgoing e-mail server's name.


Your incoming mail server's name.


The POP3 user account that is assigned to this client application.


The password for the application's POP3 account.


Interval in seconds at which the framework will check for new mail at the server.

A client channel has to extend BaseChannelWithProperties and implement IChannelSender, which in turn extends IChannel. These interfaces are shown in Listing 10-3.

Listing 10-3: IChannel and IChannelSender

start example
 public interface IChannel {    // Properties    string ChannelName { get; }    int ChannelPriority { get; }    // Methods    string Parse(string url, ref String objectURI); } public interface IChannelSender: IChannel {    // Methods    IMessageSink CreateMessageSink(string url, object remoteChannelData,       ref String objectURI); } 
end example

Let's have a look at the basic implementation of a channel and the IChannel interface first. Each channel that is creatable using a configuration file has to supply a special constructor that takes two parameters: an IDictionary object, which will contain the attributes specified in the configuration file (for example, smtpServer); and an IClientChannelSinkProvider object, which points to the first entry of the chain of sink providers specified in the configuration file.

In the case of the SMPTClientChannel, this constructor will store those values to member variables and call POP3PollManager.RegisterPolling() to register the connection information:

 using System; using System.Collections; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting; using System.Runtime.Remoting.Messaging; namespace SMTPChannel {    public class SMTPClientChannel: BaseChannelWithProperties, IChannelSender    {       IDictionary _properties;       IClientChannelSinkProvider _provider;       String _name;    public SMTPClientChannel (IDictionary properties,               IClientChannelSinkProvider clientSinkProvider)    {       _properties = properties;       _provider = clientSinkProvider;       _name = (String) _properties["name"];       POP3PollManager.RegisterPolling(          (String) _properties["pop3Server"],          (String) _properties["pop3User"],          (String) _properties["pop3Password"],          Convert.ToInt32((String)_properties["pop3PollInterval"]),          false);    } 

The implementation of IChannel itself is quite straightforward. You basically have to return a priority and a name for the channel, both of which can be either configurable or hard coded. You also have to implement a Parse() method that takes a URL as its input parameter. It then has to check if the given URL is valid for this channel (returning null if it isn't) and split it into its base URL, which is the return value from the method, and the object's URI, which is returned as an out parameter:

 public string ChannelName {    get    {       return _name;    } } public int ChannelPriority {    get    {       return 0;    } } public string Parse(string url, out string objectURI) {    String email;    SMTPHelper.parseURL(url, out email, out objectURI);    if (email == null || email=="" || objectURI == null || objectURI =="")    {       return null;    }    else    {       return "smtp:" + email;    } } 

The implementation of IChannelSender consists only of a single method: CreateMessageSink(). This method will either receive a URL or a channel data store as parameters and and will return an IMessageSink as a result and the destination object's URI as an out parameter.

When no URL is specified as a parameter, you should cast the channel data store (which is passed as object) to IChannelDataStore and take the first URL from it instead. You then have to check if the URL is valid for your channel and return null if it isn't. Next you add the client channel's transport sink provider at the end of the provider chain and call CreateSink() on the first provider. The resulting sink chain is then returned from the method:

 public IMessageSink CreateMessageSink(string url, object remoteChannelData,        out string objectURI) {    if (url == null && remoteChannelData != null &&              remoteChannelData as IChannelDataStore != null )    {       IChannelDataStore ds = (IChannelDataStore) remoteChannelData;       url = ds.ChannelUris[0];    }    // format: "smtp:user@host.domain/URI/to/object"    if (url != null && url.ToLower().StartsWith("smtp:"))    {       // walk to last provider and this channel sink's provider       IClientChannelSinkProvider prov = _provider;       while (prov.Next != null) { prov = prov.Next ;};       prov.Next = new SMTPClientTransportSinkProvider(            (String) _properties["senderEmail"],            (String) _properties["smtpServer"]);       String dummy;       SMTPHelper.parseURL(url,out dummy,out objectURI);       IMessageSink msgsink =                (IMessageSink) _provider.CreateSink(this,url,remoteChannelData);       return msgsink;    }    else    {       objectURI =null;       return null;    } } 

Creating the Client's Sink and Provider

Even though this sink provider is called a transport sink provider, it is in fact a straightforward implementation of an IClientChannelSinkProvider, which you've encountered in Chapter 9. The main difference is that its CreateSink() method has to parse the URL to provide the transport sink with the correct information regarding the destination e-mail address and the object's URI. It also doesn't need to specify any special constructors, as it will not be initialized from a configuration file. The complete sink provider is shown in Listing 10-4.

Listing 10-4: The SMTPClientTransportSinkProvider

start example
 using System; using System.Collections; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting; using System.Runtime.Remoting.Messaging; namespace SMTPChannel {    public class SMTPClientTransportSinkProvider: IClientChannelSinkProvider    {       String _senderEmailAddress;       String _smtpServer;       public SMTPClientTransportSinkProvider(String senderEmailAddress,                String smtpServer)       {          _senderEmailAddress = senderEmailAddress;          _smtpServer = smtpServer;       }       public IClientChannelSink CreateSink(IChannelSender channel,          string url, object remoteChannelData)       {          String destinationEmailAddress;          String objectURI;          SMTPHelper.parseURL(url,out destinationEmailAddress,out objectURI);          return new SMTPClientTransportSink(destinationEmailAddress,                _senderEmailAddress,_smtpServer, objectURI);       }       public IClientChannelSinkProvider Next       {          get          {             return null;          }          set          {             // ignore as this has to be the last provider in the chain          }       }    } } 
end example

When relying on the helper classes presented previously, the client-side sink's implementation will be quite simple as well. First, you have to provide a constructor that allows the sender's and the recipient's e-mail address, the object's URI, and the SMTP server to be set:

 using System; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting; using System.Runtime.Remoting.Messaging; using System.IO; namespace SMTPChannel {    public class SMTPClientTransportSink: BaseChannelSinkWithProperties,       IClientChannelSink, IChannelSinkBase    {       String _destinationEmailAddress;       String _senderEmailAddress;       String _objectURI;       String _smtpServer;       public SMTPClientTransportSink(String destinationEmailAddress,          String senderEmailAddress, String smtpServer, String objectURI)       {          _destinationEmailAddress = destinationEmailAddress;          _senderEmailAddress = senderEmailAddress;          _objectURI = objectURI;          _smtpServer = smtpServer;    } 

The key functionality of this sink is that ProcessMessage() and AsyncProcessMessage() cannot just forward the parameters to another sink, but instead have to send it by e-mail to another process. ProcessMessage() parses the URL to split it into the e-mail address and the object's URI. Those values are then used to call SMTPHelper.SendRequestMessage(). As this method has to block until aresponse is received, it also calls SMTPHelper.WaitAndGetResponseMessage().

Finally, it hands over the processing of the return message to the ProcessMessage() method of SMTPHelper to split it into a stream and an ITransportHeaders object that have to be returned from ProcessMessage() as out parameter.

 public void ProcessMessage(IMessage msg,    ITransportHeaders requestHeaders, Stream requestStream,    out ITransportHeaders responseHeaders,    out Stream responseStream) {    String ID;    String objectURI;    String email;    // check the URL    String URL = (String) msg.Properties["__Uri"];    SMTPHelper.parseURL(URL,out email,out objectURI);    if ((email==null) || (email == ""))    {       email = _destinationEmailAddress;    }    // send the message    SMTPHelper.SendRequestMessage(_senderEmailAddress,email,_smtpServer,             requestHeaders,requestStream,objectURI, out ID);    // wait for the response    POP3Msg popmsg = SMTPHelper.WaitAndGetResponseMessage(ID);    // process the response    SMTPHelper.ProcessMessage(popmsg,out responseHeaders,out responseStream,          out ID); } 

The AsyncProcessMessage() method does not block and wait for a reply, but instead creates a new instance of AsyncResponseHandler (which I introduce in a bit) and passes the sink stack to it. It then registers this object with the SMTPHelper to enable it to forward the response to the underlying sinks and finally to the IAsyncResult object that has been returned from the delegate's BeginInvoke() method:

 public void AsyncProcessRequest(IClientChannelSinkStack sinkStack,     IMessage msg, ITransportHeaders headers, Stream stream) {     String ID;     String objectURI;     String email;     // parse the url     String URL = (String) msg.Properties["__Uri"];     SMTPHelper.parseURL(URL,out email,out objectURI);     if ((email==null) || (email == ""))     {         email = _destinationEmailAddress;     }     // send the request message     SMTPHelper.SendRequestMessage(_senderEmailAddress,email,_smtpServer,        headers,stream,objectURI, out ID);     // create and register an async response handler     AsyncResponseHandler ar = new AsyncResponseHandler(sinkStack);     SMTPHelper.RegisterAsyncResponseHandler(ID, ar); } 

The rest of the SMTPClientTransportSink is just a standard implementation of the mandatory parts of IClientChannelSink. As these methods are not expected to be called for a transport sink, they will either return null or throw an exception:

 public void AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack, object state, ITransportHeaders headers, Stream stream) {     // not needed in a transport sink!     throw new NotSupportedException(); } public Stream GetRequestStream(System.Runtime.Remoting.Messaging.IMessage msg, System.Runtime.Remoting.Channels.ITransportHeaders headers) {     // no direct way to access the stream     return null; } public System.Runtime.Remoting.Channels.IClientChannelSink NextChannelSink {     get     {         // no more sinks         return null;     } } 

The last thing you have to implement before you are able to use this channel is the AsyncResponseHandler class that is used to pass an incoming reply message to the sink stack. Its HandleAsyncResponsePop3Msg() method is called by SMTPHelper.MessageReceived() whenever the originating call has been placed by using an asynchronous delegate. It calls SMTPHelper.ProcessMessage() to split the e-mail message into a Stream object and an ITransportHeaders object and then calls AsyncProcessResponse() on the sink stack that has been passed to AsyncResponseHandler in its constructor:

 using System; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting; using System.IO; using System.Runtime.Remoting.Messaging; namespace SMTPChannel {    internal class AsyncResponseHandler    {       IClientChannelSinkStack _sinkStack;       internal AsyncResponseHandler(IClientChannelSinkStack sinkStack)        {           _sinkStack = sinkStack;        }        internal void HandleAsyncResponsePop3Msg(POP3Msg popmsg)        {           ITransportHeaders responseHeaders;           Stream responseStream;           String ID;           SMTPHelper.ProcessMessage(popmsg,out responseHeaders,                out responseStream,out ID);           _sinkStack.AsyncProcessResponse(responseHeaders,responseStream);        }    } } 

Well, that's it! You've just completed your first client-side transport channel.

Advanced  .NET Remoting C# Edition
Advanced .NET Remoting (C# Edition)
ISBN: 1590590252
EAN: 2147483647
Year: 2002
Pages: 91
Authors: Ingo Rammer © 2008-2017.
If you may any questions please contact us: