二、注册组件

2.1、注册(Registration Concepts)

  Autofac中可以通过创建ContainerBuilder对象来注册组件并将组件暴露的服务告知builder。

  组件可以通过反射来创建组件(使用特定.net类型,或者开放的泛型进行注册的组件(开放的泛型指没有制定具体泛型参数的泛型),在解析时将通过反射来创建);直接返回现成的实例创建组件(通过提供对象实例进行注册的组件,解析组件时autofac将直接返回该实例);或者通过lambda表达式创建创建组件(注册组件时,通过匿名方法进行注册的组件)。你可以通过ContainerBuilder中一系列重载的Register方法来设置创建组件的方式。

  组件通过ContainerBuilder中的As()方法来暴露一个或者多个服务。

//为了注册组件而创建ContainerBuilder对象
var builder = new ContainerBuilder();

//注册一个类型并将其所实现的接口暴露为服务
builder.RegisterType<ConsoleLogger>().As<ILogger>();

//注册一个你手动实例化的对象
var output = new StringWriter();
builder.RegisterInstance(output).As<TextWriter>();

//注册一个执行创建实例工作的表达式
builder.Register(c => new ConfigReader("mysection")).As<IConfigReader>();

//创建容器来完成注册工作
var container = builder.Builder();

//现在你就可以使用autofac来解析(resolve)服务了。列如
//下面代码将会执行注册IConfigReader服务的lambda表达式
using(var scope = container.BeginLifetimeScope())
{
      var reader = scope.Resolve<IConfigReader>();            
}

2.1.1、反射组件(Reflection Components)

通过类型进行注册

  autofac使用反射进行创建的组件,通常是通过类型进行注册的:

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

  在使用基于反射进行实例化的组件时,Autofac将自动调用你注册的类中的构造函数,当然这个构造函数中的参数都必须是已经注册到容器当中的类型。

  比如说,有如下的一个类,类中有三个构造函数:

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.Builder();

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

  当解析(resolve)组件时,Autofac会发现ILogger已经被注册到容器中,但是IConfigReader并未注册。在这种情况下,第二个构造函数将被调用,该构造函数所需的参数与我们注册到容器中的组件最为匹配。

  对于通过反射进行创建的组件,有一个需要注意的地方:任何通过RegisterType方法注册的组件必须是一个具体类型(concrete type)。虽然可以将一个抽象类或者接口暴露为服务,但却不能将抽象类或者接口注册为组件。这种限制背后的原因是:Autofac将实例化你所注册的组件,.net中不能去new一个抽象类或接口,new的必须是抽象类或者接口的实现!

指定构造函数

  通过UsingConstructor方法以及提供表示构造函数中的参数类型的列表,你可以指定一个特定的构造函数来取代Autofac自行匹配的构造函数,比如:

builder.RegisterType<MyComponent>().UsingConstructor(typeof(ILogger), typeof(IConfigReader));

  注意,现在仍然需要向容器提供所选构造函数必须的的参数,否则在解析(Resolve)组件时会产生一个错误。你可以在注册时传递参数,也可以在解析(Resolve)时提供参数。

