冠军

导航

Advanced .NET Remoting: 第 9 章 4.改变编程模型

Advanced .NET Remoting: 第 9 章

4.改变编程模型

前面的所有连接器在 .NET Remoting 应用程序的服务器端和客户端两方面增强功能。可插拔的连接器架构不仅支持创建连接器,它还改变了编程模型的多个方面。例如,在第 5 章,你已经见到了为了传递像用户名和口令这样的认证凭据,导致手工修改每个对象的通道连接器的属性。

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

不过,在大多数真实世界的应用中,最好基于每个主机设置这些属性,或根据目标对象的基础 URL 设置它们。在理想情况下,使用配置文件或代码可以做到这一点,如以下示例所示:

<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="http://www.somewhere.org"
                                username="MyUser"
                                password="12345"
                            />
                        </provider>
                    </clientProviders>
                </channel>
            </channels>
        </application>
    </system.runtime.remoting>
</configuration>

当使用代码进行设置的时候,你可以简化配置文件中使用的 <url> 条目,使用下面的代码来达到相同的目的。

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

UrlAuthenticator.AddAuthenticationEntry(
    "http://www.somewhere.org",
    "MyUser",
    "12345");

不过,默认并不支持这种方式。可以通过使用自己定制实现的 IClientChannelSink 来轻松实现。

在真正处理连接器之前,你必须编写一个助理类,它提供静态方法用来存储和提取针对基础 URL 的验证条目。所有的条目以 ArrayList 的形式存储,可以通过提供给方法 GetAuthenticationEntry() 方法的 URL 参数来提取。另外,如果对于特定的基础 URL 来说,没有匹配的话,将返回默认的认证信息。助理类如列表 13-17 所示。

列表 13-17 存储用户名和口令的 UrlAuthenticator 助理类

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;
        }
    }
}

连接器本身调用其中的方法来检查对于当前消息的 URL 来说,是否存在一个认证条目。然后,它遍历连接器调用链,直到最后一个传输通道连接器,在这里设置包含正确用户名和口令的属性。最终,为该对象的连接器设置一个标志,以便该逻辑对于一个连接器链条来说只应用一次。该连接器的完整代码如下列表 13-18 所示。

列表 13-18 UrlAuthenticationSink

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;
            }
        }
    }
}

相关联的连接器提供器提供 <url> 条目,它们可以在下面的连接器提供器中,通过配置文件指定。

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

连接器提供器将通过 providerData 集合接收到这些条目。集合中包括 SinkProviderData 类型的对象实例。每个 SinkProdiverData 对象包含一个对 properties 的字典,它支持访问这些每个条目中的属性 ( 基础 URL、用户名、口令 )。

当配置文件中的条件设置了基础 URL 的时候,它简单地调用 UrlAuthenticator.AddAuthenticationEntry() 方法。如果没有指定基础 URL,就将这里的用户名和口令设置为默认认证值。该提供器的完整代码如列表 13-19 所示。

列表 13-19 UrlAuthenticationSinkProvider

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);
        }
    }
}

使用该连接器

在需要使用这个连接器的时候,你可以通过在配置文件中简单地将它添加到客户端的连接器链中,如下所示:

<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>

注意:该连接器是 IClientChannelSink,所以必须放在格式化器 之后

为了对特定的基础 URL 指定一组用户名和密码,现在可以通过将认证信息添加到配置文件中来完成,将一个或者多个 <url> 条件添加到 <provider> 配置节来。

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

如果你不希望硬编码这些信息,你可以让客户端应用程序的用户提供用户名和密码,然后使用下面的代码来为连接器注册它:

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

为了达到与前面使用配置文件片断中的 <url> 所示的相同效果,你可以如下使用:

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

参考资料

posted on 2022-06-01 10:43  冠军  阅读(134)  评论(0编辑  收藏  举报