Windows Communication Foundation (WCF)
WCF 集成对于客户端和服务端都依赖于 Autofac.Wcf NuGet 包 。
WCF 集成提供了服务端和服务代理的依赖注入整合。由于 WCF 的内部实现,对于每个请求的生命周期依赖没有明确的支持。
客户端
在您的服务客户端应用中使用 Autofac,有以下几点好处:
- 确定性释放资源:自动释放由
ChannelFactory.CreateChannel<T>()
创建的代理消耗的资源。 - 方便的服务代理注入:对于使用服务的类型,您可以轻松地为服务接口类型注入依赖项。
在应用程序启动时,针对每个服务注册一个 ChannelFactory<T>
和一个使用工厂打开通道的函数:
var builder = new ContainerBuilder();
// 注册服务的ChannelFactory。设置为SingleInstance,因为每次需要时都不需要新的实例。
builder
.Register(c => new ChannelFactory<ITrackListing>(
new BasicHttpBinding(),
new EndpointAddress("http://localhost/TrackListingService")))
.SingleInstance();
// 使用lambda注册服务接口,从工厂创建通道。包含UseWcfSafeRelease()辅助方法来处理正确的释放。
builder
.Register(c => c.Resolve<ChannelFactory<ITrackListing>>().CreateChannel())
.As<ITrackListing>()
.UseWcfSafeRelease();
// 您也可以注册其他依赖项。
builder.RegisterType<AlbumPrinter>();
var container = builder.Build();
在这个例子中...
- 直到从容器中请求
ITrackListing
时,才执行CreateChannel()
调用。 UseWcfSafeRelease()
配置选项确保在释放客户端通道时不会丢失异常信息。
当消费服务时,像通常那样添加构造函数依赖项。例如,这个示例展示了一个通过远程 ITrackListing
服务将专辑列表打印到控制台的应用。它通过 AlbumPrinter
类实现:
public class AlbumPrinter
{
readonly ITrackListing _trackListing;
public AlbumPrinter(ITrackListing trackListing)
{
_trackListing = trackListing;
}
public void PrintTracks(string artist, string album)
{
foreach (var track in _trackListing.GetTracks(artist, album))
Console.WriteLine("{0} - {1}", track.Position, track.Title);
}
}
当你从生命周期作用域中解析 AlbumPrinter
类时,服务接口的通道会自动注入。
注意,鉴于 服务代理是可丢弃的 ,你应该从子作用域而不是根容器中解析它。因此,如果必须手动解析(无论出于何种原因),确保您是从其中创建子作用域的地方进行操作:
using(var lifetime = container.BeginLifetimeScope())
{
var albumPrinter = lifetime.Resolve<AlbumPrinter>();
albumPrinter.PrintTracks("The Shins", "Wincing the Night Away");
}
服务端
快速入门
要让 Autofac 与 WCF 在服务端集成,你需要引用 WCF 集成的 NuGet 包,注册你的服务,并设置依赖解析器。你还需要更新 .svc
文件,引用 Autofac 服务主机工厂。
这里有一个示例应用启动块:
protected void Application_Start()
{
var builder = new ContainerBuilder();
// 注册你的服务实现。
builder.RegisterType<TestService.Service1>();
// 设置依赖解析器。
var container = builder.Build();
AutofacHostFactory.Container = container;
}
而 .svc
文件看起来像这样:
<%@ ServiceHost
Service="TestService.Service1, TestService"
Factory="Autofac.Integration.Wcf.AutofacServiceHostFactory, Autofac.Integration.Wcf" %>
下面的各节将详细说明这些功能以及如何使用它们。
注册服务实现
你可以通过类型、接口或名称三种方式之一在容器中注册服务类型。
通过类型注册
最常见的方式是直接在容器中注册服务实现类型,并在 .svc
文件中指定该实现类型。
在应用程序启动时,代码可能如下所示:
var builder = new ContainerBuilder();
builder.RegisterType<TestService.Service1>();
AutofacHostFactory.Container = builder.Build();
.svc
文件会指定适当的服务实现类型和主机工厂,如下所示:
<%@ ServiceHost
Service="TestService.Service1, TestService"
Factory="Autofac.Integration.Wcf.AutofacServiceHostFactory, Autofac.Integration.Wcf" %>
注意在 .svc
文件中需要使用服务的完全限定名,即 Service="Namespace.ServiceType, AssemblyName"
。
通过接口注册
第二种选择是将合同类型注册到容器中,并在 .svc
文件中指定合同。如果你不想改变 .svc
文件,但想更改处理请求的实现类型,这很有用。
应用程序启动时的代码可能是这样的:
var builder = new ContainerBuilder();
builder.RegisterType<TestService.Service1>()
.As<TestService.IService1>();
AutofacHostFactory.Container = builder.Build();
.svc
文件会指定服务合同类型和主机工厂,如下所示:
<%@ ServiceHost
Service="TestService.IService1, TestService"
Factory="Autofac.Integration.Wcf.AutofacServiceHostFactory, Autofac.Integration.Wcf" %>
同样,在 .svc
文件中需要使用合同的完全限定名,即 Service="Namespace.IContractType, AssemblyName"
。
通过名称注册
第三种选择是在容器中注册一个命名的服务实现,并在 .svc
文件中指定该服务名称。如果你希望进一步抽象出 .svc
文件,这非常有用。
在应用程序启动时,代码可能如下所示:
var builder = new ContainerBuilder();
builder.RegisterType<TestService.Service1>()
.Named<object>("my-service");
AutofacHostFactory.Container = builder.Build();
请注意,服务实现类型作为对象注册——这一点很重要。如果你注册了一个命名的服务,但没有将其注册为对象,那么将找不到它。
.svc
文件会指定你注册的服务名称和主机工厂,如下所示:
<%@ ServiceHost
Service="my-service"
Factory="Autofac.Integration.Wcf.AutofacServiceHostFactory, Autofac.Integration.Wcf" %>
选择合适的主机工厂
WCF 提供了两种服务主机工厂。Autofac 有对应于每个的实现。
如果你在.svc
文件中使用ServiceHostFactory
,请更新为AutofacServiceHostFactory
。这是 Autofac 和 WCF 最常见的用法。
如果你在.svc
文件中使用WebServiceHostFactory
,请更新为AutofacWebServiceHostFactory
。
不使用 .svc
的服务
如果你想不使用 .svc
文件使用服务,Autofac 可以支持。
如上所述,只需在容器中注册服务。
var builder = new ContainerBuilder();
builder.RegisterType<Service1>();
AutofacHostFactory.Container = builder.Build();
为了使用不带 .svc
的服务,需要在 web.config
文件的 serviceActivation
元素下添加一个工厂条目。这样可以确保使用 AutofacServiceHostFactory
激活服务。
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true">
<serviceActivations>
<add factory="Autofac.Integration.Wcf.AutofacServiceHostFactory, Autofac.Integration.Wcf"
relativeAddress="~/Service1.svc"
service="TestService.Service1, TestService" />
</serviceActivations>
</serviceHostingEnvironment>
扩展式服务
如果你想使用扩展式服务,按照上述方式在容器中注册服务。
var builder = new ContainerBuilder();
builder.RegisterType<Service1>();
AutofacHostFactory.Container = builder.Build();
然后使用 AutofacServiceHostFactory
和服务实现类型定义一个新的 ServiceRoute
。
RouteTable.Routes.Add(new ServiceRoute("Service1", new AutofacServiceHostFactory(), typeof(Service1)));
最后,将 UrlRoutingModule
添加到 web.config
文件。
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
<handlers>
<add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" />
</handlers>
</system.webServer>
配置完 IIS 后,你就可以通过 http://hostname/appname/Service1
访问 WCF 服务了。
WAS(Windows Activation Service)托管和非 HTTP 激活
在使用 WAS (Windows Activation Service) 部署 WCF 服务时,您无法在 Global.asax
文件的 Application_Start
事件中构建容器,因为 WAS 不使用标准的 ASP.NET 管道。
另一种方法是将包含名为 AppInitialize
的静态方法的代码文件放在 App_Code
文件夹中。
namespace MyNamespace
{
public static class AppStart
{
public static void AppInitialize()
{
// 在这里放置您的容器初始化代码。
}
}
}
更多关于 AppInitialize()
的信息,请参阅 "如何初始化托管的 WCF 服务" 。
自托管
要使用集成进行自托管 WCF 服务,关键在于在服务主机上使用 AddDependencyInjectionBehavior()
扩展。设置您的容器并进行注册,但不要设置全局容器。相反,将容器应用到您的服务主机上。
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<Service1>();
using (var container = builder.Build())
{
Uri address = new Uri("http://localhost:8080/Service1");
ServiceHost host = new ServiceHost(typeof(Service1), address);
host.AddServiceEndpoint(typeof(IEchoService), new BasicHttpBinding(), string.Empty);
// 这是重要的部分 - 将 DI 行为附加到服务主机,并传递容器。
host.AddDependencyInjectionBehavior<IService1>(container);
host.Description.Behaviors.Add(new ServiceMetadataBehavior {HttpGetEnabled = true, HttpGetUrl = address});
host.Open();
Console.WriteLine("The host has been opened.");
Console.ReadLine();
host.Close();
Environment.Exit(0);
}
处理 InstanceContextMode.Single
服务
从可扩展性角度来看,使用 InstanceContextMode.Single
并不是一个好主意。允许多个调用者使用 ConcurrencyMode.Multiple
访问单个实例意味着您还需要注意任何共享状态被多个线程访问的问题。如果可能,应创建 InstanceContextMode.PerCall
的服务。
IIS/WAS 部署
AutofacServiceHostFactory
会识别标记为 InstanceContextMode.Single
的 WCF 服务,并确保可以从容器提供服务主机的单例实例。如果容器中的服务未使用 SingleInstance()
生命周期范围进行注册,将抛出异常。对于未标记为 InstanceContextMode.Single
的 WCF 服务,在容器中注册 SingleInstance()
服务也是无效的。
自托管
当自托管服务时,可以手动为标记为 InstanceContextMode.Single
的服务进行构造函数注入。这是通过从容器中获取一个 SingleInstance()
服务,然后将其传递给手动创建的 ServiceHost
构造函数来实现的。
// 从容器中获取 SingleInstance。
var service = container.Resolve<IService1>();
// 将其传递给 ServiceHost,阻止它使用默认构造函数创建实例。
var host = new ServiceHost(service, new Uri("http://localhost:8080/Service1"));
模拟请求生命周期范围
如前所述,由于 WCF 内部原因,WCF 对于按请求生命周期依赖没有明确的支持。
Autofac 通过使用 实例提供程序 来与 WCF 集成,该提供程序会根据服务实例上下文跟踪服务和依赖项的生命周期范围。
总结来说:基于服务的 实例上下文模式,会为每个服务创建一个生命周期范围。
如果不设置,默认为 “会话”。 当客户端调用时,会为您的服务创建一个实例,后续来自同一客户端的调用将得到相同的实例。
但是,如果您想模拟按请求的生命周期范围,您可以:
- 将服务设置为
InstanceContextMode.PerCall
,使用 WCF ServiceBehaviorAttribute。 - 将服务和依赖项注册为
InstancePerLifetimeScope
。
执行这两步后,每次调用都会为一个新的生命周期范围创建一个新的实例(因为 WCF 实例上下文希望每次调用都创建一个新的服务实例)。然后,服务和依赖项只会在该实例上下文生命周期范围内一次性解决——实际上实现了按请求的生命周期。
请注意,如果您的服务实现中有共享的依赖项,且这些依赖项既存在于按调用的服务(InstanceContextMode.PerCall
)中,又存在于按会话或单例服务(InstanceContextMode.PerSession
或 InstanceContextMode.Single
)中,这可能会产生反效果:对于按调用的服务,不会为每次调用创建新的服务实例,这意味着共享的依赖项(注册为 InstancePerLifetimeScope
)在整个服务生命周期内都是单例的。您可能需要尝试将依赖项注册为 InstancePerCall
或 InstancePerLifetimeScope
,以获得期望的效果。
使用装饰器与服务
标准的 Autofac 服务托管几乎适用于所有情况,但如果您的 WCF 服务实现上使用了 装饰器(不是依赖项的装饰器,而是实际服务实现的装饰器),则需要使用 多租户 WCF 服务托管机制,而不是标准的 Autofac 服务主机。
您不需要使用多租户容器、传递租户 ID 或使用其他多租户选项,但确实需要使用多租户服务主机。
这是因为 WCF 托管(.NET 内部)要求主机使用具体类型(而非抽象或接口)初始化,一旦提供了类型,就不能更改。当使用装饰器时,我们直到首次解引用时才会知道最终类型(在将所有装饰器连接在一起等操作之后)。多租户托管机制通过添加另一个动态代理来解决这个问题——一个空的、无目标的具体类,实现了服务接口。当 WCF 主机需要实现时,其中一个动态代理会被启动,而实际的实现(在这种情况下,您的装饰过的 WCF 实现)将是目标。
再次强调,只有当您装饰服务实现类本身时才需要这样做。如果您仅装饰或适应服务实现的依赖项,无需使用多租户主机。标准托管将正常工作。
示例
Autofac 示例存储库有一个 WCF 服务实现示例,以及一个 作为该服务客户端的 MVC 应用程序。
还有示例展示了 一个多租户 WCF 服务 和 关联的客户端,说明了如何使用 多租户服务托管 。