2.1.2、实例组件(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能够轻松的处理。它(单例模式的实例)可以作为一个实例注册到容器中,而不是直接将容器中的组件与该单例模式的实例进行绑定。(限于英文水平这段是我自己理解出来的,如果按原文来译的话我实在没法通顺的将其译出来,原文如下:

Registering provided instances is also handy when integrating Autofac into an existing application where a singleton instance already exists and needs to be used by components in the container. Rather than tying those components directly to the singleton, it can be registered with the container as an instance:)

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

  容器将会取代单例模式对这个实例进行管理。

  一个实例默认暴露的服务是其具体实例的类型,参阅下面的“服务 VS 组件”。

2.1.3、Lambda 表达式组件(Lambda Expression Components)

  反射是一个不错的创建组件的默认选择,不过当创建组件的逻辑的复杂程度超越对构造函数的调用时,事情变得糟糕了。

  还好Autofac可以接受委托或lambda表达式来创建组件:

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

  表达式中的参数c是一个组件上下文(component context)(一个IComponentContext对象),可以通过它来解析(Resolve)容器中的其他值来协助创建组件,重要的是采用这种方式而不是闭包访问容器的方式来处理,以便一致性配置(deterministic disposal)和嵌套容器可以被正确支持。

  使用此上下文参数可以满足额外的依赖项——在本示例中,构造函数需要一个B类型附加依赖项。

  默认的基于表达式的组件暴露的服务的方式是推断表达式的返回类型。

  下面的这些例子通过反射组件很难处理,但是表达式组件可以轻松的完成。

复杂参数(Complex Parameters)

  不能总是用简单的常量值声明的构造函数参数。比起苦思如何构建一个使用XML配置语法的特定类型的值,可以考虑如下代码:

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

  (当然,会话过期时间通常情况下将会在配置文件中指定的,但在这里可以不去考虑这些而是抓住真正问题的实质)

属性注入(Property Injection)

  虽然Autofac提供更多一流的属性注入方法,但你也可以使用表达式和属性初始值设定项来填充属性:
builder.Register(c => new A(){ MyB = c.ResolveOptional<B>() });

  ResolveOptional方法会试图解析(Resolve)B类型的实例,但是当B类型没有被注册到容器中时它不会抛出异常,这就是解析服务(resolve service)时的选项之一。

大多数情况下不建议使用属性注入。作为替代可以使用the Null Object pattern,通过重载构造函数或者构造函数参数使我们能够创建更整洁的使用可选构造函数注入依赖关系的组件。

通过实际提供的参数值来选择实例化的组件(Selection of an Implementation by Parameter Value,此处翻译是我自己脑补的结果)

  对于独立的组件来说,一个好处是其具体的实现类型是可以多样的,这个好处通常不仅体现在配置时更体现在运行时:

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

  在示例中CreditCard有两种类型的实现:金卡(GoldCard)以及标准卡(Standard),具体实例化哪个类型,由运行时提供的accountId参数来决定,当accountId以“9”开头是实例化的是GoldCard,否则示例化的是StandardCard。在本例中,具体执行对象创建工作的代码,创建对象时所依赖的参数是由表达式参数中第二个可选参数p提供的。可按如下方式使用此种类型的组件:

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

  可以使用委托工厂(delegate factory,之后会有详述)对本例进行优化,从而以更加整洁、类型安全的方式完成CreditCard对象的创建。

2.1.4、开放式泛型组件(Open Generic Components)

  Autofac通过ContainerBuilder中的RegisterGeneric()方法,支持开放式泛型类型:

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

  当从容器中匹配服务类型时,Autofac将该组件映射到一个相当接近的实现类型的版本:

//autofac将会返回一个NHibernateRepository<Task>
var tasks = container.Resolve<IRepository<Task>>();

注册一个专门的服务类型(e.g. IRepository<Person>)将会覆盖泛型版本。

2.1.5、服务 VS 组件(Service VS Component)

  当注册一个组件时,需要告诉Autofac该组件暴露了什么样的服务。默认情况下,大多数的注册将会暴露它们本身的类型,比如:

//它暴露的服务类型就是其本身“CallLogger"
builder.RegisterType<CallLogger>();

  组件只能以它暴露的服务类型进行解析(resolve),比如:

//以下代码将成功运行,因为它是以默认的方式暴露的服务
scope.Resolve<CallLogger>();

//以下代码无法执行,因为在注册组件时我们并没有指定该组件暴露的服务是CallLogger类实现的接口ILogger
scope.Resolve<ILogger>()

  你可以将一个组件暴露为若干的服务:

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

  一旦你将组件暴露为某个服务,你就可以通过该服务对组件进行解析(resolve)。但请注意,一旦你将某个组件暴露为一个指定的服务,那将会覆盖其默认的行为,例如:

// 以下代码将成功执行,因为我们在注册时为其暴露了指定的服务
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>();

2.1.6、默认注册(Default Registrations)

  如果多个组件暴露为同一个服务,Autofac将使用最后注册组件作为服务的默认提供程序:

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

  这个场景下,FileLogger将会成为默认的服务提供程序,仅仅因为它是最后进行注册的。

  可以使用PreserveExistingDefaults()方法改写这种默认的行为:

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

  在此场景下ConsoleLogger将成为默认的服务提供程序。

2.1.7、通过配置进行注册(Configuration of Registrations)

  可以使用XML或编程配置的方式共同提供一组注册或在运行时更改注册。还可以使用Autofac模块(use Autofac modules)进行动态注册或登记注册条件逻辑。

2.1.8、以动态的方式提供注册(Dynamically-Provided Registrations)

  ......

2.2、将参数传递给注册(Passing Parameters to Register)

  注册组件时可以传递一组参数,当注册的组件被作为服务解析时将会使用这组参数(如果你不想在resolve组件时提供参数,你可以这么做)

2.2.1、可用的参数类型

Autofac提供了几种不同的参数匹配策略:
  1、NamedParameter—使用名称匹配目标参数。
  2、TypedParameter—使用类型匹配目标参数(必须是精确的类型)。
  3、ResolvedParameter—灵活的参数匹配。
NamedParameter、TypedParameter只能匹配常量值。
ResolvedParameter可以被用来从容器中动态的检索值,比如使用名称来获得服务。
2.2.2、参数与反射组件(Parameters with Reflection Components)
  当你注册基于反射创建的组件时,组件构造函数所需的参数可能无法从容器获得。这种情况可以在注册时为组件的构造函数提供相应的参数。
  比如,有一个配置读取器,构造函数需要传入一个代表配置节点的字符创参数:
public class ConfigReader : IConfigReader
{
    public ConfigReader(string configSectionName)
    {
        // 保存配置节名称
    }
        // ...通过配置节名称读取配置内容.
}

  可以使用lambda表达式注册组件的方式:

builder.Register(c => new ConfigReader("sectionName")).As<IConfigReader>();

  或者可以在注册的时候向反射组件传递参数:

//使用命名参数
builder.RegisterType<ConfigReader>().As<IConfigReader>().WithParameter("configSectionName", "sectionName");

//使用类型参数
builder.RegisterType<ConfigReader>().As<IConfigReader>().WithParameter(new TypedParameter(Typeof(string), "sectionName"));

//使用RESOLVED parameter
builder.RegisterType<ConfigReader>()
.As<IConfigReader>()
.WithParameter(new ResolveParameter(pi, ctx) =>
  pi.ParameterType == typeof(string) && pi.Name == "configSectionName",
  (pi, cxt) => "sectionName"));

