Registration Concepts With Autofac

本文详细介绍了Autofac中组件的注册方式,包括通过反射、实例、lambda表达式创建组件,以及注册开放泛型组件。同时阐述了如何指定构造函数、控制组件生命周期,并讨论了服务与组件的关系,以及条件注册和动态注册的概念。文章还提到了注册源和配置注册在实现更复杂逻辑中的应用。
摘要由CSDN通过智能技术生成

Registration Concepts

注册概念

You register components with Autofac by creating a ContainerBuilder and informing the builder which components expose which services.

通过Autofac注册组件创建一个ContainerBuilder并且通知生成器哪些组件公开哪些服务。

Components can be created via reflection (by registering a specific .NET type or open generic); by providing a ready-made instance (an instance of an object you created); or via lambda expression (an anonymous function that executes to instantiate your object). ContainerBuilder has a family of Register() methods that allow you to set these up.

组件可以通过反射来创建(通过注册一个特定的组件.NET类型或打开通用型);通过提供现成实例(创建对象的实例);或者通过lambda表达式(执行实例化对象的匿名函数)。ContainerBuilder有一系列的Register()方法,允许您设置这些方法。

Each component exposes one or more services that are wired up using the As() methods on ContainerBuilder.

每个组件公开使用ContainerBuilder上的As()方法连接的一个或多个服务。

// 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 = scope.Resolve<IConfigReader>();
}

Reflection Components

反射组件

Register by Type

按类型进行注册

Components generated by reflection are typically registered by type:

由反射生成的组件通常按以下类型进行注册:

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

When using reflection-based components, Autofac automatically uses the constructor for your class with the most parameters that are able to be obtained from the container.

当使用基于反射的组件时,Autofac会自动使用类中具有能够从容器中获得的最多参数的构造函数。

For example, say you have a class with three constructors like this:

例如,假设您有一个具有这样三个构造函数的类:

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

Now say you register components and services in your container like this:

现在假设在容器中注册组件和服务:

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

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

When you resolve your component, Autofac will see that you have an ILogger registered, but you don’t have an IConfigReader registered. In that case, the second constructor will be chosen since that’s the one with the most parameters that can be found in the container.

解析组件时,Autofac将看到您已注册了ILogger,但没有注册了IConfigReader。在这种情况下,将选择第二个构造函数,因为它是在容器中可以找到的参数最多的构造函数。

An important note on reflection-based components: Any component type you register via RegisterType must be a concrete type. While components can expose abstract classes or interfaces as services, you can’t register an abstract/interface component. It makes sense if you think about it: behind the scenes, Autofac is creating an instance of the thing you’re registering. You can’t “new up” an abstract class or an interface. You have to have an implementation, right?

关于基于反射的组件的重要注意事项:通过注册器类型注册的任何组件类型都必须是具体类型。虽然组件可以将抽象类或接口公开为服务,但您无法注册抽象/接口组件。如果您仔细想想,这一点很有意义:在幕后,Autofac正在创建一个您正在注册的内容的实例。您不能“更新”一个抽象类或一个接口。你必须有一个实现,对吧?

Specifying a Constructor

指定一个构造函数

You can manually choose a particular constructor to use and override the automatic choice by registering your component with the UsingConstructor method and a list of types representing the parameter types in the constructor:

通过使用UsingConstructor方法和表示构造函数中参数类型的类型列表,可以手动选择要使用的特定构造函数并覆盖自动选择:

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

Note that you will still need to have the requisite parameters available at resolution time or there will be an error when you try to resolve the object. You can pass parameters at registration time or you can pass them at resolve time.

请注意,在解析时仍需要提供必要的参数,否则在尝试解析对象时会出现错误。您可以在注册时传递参数,或者也可以在解析时传递这些参数。

Instance Components

实例组件

In some cases, you may want to pre-generate an instance of an object and add it to the container for use by registered components. You can do this using the RegisterInstance method:

在某些情况下,可能需要预生成对象实例并将其添加到容器中供注册组件使用。可以使用RegisterInstance 方法:

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

Something to consider when you do this is that Autofac automatically handles disposal of registered components and you may want to control the lifetime yourself rather than having Autofac call Dispose on your object for you. In that case, you need to register the instance with the ExternallyOwned method:

当您这样做时,需要考虑的一点是,Autofac会自动处理注册组件的处理,并且您可能希望自己控制生命周期,而不是为您让Autofac对对象调用“处理”。在这种情况下,您需要使用 ExternallyOwned方法注册该实例:

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

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:

