.NET手记-Autofac进阶(注册的概念 Registering Concepts)

Changwei | 1/11/2016 7:13:00 PM


通过创建ContainerBuilder并配置暴露的service(接口或者类型)来使用Autofac注册我们的组件。

组件(Components) 可以通过反射, 对象实例,或者lambda表达式来创建. ContainerBuilder有一系列的Register()方法来实现组件的注册。

ContainerBuilder中每个组件都能通过As()方法来暴露他们一个或多个service.

 

// Create the builder with which components/services are registered.
var builder = new ContainerBuilder();

// Register types that expose interfaces...
builder.RegisterType<ConsoleLogger>().As<ILogger>();

// Register instances of objects you create...
var output = new StringWriter();
builder.RegisterInstance(output).As<TextWriter>();

// Register expressions that execute to create objects...
builder.Register(c => new ConfigReader("mysection")).As<IConfigReader>();

// Build the container to finalize registrations
// and prepare for object resolution.
var container = builder.Build();

// Now you can resolve services using Autofac. For example,
// this line will execute the lambda expression registered
// to the IConfigReader service.
using(var scope = container.BeginLifetimeScope())
{
  var reader = container.Resolve<IConfigReader>();
}

 

反射组件 Reflection Components

 

通过类型注册

 

通过反射生成组件最典型的方法是通过类型注册:

 

var builder = new ContainerBuilder();
builder.RegisterType<ConsoleLogger>();
builder.RegisterType(typeof(ConfigReader));

 

当使用基于反射的组件时,Autofac会自动使用容器中最可能获取到的参数来构造实例你的类

例如,你的类有3个构造函数

 

public class MyComponent
{
    public MyComponent() { /* ... */ }
    public MyComponent(ILogger logger) { /* ... */ }
    public MyComponent(ILogger logger, IConfigReader reader) { /* ... */ }
}

 

现在使用如下方法注册你的组件:

 

var builder = new ContainerBuilder();
builder.RegisterType<MyComponent>();
builder.RegisterType<ConsoleLogger>().As<ILogger>();
var container = builder.Build();

using(var scope = container.BeginLifetimeScope())
{
  var component = container.Resolve<MyComponent>();
}

 

当你解析你的组件时,Autofac发现你已经注册了ILogger,但是没有注册IConfigReader。在这个例子中,第二个构造函数会被执行,因为容器中具有最符合的参数。

注意:任何你注册的Type必须都是实类型,这意味着你不能直接注册抽象类或者接口。应为在解析组件时,对象可能会被new实例化,而接口或者抽象类是无法直接实例化的。

 

指定构造函数 Specifying a Constructor

 

你可以通过方法UsingConstructor手动指定使用哪个构造函数,其参数为指定构造函数的参数类型。

 

var output = new StringWriter();
builder.RegisterInstance(output).As<TextWriter>();

 

注意你提供的参数必须能在容器中获取,不然会出现错误。

 

实例组件 Instance Components

 

在很多情况下,你可能想要预先生成对象实例并将它注册到容器中。你可以通过使用RegisterInstance方法来实现:

 

var output = new StringWriter();
builder.RegisterInstance(output).As<TextWriter>();

 

当你这么做的时候,一些事情需要被考虑到。Autofac会自动处理对象的释放,但是你很可能想自己控制对象的生命周期,而不是让Autofac调用对象Dispose方法。在这种情况下,你需要使用ExternallyOwned方法:

 

var output = new StringWriter();
builder.RegisterInstance(output)
       .As<TextWriter>()
       .ExternallyOwned();

 

当我们集成Autofac到现有项目中时,可能会存在容器中一些组件用到的对象的单例写法。RegisterInstance()也用来处理这种情况,你可以通过容器来注册它们,而不是直接使用它们:

 

builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();

 

这确保了单例最后会被排除,并使用容器托管的对象来替代他。

 

表达式组件 Lambda Expression Components

 

反射是创建组件相当好的默认选择,尽管当组件创建逻辑超出简单构造函数调用后,事情可能会变混乱。

Autofac支持通过委托或者Lambda表达式来创建组件:

 

builder.Register(c => new A(c.Resolve<B>()));

 

提供的参数c是一个组件上下文对象(IComponentContext),在上下文中创建组件。你可以通过它从容器中解析出其他的值来辅助创建你的组件。使用它而不是使用闭包去访问容器是很重要的,为了层叠容器能够被正确支持。

