65.9K
CodeProject 正在变化。 阅读更多。
Home

使用RoutingService聚合服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (3投票s)

2012年3月1日

MIT

3分钟阅读

viewsIcon

32504

downloadIcon

14

使用内置的 RoutingService .NET Framework 类聚合自托管 WCF 服务。

引言

您是否曾经尝试在规模适中的面向 SOA 的架构中进行 WCF 服务的自托管?如果是,您肯定遇到过这样的问题

上述问题的答案要么是实现一个通用服务类,该类实现您要发布和托管的所有服务契约,并将其托管在单个 ServiceHost 中,要么是使用 Net TCP 端口共享,通过这种方式,您会失去对系统的很大一部分控制权,并且还会引入对端口共享服务的依赖,这不一定是一个理想的选择。

上述问题的解决方案是通过利用内置的 RoutingService 类将请求的路由(即端口共享服务所做的事情)移到您的应用程序中。

实现

该解决方案的架构由一个基本服务组成,该服务仅包含您需要关心的三个类。

第一个是抽象服务句柄基类,它封装了一个 ServiceHost 并负责它在单独的线程上运行。您可以在附加的代码档案中查找此类的代码。

接下来是 ServiceShell 类,它实现了前面提到的句柄基类,并设置 ServiceHost 以在给定的基地址处托管 RoutingService。它还可以通过相对于其基地址的地址来公开(读取:路由到)具有自定义契约的多个子服务。

public class ServiceShell : ServiceHandleBase
{
    private readonly Uri _baseAddress;
    private readonly RoutingConfiguration _routingConfiguration;
    private readonly Guid _serviceShellId;

    public ServiceShell(Uri baseAddress)
    {
        if (baseAddress == null)
        {
            throw new ArgumentNullException("baseAddress");
        }
        _baseAddress = baseAddress;
        _routingConfiguration = 
          new RoutingConfiguration { RouteOnHeadersOnly = true, SoapProcessingEnabled = true };
        _serviceShellId = Guid.NewGuid();
    }

    protected override ServiceHost CreateServiceHost()
    {
        var serviceHost = new ServiceHost(typeof(RoutingService), _baseAddress);
        serviceHost.Description.Behaviors.Add(new RoutingBehavior(_routingConfiguration));
        return serviceHost;
    }

    protected override ServiceEndpoint CreateService(ServiceHost serviceHost)
    {
        CheckDisposed();
        return serviceHost.AddServiceEndpoint(typeof (IDuplexSessionRouter), 
              new NetTcpBinding(SecurityMode.None, true), string.Empty);
    }

    public ServiceHandleBase RegisterChildService(Type serviceImplementation, 
           Type serviceContract, Uri relativeUri)
    {
        CheckDisposed();
        return new AggregatedService(this, serviceImplementation, serviceContract, relativeUri);
    }

    private void RemoveFromFilterTable(MessageFilter filter)
    {
        lock (SyncRoot)
        {
            if (_routingConfiguration.FilterTable.Remove(filter))
            {
                UpdateFilterTable();
            }
        }
    }

    private void AddToFilterTable(MessageFilter filter, 
            IEnumerable<ServiceEndpoint> services)
    {
        lock (SyncRoot)
        {
            _routingConfiguration.FilterTable.Add(filter, services);
            UpdateFilterTable();
        }
    }

    private void UpdateFilterTable()
    {
        var parentRoutingExtensions = Host.Extensions.Find<routingextension>();
        parentRoutingExtensions.ApplyConfiguration(_routingConfiguration);
    }
    // ...
}

因此,我们来到了最后一个类,即 AggregatedService(也是一个服务句柄实现),它是 ServiceShell 的一个私有内部类,因为它需要访问 ServiceShell 的一些内部实现细节。此类通过命名管道绑定将传递给它的类型作为服务公开。命名管道地址使用其父级的唯一 ID 来确保在同一系统上运行的多个实例不会发生名称冲突。当此类服务启动时,它还会向父级 ServiceShell 注册一个 EndpointAddressMessageFilter,从而通过相对于 shell 的基地址的地址公开/路由服务的端点 - 当然 - 并且通过 shell 提供的绑定(在我们的例子中是一个 net.tcp 绑定)。