在将Autofac集成到现有的应用程序中时,如果单例实例已经存在,并且需要被容器中的组件使用,则注册所提供的实例也很方便。与其将这些组件直接绑定到单例中,还可以将其作为容器注册为实例:

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

This ensures that the static singleton can eventually be eliminated and replaced with a container-managed one.

这确保了静态单例最终可以被消除并替换为容器管理的单例。

The default service exposed by an instance is the concrete type of the instance. See “Services vs. Components,” below.

实例公开的默认服务是该实例的具体类型。请参见下面的“服务与组件”。

Lambda Expression Components

Lambda表达式组件

Reflection is a pretty good default choice for component creation. Things get messy, though, when component creation logic goes beyond a simple constructor call.

反射是创建组件的默认选择。但是,当组件创建逻辑超越简单的构造函数调用时,事情变得混乱。

Autofac can accept a delegate or lambda expression to be used as a component creator:

Autofac可以接受要用作组件创建者的委托或lambda表达式:

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

The parameter c provided to the expression is the component context (an IComponentContext object) in which the component is being created. You can use this to resolve other values from the container to assist in creating your component. It is important to use this rather than a closure to access the container so that deterministic disposal and nested containers can be supported correctly.

提供给该表达式的参数c是正在在其中创建该组件的组件上下文(IComponent上下文对象)。可以使用此方法解析容器中的其他值,以帮助创建组件。使用此操作而不是关闭操作来访问容器很重要,以便能够正确支持确定性处理和嵌套容器。

Additional dependencies can be satisfied using this context parameter - in the example, A requires a constructor parameter of type B that may have additional dependencies.

使用此上下文参数可以满足附加的依赖关系——在示例中,A需要一个可能具有附加依赖项的B类型的构造函数参数。

The default service provided by an expression-created component is the inferred return type of the expression.

由表达式创建的组件提供的默认服务是该表达式的推断返回类型。

Below are some examples of requirements met poorly by reflective component creation but nicely addressed by lambda expressions.

下面是一些反射组件创建满足需求不佳的例子,但lambda表达式很好地解决了需求。

Complex Parameters

复杂的参数

Constructor parameters can’t always be declared with simple constant values. Rather than puzzling over how to construct a value of a certain type using an XML configuration syntax, use code:

构造函数参数不能总是使用简单的常数值进行声明。与其担心如何使用XML配置语法构造某一类型的值,不如使用代码:

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

(Of course, session expiry is probably something you’d want to specify in a configuration file - but you get the gist ;))

(当然,会话过期可能是您要在配置文件中指定的内容,但您可以得到要点;)

Property Injection

属性注入

While Autofac offers a more first-class approach to property injection, you can use expressions and property initializers to populate properties as well:

虽然Autofac提供了更一流的属性注入方法,但也可以使用表达式和属性初始化设定项来填充属性:

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

The ResolveOptional method will try to resolve the value but won’t throw an exception if the service isn’t registered. (You will still get an exception if the service is registered but can’t properly be resolved.) This is one of the options for resolving a service.

ResolveOptional方法将尝试解析该值,但如果未注册服务,则不会抛出异常。(如果服务已注册但无法正确解析,您仍会出现异常。)这是解决服务的选项之一。

Property injection is not recommended in the majority of cases. Alternatives like the Null Object pattern, overloaded constructors or constructor parameter default values make it possible to create cleaner, “immutable” components with optional dependencies using constructor injection.

在大多数情况下,不建议注入属性。如空对象模式、重载构造函数或构造函数参数默认值可以使用构造函数注入创建具有可选依赖项的更干净的“不可变”组件。

Selection of an Implementation by Parameter Value

按参数值选择一个实现

One of the great benefits of isolating component creation is that the concrete type can be varied. This is often done at runtime, not just configuration time:

隔离组件创建的一个最大好处是,混合类型可以发生变化。这通常是在运行时完成的,而不仅仅是在配置时:

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

In this example, CreditCard is implemented by two classes, GoldCard and StandardCard - which class is instantiated depends on the account ID provided at runtime.

在这个示例中,信用卡由两个类实现,金卡和备用卡——哪个类的实例化取决于在运行时提供的帐户ID。

Parameters are provided to the creation function through an optional second parameter named p in this example.

参数通过本例中可选的名为p的第二个参数提供给创建函数。

Using this registration would look like:

使用此注册的方式如下:

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

A cleaner, type-safe syntax can be achieved if a delegate to create CreditCard instances is declared and a delegate factory is used.

