Changing the Programming Model

The previous sinks all add functionality to both the client- and the server-side of a .NET Remoting application. The pluggable sink architecture nevertheless also allows the creation of sinks, which change several aspects of the programming model. In Chapter 5, for example, you've seen that passing custom credentials such as username and password involves manual setting of the channel sink's properties for each object:

 CustomerManager  mgr  = new CustomerManager(); IDictionary props   = ChannelServices.GetChannelSinkProperties(mgr); props["username"] = "dummyremotinguser"; props["password"] = "12345"; 

In most real-world applications, it is nevertheless preferable to set these properties on a per-host basis, or set them according to the base URL of the destination object. In a perfect world, this would be possible using either configuration files or code, as in the following example:

 <configuration>    <system.runtime.remoting>       <application>          <channels>             <channel ref="http">             <clientProviders>                <formatter ref="soap" />                   <provider type="UrlAuthenticationSink.UrlAuthenticationSinkProvider,                        UrlAuthenticationSink">                       <url                          base="http://localhost"                          username="DummyRemotingUser"                          password="12345"                       />                       <url                          base=""                          username="MyUser"                          password="12345"                       />                   </provider>             </clientProviders>             </channel>            </channels>           </application>         </system.runtime.remoting>       </configuration> 

When setting these properties in code, you can simply omit the <url> entries from the configuration file and instead use the following lines to achieve the same behavior:

 UrlAuthenticator.AddAuthenticationEntry(    "http://localhost",    "dummyremotinguser",    "12345"); UrlAuthenticator.AddAuthenticationEntry(    "",    "MyUser",    "12345"); 

In fact, this behavior is not supported by default but can be easily implemented using a custom IClientChannelSink.

Before working on the sink itself, you have to write a helper class that provides static methods to store and retrieve authentication entries for given base URLs. All those entries will be stored in an ArrayList and can be retrieved by passing a URL to the GetAuthenticationEntry() method. In addition, default authentication information that will be returned if none of the specified base URLs matches the current object's URL can be set as well. This helper class is shown in Listing 9-17.

Listing 9-17: The UrlAuthenticator Stores Usernames and Passwords