private class AggregatedService : ServiceHandleBase
{
    private readonly Binding _binding;
    private readonly MessageFilter _filter;
    private readonly ServiceShell _parent;
    private readonly Uri _rootUri;
    private readonly Type _serviceContract;
    private readonly Type _serviceImplementation;
    private readonly Uri _rootUriExternal;

    public AggregatedService(ServiceShell parent, Type serviceImplementation, 
           Type serviceContract, Uri relativeUri)
    {
        var baseUri = new Uri("net.pipe:///" + 
                      parent._serviceShellId + "/");
        _rootUri = new Uri(baseUri, relativeUri);
        _parent = parent;
        _serviceContract = serviceContract;
        _serviceImplementation = serviceImplementation;
        _binding = new NetNamedPipeBinding();
        _rootUriExternal = new Uri(_parent._baseAddress, relativeUri);
        _filter = new EndpointAddressMessageFilter(new EndpointAddress(_rootUriExternal));
    }

    protected override ServiceHost CreateServiceHost()
    {
        CheckDisposed();
        return new ServiceHost(_serviceImplementation, _rootUri);
    }

    protected override ServiceEndpoint CreateService(ServiceHost serviceHost)
    {
        CheckDisposed();
        return serviceHost.AddServiceEndpoint(_serviceContract, _binding, string.Empty);
    }

    public override bool Start()
    {
        lock (SyncRoot)
        {
            bool result = base.Start();
            if (result)
            {
                _parent.AddToFilterTable(_filter, new[] { Service });
            }
            return result;
        }
    }
    // ....
}

演示应用程序

演示应用程序包含两个简单的服务定义。一个计算服务,其中包含一个双向加法运算,以及一个双工回显服务,该服务执行一些字符串操作并将结果返回到双工回调契约。这些服务既不使用端口共享,也不需要实现所有服务契约的类。

static void Main()
{
    var shellUri = new Uri("net.tcp://:10011/AggregatedService/");
    var shellBinding = new NetTcpBinding(SecurityMode.None, true);

    using (var shell = new ServiceShell(shellUri))
    using (var echo = shell.RegisterChildService(typeof(EchoServer), 
           typeof(IEchoServer), new Uri("Echo", UriKind.Relative)))
    using (var calc = shell.RegisterChildService(typeof(CalculatorServer), 
           typeof(ICalculator), new Uri("Calc", UriKind.Relative)))
    {
        // External URI                                      -> Internal URI
        // ------------------------------------------------------------------------
        shell.Start();  // net.tcp://:10011/AggregatedService/      -> N/A
        echo.Start();   // net.tcp://:10011/AggregatedService/Echo  -> net.pipe://.../Echo
        calc.Start();   // net.tcp://:10011/AggregatedService/Calc  -> net.pipe://.../Calc
        
        var echoClient = new EchoClient(); 
        var echoChannelFactory = new DuplexChannelFactory<IEchoServer>(echoClient, 
            shellBinding, new Uri(shellUri, "Echo").ToString());
        var echoChannel = echoChannelFactory.CreateChannel(); 

        var calcChannelFactory = new ChannelFactory<icalculator>(shellBinding, 
            new Uri(shellUri, "Calc").ToString());
        var calcChannel = calcChannelFactory.CreateChannel();

        // ...        
        var c = calcChannel.Add(1, 2); // c == 3
        // ...
        echoChannel.Shout("demo");         
        // ...
    }
}

结论

正如您所看到的,RoutingService 实际上消除了使用外部端口共享服务和其他不可维护方法的需要。

关注点

除了命名管道绑定之外,我们还可以使用 NullTransport 绑定,但我没有使用它,因为命名管道 显然至少一样快

您可以在 MSDN 上阅读有关 WCF 路由的更多信息。我还偶然发现了一篇关于 过滤 的好文章。

本文的源代码也可以在 BitBucket 上作为 Mercurial 存储库使用。

© . All rights reserved.