使用此上下文参数能够满足额外的依赖项。在这个例子中,类型A构造函数要求的B类型参数可能还会依赖其他类型参数。表达式创建的组件暴露的默认服务是通过表达式返回类型来推断的。

下面会给出一些例子:

 

复杂参数

 

builder.Register(c => new UserSession(DateTime.Now.AddMinutes(25)));

 

属性注入

 

builder.Register(c => new UserSession(DateTime.Now.AddMinutes(25)));

 

通过参数值选择实现方式

 

builder.Register<CreditCard>(
  (c, p) =>
    {
      var accountId = p.Named<string>("accountId");
      if (accountId.StartsWith("9"))
      {
        return new GoldCard(accountId);
      }
      else
      {
        return new StandardCard(accountId);
      }
    });

 

在这个例子中,CreditCard被两个类实现,分别是GoldenCard和StandardCard,哪种类型被实例取决于输入的卡号。

参数通过可选构造参数p来传入,注册方式可能像这样:

 

var card = container.Resolve<CreditCard>(new NamedParameter("accountId", "12345"));

 

范型组件 

 

Autofac支持范型,通过使用RegisterGeneric()方法:

 

builder.RegisterGeneric(typeof(NHibernateRepository<>))
       .As(typeof(IRepository<>))
       .InstancePerLifetimeScope();

 

当一个匹配的Service类型被请求时,容器会自动映射它到一个相近的实现类型:

 

// Autofac将会返回NHibernateRepository<Task>类型
var tasks = container.Resolve<IRepository<Task>>();

 

指定类型服务的注册将会覆盖掉范型版本。

 

服务 vs 组件 Services vs. Components

 

当注册组件时,我们需要告诉Autofac组件暴露了哪种服务。大多数情况下,会暴露组件自身的类型。

 

// This exposes the service "CallLogger"
builder.RegisterType<CallLogger>();

 

组件仅能被其暴露的服务来解析出实例,这就意味着如下:

 

// This will work because the component
// exposes the type by default:
scope.Resolve<CallLogger>();

// 这个将会失败
// tell the registration to also expose
// the ILogger interface on CallLogger:
scope.Resolve<ILogger>();

 

你也可以使用任何数量的服务来暴露你的组件:

 

builder.RegisterType<CallLogger>()
       .As<ILogger>()
       .As<ICallInterceptor>();

 

你可以通过你暴露的服务来解析出组件实例,但这同时意味着默认服务(组件自身类型)将被覆盖:

 

// These will both work because we exposed
// the appropriate services in the registration:
scope.Resolve<ILogger>();
scope.Resolve<ICallInterceptor>();

// 失败
// 默认服务被覆盖
scope.Resolve<CallLogger>();

 

如果你想为组件暴露一系列服务,同时自身类型仍然可用,你需要使用AsSelf方法:

 

builder.RegisterType<CallLogger>()
       .AsSelf()
       .As<ILogger>()
       .As<ICallInterceptor>();

 

现在所有服务均可以解析:

 

// These will all work because we exposed
// the appropriate services in the registration:
scope.Resolve<ILogger>();
scope.Resolve<ICallInterceptor>();
scope.Resolve<CallLogger>();

 

 

默认注册 Default Registrations

 

如果有多个提供相同service的组件被注册,Autofac默认使用最后注册的组件。

 

builder.Register<ConsoleLogger>().As<ILogger>();
builder.Register<FileLogger>().As<ILogger>();

 

在这个例子中,FileLogger将会成为ILogger的默认组件提供者,因为它是最后注册的。

为了改写这种行为,使用PreserveExistingDefaults()来修改:

 

builder.Register<ConsoleLogger>().As<ILogger>();
builder.Register<FileLogger>().As<ILogger>().PreserveExistingDefaults();

 

在这种情况下,ConsoleLogger将会成为默认组件提供者。

 

配置文件注册 Configuration of Registrations

 

你可以使用定义的XML或者代码模块(Module)来实现孕事批量注册或者更改。也可以使用Autofac modules进行一些动态注册生成或者条件注册逻辑。具体请看:http://autofac.readthedocs.org/en/latest/configuration/index.html

 

 

动态注册 Dynamically-Provided Registrations

 

Autofac modules是最简单的方式来引入动态注册逻辑或简单交叉特性。例如,你可以使用module动态地附加一个Log4net实例到解析出的一个service上。请看:http://autofac.readthedocs.org/en/latest/examples/log4net.html