如果声明了创建 CreditCard 实例的代理并使用了代理工厂,则可以实现更干净、类型安全的语法。

Open Generic Components

打开的通用组件

Autofac supports open generic types. Use the RegisterGeneric() builder method:

Autofac支持开放的泛型类型。使用 RegisterGeneric() 生成器方法:

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

When a matching service type is requested from the container, Autofac will map this to an equivalent closed version of the implementation type:

当从容器请求匹配的服务类型时,Autofac将将其映射到实现类型的等效封闭版本:

// Autofac will return an NHibernateRepository<Task>
var tasks = container.Resolve<IRepository<Task>>();

Registration of a specialized service type (e.g. IRepository<Person>) will override the open generic version.

专业服务类型的注册。(例如 IRepository<Person>)将覆盖打开的通用版本。

You can also use delegates to provide the closed generic type, if you have some custom behavior for choosing the closed generic implementation:

如果您具有选择封闭泛型实现的自定义行为,也可以使用委托提供封闭泛型类型:

var builder = new ContainerBuilder();

builder.RegisterGeneric((context, types, parameters) =>
{
    // Make decisions about what closed type to use.
    if (types.Contains(typeof(string)))
    {
        return new StringSpecializedImplementation();
    }

    return Activator.CreateInstance(typeof(GeneralImplementation<>).MakeGenericType(types));
}).As(typeof(IService<>));

Note

Be aware that the delegate form of RegisterGeneric will usually be slightly less performant than the reflection-based version, because the closed generic type cannot be cached in the same way.

备注:注意

请注意,注册器泛型类型的委托形式通常比基于反射的版本的性能稍低,因为封闭的泛型类型不能以相同的方式缓存。

Services vs. Components

服务对比组件

When you register components, you have to tell Autofac which services that component exposes. By default, most registrations will just expose themselves as the type registered:

注册组件时,必须告诉Autofac组件公开了哪些服务。默认情况下,大多数注册将公开自己为注册的类型:

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

Components can only be resolved by the services they expose. In this simple example it means:

只能通过其公开的服务来解决组件。在这个简单的例子中,它意味着:

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

// This will NOT work because we didn't
// tell the registration to also expose
// the ILogger interface on CallLogger:
scope.Resolve<ILogger>();

You can expose a component with any number of services you like:

您可以公开具有您喜欢的任意数量的服务的组件:

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

Once you expose a service, you can resolve the component based on that service. Note, however, that once you expose a component as a specific service, the default service (the component type) is overridden:

公开服务后,就可以基于该服务解析该组件。但是请注意,一旦将组件公开为特定服务,将覆盖默认服务(组件类型):

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

// This WON'T WORK anymore because we specified
// service overrides on the component:
scope.Resolve<CallLogger>();

If you want to expose a component as a set of services as well as using the default service, use the AsSelf method:

如果要将组件公开为一组服务并使用默认服务,请使用 AsSelf 方法:

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

Now all of these will work:

现在所有这些都可以工作:

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

Default Registrations

默认的注册表

If more than one component exposes the same service, Autofac will use the last registered component as the default provider of that service:

如果多个组件公开同一服务,则Autofac将使用上次注册的组件作为该服务的默认提供程序:

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

In this scenario, FileLogger will be the default for ILogger because it was the last one registered.

在这种情况下,文件日志记录器将是ILogger的默认值,因为它是最后一个注册的一个。

To override this behavior, use the PreserveExistingDefaults() modifier:

要覆盖此行为,请使用PreserveExistingDefaults()修改器:

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

In this scenario, ConsoleLogger will be the default for ILogger because the later registration for FileLogger used PreserveExistingDefaults().

在此情况下,ConsoleLogger 将是 ILogger 的默认值,因为FileLogger 的后期注册使用了PreserveExistingDefaults()

Conditional Registration

有条件的注册

Note

Conditional registration was introduced in Autofac 4.4.0

备注:注意

在Autofac4.4.0中引入了条件注册

In most cases, overriding registrations as noted in the section above, “Default Registrations,” is enough to get the right component resolved at runtime. Ensuring things get registered in the right order; using PreserveExistingDefaults(); and taking advantage of lambda/delegate registrations for more complex conditions and behavior can get you pretty far.

在大多数情况下,覆盖上节“默认注册”所述的注册足以在运行时解决正确的组件。确保事物按正确的顺序注册;使用 PreserveExistingDefaults();利用lambda/委托注册为更复杂的条件和行为可以让你走得很远。

There can be a few scenarios where this may not be the way you want to go:

