使用RoutingService聚合服务
使用内置的 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 存储库使用。