2.2.3、参数与lambda表达式组件(Parameters with Lambda Expression Components)

  使用lambda表达式组件注册,比起在注册时传递参数,你可以在解析(resolve)服务是传递参数。

  组件注册表达式中,可以使用传入参数改变你使用注册的委托签名,同时使用IComponentContext以及IEnumerable<Parameter>取代只是用IComponentContext的注册方式:

// 在注册时使用两个参数:
// c = 当前IComponentContext动态解析依赖项
// p = 使用IEnumerable<Parameter>类型包装的传入参数
builder.Register((c, p) =>
new ConfigReader(p.Named<string>("configSectionName")))
.As<IConfigReader>();
  在进行服务解析(resolve)时以如下方式传入lambda表达式:
当前IComponentContext动态解析依赖项

2.3、属性注入和方法注入(Property and Method Injection)

  构造函数注入,是推荐的为组件提供参数的方式,但是依然可以使用属性注入和方法注入来提供依赖项。

  属性注入使用可写属性,而不是构造函数参数来执行注射,方法注入则是通过调用方法来传入依赖项。

2.3.1、属性注入(Property Injection)

  如果组件是一个lambda表达式组件,使用对象来进行初始化:

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

  为支持循环依赖,使用激活事件处理程序(activated event handler)

builder.Register(c => new A()).OnActivated(e => e.Instance.B = e.Context.Resolve<B>());

  如果组件是一个反射组件,使用PropertiesAutowired()方法将注册方式改变为属性注入:

builder.RegisterType<A>().PropertiesAutowired();

  如果需要注入一个特定的依赖项,可以采用WithProperty()方法:

builder.RegisterType<A>().WithProperty("PropertyName", propertyValue);

2.3.2、方法注入(Method Injection)

    方法注入中最简单的为组件注入依赖的方式是使用lambda表达式组件,并直接调用相应的注入方法:

builder.Register(c => {
var result = new MyObjectType();
var dep = c.Resolve<TheDependency>();
result.SetTheDependency(dep);
return result;
});

  如果不能使用注册表达式,你可以添加一个激活事件处理句柄(activating event handler):

builder
.Register<MyObjectType>()
.OnActivating(e => {
var dep = e.Context.Resolve<TheDependency>();
e.Instance.SetTheDependency(dep);
});

2.4、扫描程序集(Assembly Scanning)

  Autofac可以使用约定来查找并注册程序集中的组件。可以扫描并注册单独的类型,或者为扫描注册特定的模块。

2.4.1、扫描类型(Scanning for Types)

  换个描述就是基于约定的扫描或者注册,Autofac可以从程序集中按照用户指定的规则注册一组类型:

var dataAccess = Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(dataAccess)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces();

  每一个对RegisterAssemblyTypes()方法的调用都可以支持唯一组规则,如果有几组具有不同规则的组件需要注册,那么可以多次调用RegisterAssemblyTypes()方法。

类型过滤(Filtering Types)

  RegisterAssemblyTypes()方法的参数接受一个数组,该数组包涵一个或者多个程序集。默认情况下,所有公有(public)、具有具体类型的类都将被注册。可以采用过滤器对这些类型进行过滤。

  为了过滤这些不需要的类,可以使用Where()断言(predicate):

builder.RegisterAssemblyTypes(asm)
.Where(t => t.Name.EndsWith("Repository"));

  要将某个类型从扫描类型中排出,使用Except()断言(predicate):

builder.RegisterAssemblyTypes(asm)
.Except<MyUnwantedType>();

  Except()断言同样允许用户自定义要排除的类型(这句感觉不太对,原文:The Except() predicate also allows you to customize the registration for the specific excluded type):