start example
 using System; using System.Collections; namespace UrlAuthenticationSink {    internal class UrlAuthenticationEntry    {       internal String Username;       internal String Password;       internal String UrlBase;       internal UrlAuthenticationEntry (String urlbase,          String user,          String password)       {          this.Username = user;          this.Password = password;          this.UrlBase = urlbase.ToUpper();       }    }    public class UrlAuthenticator    {       private static ArrayList _entries = new ArrayList();       private static UrlAuthenticationEntry _defaultAuthenticationEntry;       public static void AddAuthenticationEntry(String urlBase,          String userName,          String password)       {          _entries.Add(new UrlAuthenticationEntry(             urlBase,userName,password));       }       public static void SetDefaultAuthenticationEntry(String userName,          String password)       {          _defaultAuthenticationEntry = new UrlAuthenticationEntry(             null,userName,password);       }       internal static UrlAuthenticationEntry GetAuthenticationEntry(String url)       {          foreach (UrlAuthenticationEntry entr in _entries)          {             // check if a registered entry matches the url-parameter             if (url.ToUpper().StartsWith(entr.UrlBase))             {                return entr;             }          }          // if none matched, return the default entry (which can be null as well)          return _defaultAuthenticationEntry;       }    } } 
end example

The sink itself calls a method that checks if an authentication entry exists for the URL of the current message. It then walks the chain of sinks until reaching the final transport channel sink, on which is set the properties that contain the correct username and password. It finally sets a flag for this object's sink so that this logic will be applied only once per sink chain. The complete source for this sink can be found in Listing 9-18.

Listing 9-18: The UrlAuthenticationSink

start example
 using System; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Messaging; using System.IO; namespace UrlAuthenticationSink {    public class UrlAuthenticationSink: BaseChannelSinkWithProperties,                                    IClientChannelSink    {       private IClientChannelSink _nextSink;       private bool _authenticationParamsSet;       public UrlAuthenticationSink(IClientChannelSink next)       {          _nextSink = next;       }       public IClientChannelSink NextChannelSink       {          get {             return _nextSink;          }       }       public void AsyncProcessRequest(IClientChannelSinkStack sinkStack,          IMessage msg,          ITransportHeaders headers,          Stream stream)       {          SetSinkProperties(msg);          // don’t push on the sinkstack because this sink doesn’t need          // to handle any replies!          _nextSink.AsyncProcessRequest(sinkStack,msg,headers,stream);       }       public void AsyncProcessResponse(          IClientResponseChannelSinkStack sinkStack,          object state,          ITransportHeaders headers,          Stream stream)       {          // not needed       }       public Stream GetRequestStream(IMessage msg,                                         ITransportHeaders headers)       {          return _nextSink.GetRequestStream(msg, headers);       }       public void ProcessMessage(IMessage msg,                                     ITransportHeaders requestHeaders,                                     Stream requestStream,                                     out ITransportHeaders responseHeaders,                                     out Stream responseStream)       {          SetSinkProperties(msg);          _nextSink.ProcessMessage(msg,requestHeaders,requestStream,             out responseHeaders,out responseStream);       }       private void SetSinkProperties(IMessage msg)       {          if (! _authenticationParamsSet)          {             String url = (String) msg.Properties["__Uri"];             UrlAuthenticationEntry entr =                UrlAuthenticator.GetAuthorizationEntry(url);             if (entr != null)             {                IClientChannelSink last = this;                while (last.NextChannelSink != null) {                {                   last = last.NextChannelSink;                }                // last now contains the transport channel sink                last.Properties["username"] = entr.Username;                last.Properties["password"] = entr.Password;             }              _authenticationParamsSet = true;          }       }    } } 
end example

The corresponding sink provider examines the <url> entry, which can be specified in the configuration file below the sink provider:

 <provider type="UrlAuthenticationSink.UrlAuthenticationSinkProvider,           UrlAuthenticationSink">     <url        base="http://localhost"        username="DummyRemotingUser"        password="12345"     /> </provider> 

The sink provider will receive those entries via the providerData collection, which contains objects of type SinkProviderData. Every instance of SinkProviderData has a reference to a properties dictionary that allows access to the attributes (base, username, and password) of the entry.

When the base URL is set in the configuration file, it simply calls UrlAuthenticator.AddAuthenticationEntry(). If no base URL has been specified, it sets this username/password as the default authentication entry. You can see the complete source code for this provider in Listing 9-19.

Listing 9-19: The UrlAuthenticationSinkProvider

start example
 using System; using System.Runtime.Remoting.Channels; using System.Collections; namespace UrlAuthenticationSink {    public class UrlAuthenticationSinkProvider: IClientChannelSinkProvider    {       private IClientChannelSinkProvider _nextProvider;       public UrlAuthenticationSinkProvider(IDictionary properties,               ICollection providerData)       {          foreach (SinkProviderData obj in providerData)          {             if (obj.Name == "url")             {                if (obj.Properties["base"] != null)                {                   UrlAuthenticator.AddAuthenticationEntry(                      (String) obj.Properties["base"],                      (String) obj.Properties["username"],                      (String) obj.Properties["password"]);                }                else                {                   UrlAuthenticator.SetDefaultAuthenticationEntry(                      (String) obj.Properties["username"],                      (String) obj.Properties["password"]);                }             }          }       }       public IClientChannelSinkProvider Next       {          get {return _nextProvider; }          set {_nextProvider = value;}       }       public IClientChannelSink CreateSink(IChannelSender channel,              string url,              object remoteChannelData)       {          // create other sinks in the chain          IClientChannelSink next = _nextProvider.CreateSink(channel,             url,             remoteChannelData);          // put our sink on top of the chain and return it          return new UrlAuthenticationSink(next);       }    } } 
end example

Using This Sink

When using this sink, you can simply add it to your client-side sink chain in the configuration file as shown here:

 <configuration>   <system.runtime.remoting>     <application>       <channels>        <channel ref="http">          <clientProviders>            <formatter ref="soap" />            <provider type="UrlAuthenticationSink.UrlAuthenticationSinkProvider,                   UrlAuthenticationSink" />          </clientProviders>        </channel>       </channels>     </application>   </system.runtime.remoting> </configuration> 


This sink is an IClientChannelSink, so you have to place it after the formatter.

To specify a username/password combination for a given base URL, you can now add this authentication information to the configuration file by using one or more <url> entries inside the <provider> section:

 <clientProviders>    <formatter ref="soap" />    <provider type="UrlAuthenticationSink.UrlAuthenticationSinkProvider,                       UrlAuthenticationSink">          <url                 base="http://localhost"                 username="DummyRemotingUser"                 password="12345"           />        </provider> </clientProviders> 

If you don't want to hard code this information, you can ask the user of your client program for the username/password and employ the following code to register it with this sink:

 UrlAuthenticator.AddAuthenticationEntry(<url>, <username>, <password>); 

To achieve the same behavior as that of the <url> entry in the previous configuration snippet, you use the following command:

 UrlAuthenticator.AddAuthenticationEntry(    "http://localhost",    "dummyremotinguser",    "12345"); 

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: