18.12 Creating Custom Channel Sinks for Logging

 <  Day Day Up  >  

You want to create a custom channel sink to intercept the method call to the remote object on the client.


Technique

Sink objects can intercept the method calls both on the client and on the server. The formatters discussed in Recipe 18.8, "Using Channels and Formatters," are such sink objects, too.

Such sinks are connected in a chain of multiple sink objects, where one sink hands over the message to the next sink. With the client, every sink object implements the interface IClientChannelSink , whereas on the server, the interface IServerChannelSink must be implemented.

A sink provider class implements a factory pattern to create sink objects. Sink providers are assigned to the channel. You can assign the sink provider by using a configuration file or by setting parameters in the constructor of a channel class.

Figure 18.4 shows how sink objects are mapped into a message chain.

Figure 18.4. Sink objects.

graphics/18fig04.gif

To create a custom channel sink, you must create two classes: a sink and a sink provider. The sink must implement interface IClientChannelSink , and the sink provider that is responsible for creating a sink object must implement the interface IClientChannelSinkProvider . For using the sink with the client, you can define the sink provider with the .NET Remoting configuration file.

In the example, the class LoggingSink is an implementation of a sink class that writes remoting requests to a log file before the request is passed to the next sink. LoggingSink derives from the base class BaseChannelSinkWithProperties that provides a common implementation for sinks that are configurable with properties and implements the interface IChannelSinkBase . IClientChannelSink derives from IChannelSinkBase . So by using the base class BaseChannelSinkWithProperties , it is not necessary to implement the methods of the base interface yourself:

 
 public class LoggingSink : BaseChannelSinkWithProperties, IClientChannelSink { 

In the constructor of a sink class, you have to remember the next sink to pass the request to this sink after the message was processed . With the LoggingSink class, you also need a filename in the constructor to define where the log information should be stored:

 
 public LoggingSink(IClientChannelSink sink, string logFilename) {     nextSink = sink;     this.logFilename = logFilename; } 

If you do not need asynchronous requests with your sink, you can throw an exception of type NotImplementedException with the methods AsyncProcessRequest and AsyncProcessResponse . However, because these methods are defined with the interface IClientChannelSink , it is necessary to write an implementation:

 
 public void AsyncProcessRequest(IClientChannelSinkStack sinkStack,                                 IMessage msg,                                 ITransportHeaders headers, Stream stream) {     throw new NotImplementedException(); } public void AsyncProcessResponse(                IClientResponseChannelSinkStack sinkStack, object state,                ITransportHeaders headers, Stream stream) {     throw new NotImplementedException(); } 

The read-only property NextChannelSink must return the next sink object. The next sink object was assigned in the constructor of the class LoggingSink :

 
 public IClientChannelSink NextChannelSink {     get     {         return nextSink;     } } 

The method GetRequestStream must return a stream object where the message is to be serialized:

 
 public Stream GetRequestStream(IMessage msg, ITransportHeaders headers) {     return(nextSink.GetRequestStream(msg, headers)); } 

The heart of the sink class is in the method ProcessMessage . With this method, the request from the client is received, and the response must be returned. In this method, you can access the properties and methods of the message, the request headers, and the request stream itself to write some information to a log file. To build up the response, you just have to call the next sink to process the message:

 
 public void ProcessMessage(IMessage msg,                            ITransportHeaders requestHeaders,                            Stream requestStream,                            out ITransportHeaders responseHeaders,                            out Stream responseStream) {     StreamWriter writer = new StreamWriter(logFilename, true);     writer.WriteLine("---- Message from the client ({0:T})----",                      DateTime.Now);     //...     // Hand off to the next sink in the chain.     nextSink.ProcessMessage(msg, requestHeaders, requestStream,                             out responseHeaders,                             out responseStream); } 

Listing 18.5 shows the complete implementation of the method ProcessMessage , where information about the message is written to the console.

Listing 18.5 A Sink Class Implementation to Log Remoting Requests to a File
 public void ProcessMessage(IMessage msg, ITransportHeaders requestHeaders,                            Stream requestStream,                            out ITransportHeaders responseHeaders,                            out Stream responseStream) {     StreamWriter writer = new StreamWriter(logFilename, true);     writer.WriteLine("---- Message from the client ({0:T})----", DateTime.Now);     writer.WriteLine("...Request Headers...");     foreach (DictionaryEntry header in requestHeaders)     {         writer.WriteLine("header - {0} = {1}", header.Key, header.Value);     }     writer.WriteLine();     IDictionary dict = msg.Properties;     foreach (object key in dict.Keys)     {         writer.WriteLine("{0} = {1}", key, dict[key]);     }     long pos = requestStream.Position;     byte[] buffer = new byte[requestStream.Length];     requestStream.Read(buffer, 0, (int)requestStream.Length);     System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();     string request = enc.GetString(buffer, 0, buffer.Length);     writer.WriteLine("...Request...");     writer.WriteLine(request);     Console.WriteLine();     // set stream to previous position for message processing     requestStream.Seek(pos, SeekOrigin.Begin);     writer.WriteLine("----------------------------------------------");     writer.Close();     // Hand off to the next sink in the chain.     nextSink.ProcessMessage(msg, requestHeaders, requestStream,                             out responseHeaders,                             out responseStream); } 

In addition to the sink class, you need a sink provider. The sink provider is responsible for creating the sink class.

The client sink provider has to implement the interface IClientChannelSinkProvider .

In the constructor of the sink provider, you pass the properties that are configured with the sink provider as the first argument. In the LoggingSinkProvider example, the property file defines the filename for the log file:

 
 private string logFilename = "c:\remotingdemo.log"; public LoggingSinkProvider(IDictionary properties,                            ICollection providerData) {     channelSinkProperties = properties;     if (properties["file"] != null)     {         logFilename = properties["file"].ToString();     }     //... 

The sink provider interface defines the property Next . This property is called to set the next provider:

 
 public IClientChannelSinkProvider Next {     get     {         return nextProvider;     }     set     {         nextProvider = value;     } } 

The method CreateSink creates a sink object of type LoggingSink . You perform this step by using the CreateSink method of the next provider and passing this sink to the LoggingSink constructor as well as the name of the log file:

 
 public IClientChannelSink CreateSink(IChannelSender channel,                                      string url,                                      object remoteChannelData) {     //...     IClientChannelSink nextSink = null;     if (nextProvider != null)     {         nextSink = nextProvider.CreateSink(channel, url,                                            remoteChannelData);         //...         return new LoggingSink(nextSink, logFilename);     }     return nextSink; } 

You can assign the sink provider to the channel by changing the configuration file (see Listing 18.6). The sink provider is defined with the <provider> element. You just have to set the type attribute that consists of the class name as well as the name of the assembly. <provider> is a child element of <clientProviders> that itself is a child element of <channelSinkProviders> . You assign the provider to the channel by setting the child element <clientProviders> of the <channel> element.

Listing 18.6 Remoting Configuration File with a Sink Provider
 <?xml version="1.0" encoding="utf-8" ?> <configuration>     <system.runtime.remoting>         <channelSinkProviders>             <clientProviders>                 <provider id="logger"                           type="_12_LoggingSink.LoggingSinkProvider, 12_LoggingSink" />             </clientProviders>         </channelSinkProviders>         <application>             <client>                 <wellknown url="http://localhost:8088/Demo"                            type="_1_RemoteObject.RemoteObject, 1_RemoteObject" />             </client>             <channels>                 <channel port="0" ref="http">                     <clientProviders>                         <formatter ref="soap" />                         <provider ref="logger" file="c:/log.txt" />                     </clientProviders>                 </channel>             </channels>         </application>     </system.runtime.remoting> </configuration> 

Comments

Sinks make it possible to intercept method calls. With this interception, you can read message information before it is passed to the server, change it, encrypt or decrypt messages, or perform any other technique you can imagine.

Figure 18.5 shows the output of the client application that displays the information about the message inside the custom channel sink.

Figure 18.5. Log file output using the channel sink.

graphics/18fig05.jpg

 <  Day Day Up  >  


Microsoft Visual C# .Net 2003
Microsoft Visual C *. NET 2003 development skills Daquan
ISBN: 7508427505
EAN: 2147483647
Year: 2003
Pages: 440

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net