猿教程 Logo

Autofac教程:注册组件

您可以通过创建一个ContainerBuilder来注册Autofac组件,并通知构建器哪些组件公开哪些服务。

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

每个组件公开使用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 = container.Resolve<IConfigReader>();
}



反射组件

按类型注册

通过反射生成的组件通常按类型注册:

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

当使用基于反射的组件时,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.Build();
using(var scope = container.BeginLifetimeScope())
{
  var component = container.Resolve<MyComponent>();
}

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

基于反射的组件的重要注意事项:通过RegisterType注册的任何组件类型必须是具体类型。 虽然组件可以将抽象类或接口暴露为服务,但是您不能注册抽象/接口组件。 如果您考虑到这一点,那么在幕后,Autofac正在创建一个您注册的实例。 你不能“新建”一个抽象类或接口。 你必须有一个实现,对吧?

指定构造函数

您可以通过使用UsingConstructor方法注册组件并在构造函数中表示参数类型的类型列表来手动选择要使用的特定构造函数并覆盖自动选择:

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

请注意,您仍然需要在分辨率时间提供必需的参数,否则当您尝试解析对象时会出现错误。 您可以在注册时传递参数,或者您可以在决定时间传递参数。

实例组件

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

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

当您执行此操作时,需要考虑的事情是Autofac自动处理注册组件的处理,您可能希望自己控制自己的生命周期,而不是自动调用对您的对象的处理。 在这种情况下,您需要使用ExternallyOwned方法注册该实例:

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

将Autofac集成到已存在单例实例的现有应用程序中并且需要由容器中的组件使用时,注册提供的实例也非常方便。 可以将这些组件直接绑定到单例,而不是将它们与容器注册为一个实例:

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

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

实例暴露的默认服务是实例的具体类型。 

Lambda表达组件

反射是组件创建的一个很好的默认选择。 当组件创建逻辑超出一个简单的构造函数调用时,事情会变得凌乱。

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

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

提供给表达式的参数c是其中正在创建组件的组件上下文(IComponentContext对象)。 您可以使用此方法从容器中解析其他值,以帮助创建组件。 使用这个而不是关闭来访问容器是非常重要的,因此可以正确支持确定性的处理和嵌套容器。

可以使用此上下文参数来满足其他依赖关系 - 在示例中,A需要可能具有其他依赖关系的B类构造函数参数。

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

以下是反射组件创建遇到的需求的一些示例,但是由lambda表达式很好地解决了。

复杂参数

构造函数参数不能总是用简单的常量值声明。 而不是困惑如何使用XML配置语法构造某种类型的值,请使用代码:

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

(当然,会话到期可能是你想在一个配置文件中指定的东西,但你可以得到这个要点))

属性注入

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

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

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

在大多数情况下不推荐使用属性注入。 像Null Object模式,重载构造函数或构造函数参数默认值这样的替代项可以使用构造函数注入创建更清晰的“不可变”组件,并使用可选依赖关系。

通过参数值选择实现

隔离组件创建的一个重要优点是具体类型可以变化。 这通常在运行时完成,而不仅仅是配置时间:

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由GoldCard和StandardCard两类实现,该类实例化取决于运行时提供的帐号。

在本示例中,通过可选的第二个参数p将参数提供给创建函数。

使用此注册将如下所示:

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

如果声明创建CreditCard实例的委托并使用委托工厂,则可以实现更清洁,类型安全的语法。

泛型组件

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

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

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

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

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

服务与组件

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

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

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

// 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>();

您可以使用您喜欢的任何数量的服务来显示组件:

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>();
// This WON'T WORK anymore because we specified
// service overrides on the component:
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>();

默认注册

如果多个组件公开相同的服务,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将为ILogger的默认值,因为FileLogger的后续注册使用了PreserveExistingDefaults()。


有条件注册

注意:Autofac 4.4.0引入了条件注册

在大多数情况下,如上述部分所述,“Default Registration”(默认注册)已经足够让运行时得到正确的组件。确保事情按正确的顺序注册;使用PreserveExistingDefaults();并利用lambda /委托注册更复杂的条件和行为可以让你解决相当多的情况。

可能有几种情况,这可能不是你想去的方式:

  • 如果还有其他功能正在处理,您不希望系统中存在组件。例如,如果您解析服务的IEnumerable <T>,则将返回实现该服务的所有注册组件,无论您是否使用了PreserveExistingDefaults()。通常这是很好的,但有一些边缘的情况,你可能不希望那样。

  • 您只想注册组件,如果其他组件未注册;或仅当注册了其他组件时。您无法从正在构建的容器中解决问题,您不应更新已构建的容器。能够有条件地注册基于其他注册的组件可能会有所帮助。

有两种注册扩展可以帮助这些情况:

  • OnlyIf() - 提供一个使用IComponentRegistry来确定是否应该发生注册的lambda。

  • IfNotRegistered() - 如果已经注册了其他一些服务,则停止注册的快捷方式。

这些扩展在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();

注册配置

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

动态提供的注册

Autofac模块是引入动态注册逻辑或简单交叉特征的最简单方式。 例如,您可以使用模块将log4net记录器实例动态附加到要解析的服务。

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



版权声明:本站所有教程均为本站原创或翻译,转载请注明出处,请尊重他人劳动果实。请记住本站地址:www.yuanjiaocheng.net (猿教程) 作者:卿文刚
本文标题: C#环境
本文地址:http://www.yuanjiaocheng.net/Autofac/Registration-Concepts.html