【问题标题】:Pass different instances based on constructor parametername using AutoFac使用 AutoFac 根据构造函数参数名传递不同的实例
【发布时间】:2017-10-27 08:49:23
【问题描述】:

我有一个这样的类(WebApi 2 Controller):

class DatabaseAnalysisController (IDatabase databaseOne, 
    IDatabase databaseTwo) : ApiController

还有这样的课程:

class Database : IDatabase

现在我有两个实例:

Database databaseOne = new Database("one");
Database databaseTwo = new Database("two");

我不希望DatabaseAnalysis 对 Autofac 有任何依赖。但是我想使用Autofac根据构造函数的参数名称进行注入。

我正在使用Autofac.WebApi 注册我的控制器。

builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

但在那之后就不行了,因为databaseOnedatabaseTwo都是IDatabase

builder.RegisterInstance(new Database("one")).As<IDatabase>();

Autofac 是否可以将databaseOne 注入DatabaseAnalysis 的构造函数的databaseOne(也可以将databaseTwo 注入databaseTwo)?

如果是这样,怎么办? :P

【问题讨论】:

  • builder.Register&lt;DatabaseAnalysisController&gt;(c =&gt; new DatabaseAnalysisController(new Database("one"), new Database("two"))).InstancePerLifetimeScope(); 工作吗?
  • 那行得通。但我想让它纯粹在 parameterNames 上工作,而不必在注册中使用 DatabaseAnalysisController。
  • @Evk 的解决方案是否符合要求?
  • 确保您没有违反Liskov Substitution Principle。一个很好的检查是问自己当您交换数据库参数时会发生什么(因此将 db2 注入 databaseOne 参数,反之亦然)。如果这破坏了您的控制器,那么您就违反了 LSP。在这种情况下,每个数据库都应该获得自己的抽象。
  • @Steven,你基本上是说我应该去找 NightOwl888 的答案 ;-)

标签: c# dependency-injection inversion-of-control autofac


【解决方案1】:

由于您有多个具有相同接口和类型的实例 - 您需要为它们注册一个名称:

builder.RegisterInstance(new Database("one")).Named<IDatabase>("databaseOne");
builder.RegisterInstance(new Database("two")).Named<IDatabase>("databaseTwo");

然后你可以像这样注册你的DatabaseAnalysisController

builder.RegisterType<DatabaseAnalysis>().WithParameter(
    (pi, ctx) => pi.ParameterType == typeof(IDatabase), 
    (pi, ctx) => ctx.ResolveNamed<IDatabase>(pi.Name));

您可以制作一个扩展方法来执行这样的注册:

public static class AutofacExtensions {
    public static IRegistrationBuilder<TLimit, TReflectionActivatorData, TStyle> WithNamedParameter<TLimit, TReflectionActivatorData, TStyle>(this IRegistrationBuilder<TLimit, TReflectionActivatorData, TStyle> registration, Type targetType)
        where TReflectionActivatorData : ReflectionActivatorData {
        return registration.WithParameter(
            (pi, ctx) => pi.ParameterType == targetType,
            (pi, ctx) => ctx.ResolveNamed(pi.Name, targetType));
    }
}

要对所有控制器执行此操作,请执行以下操作:

builder.RegisterApiControllers().WithNamedParameter(typeof(IDatabase));

【讨论】:

  • 我想让它在它被注入的任何类上工作。所以我不想在注册期间命名该类。我想让它只对目标参数名称起作用。
  • @frankhommers 然后尝试执行RegisterApiControllers().WithParameter(...) 其中WithParameter 您可以从我的答案中的第二个代码块中获取。我现在无法测试它是否会起作用,但你可以试一试。当然,在此之前将两个数据库注册为上面的命名实例。
  • 你用RegisterApiControllers注册了api控制器,所以你也注册了其他类。我猜也只需将WithParameter 添加到他们的注册中即可。你可以用它做一个扩展方法('WithParametersOfType`类似的东西)。
  • 谢谢。但我看到这种方法有一个缺点。我需要在每次注册时调用此分机。因此,当我将 IDatabase databaseOne 参数添加到另一个现有类时,它不会自动工作。
【解决方案2】:

除了使注入的依赖项依赖于构造函数参数名称之外,另一种处理这些依赖项的方法就是使接口成为通用的。然后通用关闭类型 (T) 将确定注入哪个 IDatabase&lt;T&gt;

interface IDatabase<T> {}
class Database1 : IDatabase<Database1>
class Database2 : IDatabase<Database2>

在您的服务中:

class SomeService1
{
    public SomeService1(IDatabase<Database1> database)
    {
        // set database to private variable
    }
}

class SomeService2
{
    public SomeService2(IDatabase<Database2> database)
    {
        // set database to private variable
    }
}

可以这样注册:

builder.RegisterType<IDatabase<Database1>>().As<Database1>();
builder.RegisterType<IDatabase<Database2>>().As<Database2>();

虽然IDatabase&lt;T&gt; 在实现之间共享,但.NET 类型系统(以及因此Autofac)将IDatabase&lt;Database1&gt; 视为与IDatabase&lt;Database2&gt; 完全不同的类型。这意味着类型会自动放置到正确的位置,但这也意味着您不能在应用程序中将一个替换为另一个。因此,这是否适合您取决于您​​的具体用例。

【讨论】:

  • 但是使用接口没有多大意义——你也可以直接注入Database1Database2类。
  • 这是一种方法,但这基本上是定义另一种类型而不是按名称注入。不是我的目标......
  • @Evk - 如果直接注入具体类型,模拟它进行测试的唯一方法就是继承它。不像这样灵活的解决方案。
猜你喜欢
  • 2020-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多