Autofac Multi Tenant覆盖和IEnumerable <t>注入/解析

时间:2018-02-14 19:00:44

标签: autofac multi-tenant

请原谅我的非母语英语:

简而言之,租户覆盖默认IEnumerable<T>注册的最佳方式是什么?

TL; DR所以我有一个服务ServiceToBeResove(IEnumerable<IShitty> svcs)需要一个IEnumerable<IShitty>依赖项,但我们发现并非所有租户都有注册为IShitty的服务,所以在我们的应用程序容器中我们创建了一个没有实现NoImplementShitty并将其作为IShitty的TypeService注册到服务器作为默认设置以使解决过程满意,如果租户已注册,我们确实获得特定于租户的权限,如果租户忘记,则默认未实现注册。但我们很快发现ServiceToBeResove将为两个租户实现注册IShitty,并且默认NoImplementShitty因为它依赖于IEnumerable。我真正想要的IEnumerable<IShitty>依赖项仅用于租户注册(注册1个或更多),如果租户未注册,只需使用默认NoImplementShitty作为IEnumerable<IShitty>。我在应用容器上使用了.OnlyIf()OnlyIfRegistered().PreventDefault(),但实际上没有帮助,因为autofac会首先构建默认值,然后是租户。我当然可以将NoImplementShitty用于错过IShitty注册的所有租户,但它似乎没有利用多租户的覆盖默认功能。

更具体地说,在我们的基地AgreementModule中,我们有

builder.RegisterType<NoOpAgreementHandler>() //NoOpAgreementHandler is the IShitty
    .As<IAgreementHandler>()
    .InstancePerLifetimeScope();

在我们的租户A中,我们有

public class TenantAContainerBuilder : ITenantContainerBuilder
{
    public virtual object TenantId => "1";
    public virtual void Build(ContainerBuilder builder)
    {
        builder.RegisterType<TenantAAgreementHandler>()
            .As<IAgreementHandler>()
            .InstancePerLifetimeScope();
    }
}

我们按如下方式构建容器:

var appContainer = builder.Build();
var tenantIdentifier = new ManualTenantIdentificationStrategy(); //We have our own strategy here I just use the ManualTenantIdentificationStrategy for example
var multiTenantContainer = new MultitenantContainer(tenantIdentifier, appContainer);
//GetTenantContainerBuilders will basically give you all TenantBuilder like TenantAContainerBuilder above
foreach (IGrouping<object, ITenantContainerBuilder> source in GetTenantContainerBuilders().GroupBy(x => x.TenantId))
{
    var configurationActionBuilder = new ConfigurationActionBuilder();
    configurationActionBuilder.AddRange(source.Select(x => new Action<ContainerBuilder>(x.Build)));
    multiTenantContainer.ConfigureTenant(source.Key, configurationActionBuilder.Build());
}

尝试解析服务时,如果我们这样做:

public DisbursementAgreementManager(IEnumerable<IAgreementHandler> agreementHandlers)
{
    _agreementHandlers = agreementHandlers;
}

agreementHandlers将是NoOpAgreementHandlerTenantAAgreementHandler的IEnumerable,看起来很奇怪NoOpAgreementHandler我认为我们只会获得TenantAAgreementHandler。但是,如果我们将DisbursementAgreementManager更改为

public DisbursementAgreementManager(IAgreementHandler agreementHandler)
{
    _agreementHandler = agreementHandler;
}

我们只会获得预期的TenantAAgreementHandler

1 个答案:

答案 0 :(得分:1)

Autofac的默认行为是有原因的。要求它以不同的方式执行它将在依赖注入级别添加应用程序逻辑,这违反了关注点的分离(DI应该只注入依赖关系)并直接导致令人惊讶的行为(“为什么DI不注入每个可用的组件?”)并削弱了系统的可维护性。

这可能不是问题。

  

逻辑在每个IAgreementHandler内都是自包含的。

如果是这样,在DisbursementAgreementManager调用它们时,它们都被调用,然后执行它们自己的逻辑(可能包括决定是否全部做任何事情)。 E.g:

foreach (var ah in _agreementHandlers) ah.Agree(disbursementInfo);

或者类似

之类的东西
foreach (var ah in _agreementHandlers.Where(a => a.ShouldRun(data) || overridingCondition)) 
{
    var agreement = ah.Agree(info);
    this.Process(agreement);
}

或其他什么。关键是如果NoOpAgreementHandler正在做它应该做的事情(也就是什么都没有)那么它在被调用时应该没有效果。没问题。

如果情况不同于描述,那么NoOpAgreementHandler和可能IAgreementHandler需要重构。

还有另一个值得关注的问题:

  

我们添加no-op的原因是我们有注册/解析的单元测试,以确保所有注册都已正确配置。

您的测试要求正在渗透到您的主要逻辑中。这些DI配置测试应独立于生产DI配置。 NoOpAgreementHandler甚至不应该在您的主要项目中,只是单元测试项目的成员。