builder.RegisterAssemblyTypes(asm)
.Except<MyCustomisedType>(ct =>
ct.As<ISpecial>().SingleInstance());

可以使用多个筛选器,使用逻辑运算符连接。

指定服务(Specifying Services)

  RegisterAssemblyTypes()所使用的组件注册语法是针对单个类型进行组件注册语法的超集,所以类似As()这一类型的方法仍然适用。

builder.RegisterAssemblyTypes(asm)
.Where(t => t.Name.EndsWith("Repository"))
.As<IRepository>();

  As()以及Named()额外的重载能够接收一个lambda表达式作为参数,以此推断一个类型提供了什么样的服务:

builder.RegisterAssemblyTypes(asm).As(t => t.GetInterfaces()[0]);

  和常规的组件注册一样,扫码程序集依然可以使用多个As()方法来指定组件提供的多个服务。

  一些额外的注册方法,让它更容易建立通用的约定:

方法描述实例
AsImplementedInterfaces()注册类型,并将类型实现的所有公共接口作为服务(除IDisposable接口外)

builder.RegisterAssemblyTypes(asm)

.Where(t =>

.Name.EndsWith("Repository")

.AsImplementedInterfaces();

AsImplementedInterfaces()

注册类型,该类将作为其所实现的开发泛型的闭合实例

Register types that are assignable to a closed

instance of the open generic type.)

builder.RegisterAssemblyTypes(asm)

.AsClosedTypesOf(typeof(IRepository<>));

AsSelf()将类型本身作为服务进行注册

builder.RegisterAssemblyTypes(asm)

.AsImplementedInterfaces()

.AsSelf();

 

2.4.2、扫描模块(Scanning for Modules)

  使用RegisterAssemblyModules()组件注册方法来进行模块注册。该方法扫码所提供程序集中的Autofac Module,之后创建该Module的实例并调用Module中的Load方法将组件注册到当前的Container Builder中。

  例如,下面两个的Module类,存在于同一个程序集当中,每一个Module类都注册了一个简单组件:

public class AModule : Module
{
  protected override void Load(ContainerBuilder builder)
  {
    builder.Register(c => new AComponent()).As<AComponent>();  
  }
}

public class BModule : Module
{
  protected override void Load(ContainerBuilder builder)
  {
    builder.Register(c => new BComponent()).As<BComponent>();  
  }
}
 

  不接受任何类型参数的RegisterAssemblyModules()重载方法,它将对所提供程序集中所有实现IModule接口的类型进行注册,下面的列子中两个Module都将被注册:

var asm = typeof(AComponent).Assembly;
var builder = new ContainerBuilder();
//AModule、BModule都将被注册
builder.RegisterAssemblyModules(asm);

 

  提供一个泛型参数的RegisterAssemblyModules()重载方法,它允许用户指定一个基类,只有该基类或者继承该基类的Module才会被注册到Container中:

var asm = typeof(AComponent).Assembly();
var builder = new ContainBuilder();
//只有AModule会被注册
builder.RegisterAssemblyModules<AModule>(asm);

  提供一个Type类型作为参数的RegisterAssemblyModules()重载方法与提供泛型参数的重载方法类似,该参数同样允许你提供一个基类类型,但该类型直到运行时才会被检测:

 

var assembly = typeof(AComponent).Assembly;
var builder = new ContainerBuilder();
// 对AModule进行了注册,但没有注册BModule
builder.RegisterAssemblyModules(typeof(AModule), assembly);

2.4.3、IIS托管Web应用程序(IIS Hosted Web Applications)

  在IIS托管的应用程序中使用程序集扫描时,你可能会遇到如何定位程序集的问题。

  当使用IIS托管应用程序是,所有程序集都将在应用程序第一次启动时被加载到应用程序域中,但当应用程序域被IIS回收再利用时,应用程序集只有当被使用时才会被加载。为避免该问题可以使用System.Web.Compilation.BuildManager.GetReferencedAssemblies()来获取一个相关应用程序集的列表。(这一段,我也不是十分理解,希望对IIS应用程序域回收机制比较熟悉的朋友能够出来知道一下。以下是原文:When hosting applications in IIS all assemblies are loaded into the AppDomain when the application first starts, but when the AppDomain is recycled by IIS the assemblies are then only loaded on demand.)

var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>();

  该操作将迫使被引用的应用程序集立刻加载到应用程序域中,从而使得程序集扫描能够正常执行。

 

PS:本系列博客是对autofac英文资料的翻译,主要目的是为了提高自己英文阅读能力,同时能够帮助有需要的人,原文地址http://autofac.readthedocs.org/en/latest/getting-started/index.html。

转载于:https://www.cnblogs.com/Colorful-MrC/p/autofac_doc2.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值