Web API 定义了依赖注入的接口 IDependencyResolver , 定义如下:
public interface IDependencyResolver : IDependencyScope, IDisposable {
IDependencyScope BeginScope();
}
public interface IDependencyScope : IDisposable {
object GetService(Type serviceType);
IEnumerable<object> GetServices(Type serviceType);
}
接口 IDependencyScope 有两个方法:
IDependencyResolver
接口继承自 IDependencyScope
并添加了一个 BeginScope 方法。 每次请求都会创建新的 Controller , 为了管理对象的生命周期, IDependencyResolver 使用了作用域 (Scope) 的概念。
HttpConfiguration 对象的 DependencyResolver 拥有全局作用域, 当 Web API 创建 Controller 时, 会调用 BeginScope 方法, 返回一个类型为 IDependencyScope 的子作用域。
Web API 接下来调用子作用域的 GetService 方法来创建 Controller , 依赖注入容器可以在这里创建 Controller 的实例, 并解决 Controller 的依赖项。 如果 GetService 方法返回 null
, 则 Web API 会使用 Controller 默认的构造函数来创建 Controller 实例。
当请求完成时, 调用的 Dispose 方法来销毁子作用域, 如果 Controller 有资源需要销毁, 请在 Controller 的 Dispose 方法中显式销毁资源。
通过下面的 PowerShell 命令来安装 Windsor:
Install-Package Castle.Windsor
知道了 IDependencyScope 和 IDependencyResolver 的定义以及注意问题, 实现起来就很容易了, 首先来实现 IDependencyScope , 代码如下:
public class WindsorDependencyScope : IDependencyScope {
private ILogger logger = NullLogger.Instance;
public ILogger Logger {
get { return logger; }
set { logger = value; }
}
private IWindsorContainer container;
protected IWindsorContainer Container {
get { return container; }
}
public WindsorDependencyScope(IWindsorContainer container) {
this.container = container;
}
public void Dispose() {
// ChildScope 销毁时把 Container 也销毁;
if (container.Parent != null) {
container.RemoveChildContainer(container);
}
container.Dispose();
}
public object GetService(Type serviceType) {
// 根据 GetService 的约定, 遇到未知类型不能抛出异常
Logger.DebugFormat("GetService of type {0}", serviceType);
object service = null;
try {
service = container.Resolve(serviceType);
}
catch (Exception ex) {
Logger.Error(string.Format("Can not resolve {0}", serviceType), ex);
}
return service;
}
public IEnumerable<object> GetServices(Type serviceType) {
Logger.DebugFormat("Get All Service of type {0}", serviceType);
// Windsor 的 ResolveAll 方法不会抛出异常, 所以可以直接用;
return container.ResolveAll(serviceType).Cast<object>();
}
}
有了 WindsorDependencyScope , 再实现一个 WindsorDependencyResolver 就更容易了, 代码如下:
public class WindsorDependencyResolver : WindsorDependencyScope, IDependencyResolver {
public WindsorDependencyResolver(IWindsorContainer container) : base(container) { }
public IDependencyScope BeginScope() {
// 创建一个新的 WindsorContainer , 并添加为 ChildContainer
var childContainer = new WindsorContainer();
Container.AddChildContainer(childContainer);
// 返回新的 DepedencyScope
return new WindsorDependencyScope(childContainer);
}
}
通过下面的代码将 WindsorDependencyResolver 注册到 HttpConfiguration 就可以使用了:
public void Configuration(IAppBuilder app) {
var config = new HttpConfiguration();
// 创建 WindsorContainer 新实例
var container = new WindsorContainer();
// 向 Container 注册 WindsorDependencyResolver , 这样 WindsorDependencyResolver 自己
// 也可以使用使用依赖项;
container.Register(
Component.For<IWindsorContainer>().Instance(container),
Component.For<IDependencyResolver>().ImplementedBy<WindsorDependencyResolver>()
);
// 通过配置文件注册其它类型
var installer = Castle.Windsor.Installer.Configuration.FromXmlFile("windsor.config");
container.Install(installer);
config.DependencyResolver.Resolve<IDependencyResolver>();
// 向 OWIN 注册 WebAPI
app.UseWebApi(config);
}
值得注意的是, Windsor 中注册的类型默认全是单例的, 而 WebAPI 对每次请求都需要创建 Controller 的新实例, 在请求完成之后销毁实例, 所以在 Windsor 注册的 Controller 类型必须显示声明生命周期为 transient 才能正常使用。
如果使用配置文件注册, 则需要在 xml 文件中添加生命周期, 示例代码如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<facilities>
<facility id="logging"
type="Castle.Facilities.Logging.LoggingFacility, Castle.Facilities.Logging"
loggingApi="log4net" configFile="log.config" />
</facilities>
<components>
<!-- Controller 类型必须显示声明生命周期为 transient 默认为 singleton -->
<component type="WebApi.Controllers.CategoriesController,WebApi" lifestyle="transient"/>
<component type="WebApi.Controllers.ProductsController,WebApi" lifestyle="transient"/>
</components>
</configuration>
如果使用代码注册, 同样必须在代码中指定生命周期, 示例代码如下:
container.Register(
Component.For<CategoryController>().LifestyleTransient(),
Component.For<ProductsController>().LifestyleTransient()
);
如果 Controller 非常多的话, 可以考虑使用自定义的 Installer 。