在一些情况下,这可能不是你想要的方式:

  • You don’t want the component present in the system if something else is handling the functionality. For example, if you resolve an IEnumerable<T> of a service, all of the registered components implementing that service will be returned, whether or not you’ve used PreserveExistingDefaults(). Usually this is fine, but there are some edge cases where you may not want that.
  • You only want to register the component if some other component isn’t registered; or only if some other component is registered. You can’t resolve things out of a container that you’re building, and you shouldn’t update a container that’s already built. Being able to conditionally register a component based on other registrations can be helpful.
  • 如果有其他东西正在处理该功能,则不希望在系统中存在该组件。例如,如果解析服务的IE编号<T>,则将返回使用该服务的所有注册组件,无论是否使用保留存在默认()。通常这很好,但有一些边缘情况下,你可能不想要这样。

  • 仅在未注册其他组件时注册该组件;或者仅在注册其他组件时注册该组件。您无法从正在构建的容器中解决问题,也不应该更新已生成的容器。能够根据其他注册有条件地注册一个组件是很有帮助的。

There are two registration extensions that can help in these cases:

在以下情况下,可以帮助提供两个注册扩展:

  • OnlyIf() - Provide a lambda that uses an IComponentRegistryBuilder to determine if a registration should happen.
  • OnlyIf()-提供一个使用IComponentRegistryBuilder来确定是否应该进行注册。
  • IfNotRegistered() - Shortcut to stop a registration from happening if some other service is already registered.
  • IfNotRegistered()-在其他服务已经注册时停止注册的快捷方式。

These extensions run at the time of ContainerBuilder.Build() and will execute in the order of the actual component registrations. Here are some examples showing how they work:

这些扩展在运行ContainerBuilder.Build(),并将按照实际组件注册的顺序执行。下面是一些说明它们是如何工作的例子:

var builder = new ContainerBuilder();

// Only ServiceA will be registered.
// Note the IfNotRegistered takes the SERVICE TYPE to
// check for (the As<T>), NOT the COMPONENT TYPE
// (the RegisterType<T>).
builder.RegisterType<ServiceA>()
       .As<IService>();
builder.RegisterType<ServiceB>()
       .As<IService>()
       .IfNotRegistered(typeof(IService));

// HandlerA WILL be registered - it's running
// BEFORE HandlerB has a chance to be registered
// so the IfNotRegistered check won't find it.
//
// HandlerC will NOT be registered because it
// runs AFTER HandlerB. Note it can check for
// the type "HandlerB" because HandlerB registered
// AsSelf() not just As<IHandler>(). Again,
// IfNotRegistered can only check for "As"
// types.
builder.RegisterType<HandlerA>()
       .AsSelf()
       .As<IHandler>()
       .IfNotRegistered(typeof(HandlerB));
builder.RegisterType<HandlerB>()
       .AsSelf()
       .As<IHandler>();
builder.RegisterType<HandlerC>()
       .AsSelf()
       .As<IHandler>()
       .IfNotRegistered(typeof(HandlerB));

// Manager will be registered because both an IService
// and HandlerB are registered. The OnlyIf predicate
// can allow a lot more flexibility.
builder.RegisterType<Manager>()
       .As<IManager>()
       .OnlyIf(reg =>
         reg.IsRegistered(new TypedService(typeof(IService))) &&
         reg.IsRegistered(new TypedService(typeof(HandlerB))));

// This is when the conditionals actually run. Again,
// they run in the order the registrations were added
// to the ContainerBuilder.
var container = builder.Build();

Configuration of Registrations

注册表的配置

You can use XML or programmatic configuration (“modules”) to provide groups of registrations together or change registrations at runtime. You can also use use Autofac modules for some dynamic registration generation or conditional registration logic.

您可以使用XML或编程配置(“模块”)一起提供注册组或在运行时更改注册。您还可以使用自动传真模块用于某些动态注册生成或条件注册逻辑。

Dynamically-Provided Registrations

动态方式提供的注册表

Autofac modules are the simplest way to introduce dynamic registration logic or simple cross-cutting features. For example, you can use a module to dynamically attach a log4net logger instance to a service being resolved.

自动传真模块是引入动态配准逻辑或简单的横切功能的最简单的方法。例如,可以使用模块动态地将log4网络日志记录器实例附加到正在解决的服务中。

If you find that you need even more dynamic behavior, such as adding support for a new implicit relationship type, you might want to check out the registration sources section in the advanced concepts area.

如果发现需要更多的动态行为,例如添加对新隐式关系类型的支持,您可能需要查看高级概念区域中的注册源部分。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值