解决通过Autofac在不同子范围中单独注册的单例组件

时间:2012-03-29 15:37:13

标签: autofac multi-tenant

我在解决Autofac中的单件组件时遇到问题,并想知道Autofac是否表现正常或者这是否是真正的错误。

我理解Autofac工作的方式是,当您注册单个组件时,该注册被“拉”到根容器,以便解析该组件的任何内容都将解析同一个实例。

所以,我想知道在单独的子范围中注册单例组件时是否应该使用相同的机制。我会让代码说明一切:

public class ChildSingletonScopeTest
{
    readonly IContainer container;
    readonly ILifetimeScope scope1, scope2, childScope1;

    public ChildSingletonScopeTest()
    {
        var builder = new ContainerBuilder();
        container = builder.Build();

        scope1 = container.BeginLifetimeScope(ConfigureTenant);
        scope2 = container.BeginLifetimeScope(ConfigureTenant);

        childScope1 = scope1.BeginLifetimeScope();
    }

    void ConfigureTenant(ContainerBuilder builder)
    {
        builder.RegisterType<Testing>().As<ITesting>().SingleInstance();
    }

    [Xunit.Fact] /* this test fails */
    public void should_resolve_single_instance_for_each_tenant_scope()
    {
        var instance1 = scope1.Resolve<ITesting>();
        var instance2 = scope2.Resolve<ITesting>();

        Xunit.Assert.Equal(instance1, instance2);
    }

    [Xunit.Fact] /* this test passes */
    public void should_resolve_single_instance_down_child_scope_hierarchy()
    {
        var instance1 = scope1.Resolve<ITesting>();
        var instance2 = childScope1.Resolve<ITesting>();

        Xunit.Assert.Equal(instance1, instance2);
    }
}

interface ITesting { }

class Testing : ITesting
{
    public override string ToString()
    {
        return GetHashCode().ToString();
    }
}

我试图做这样疯狂的事情的原因归结为我们在我们的网络应用程序中实现多租户的方式。如果没有太多陷入细节,我们就要求能够在Web请求中以编程方式“切换”租户。

我们这样做的方法是将租户范围嵌套在每个一次性请求范围之下。但是,特定于租户的单一注册不会针对不同请求解析同一租户的相同实例。

所以,我想知道这是否是Autofac处理这些单例注册的方式中的错误,或者我是否需要更改我在租户范围内处理单例组件的方式。

1 个答案:

答案 0 :(得分:2)

来自the Autofac wiki page on instance scopes ...

  

使用单实例范围,从父级和所有嵌套容器中的所有请求返回一个实例。

这意味着如果你有这样的嵌套范围......

container
  scope-1
    scope-2

然后,如果你在“scope-1”中注册一个单身,那么当你在“scope-2”中解决它时,你会得到相同的单身。另一方面,如果你试图在容器中解决它,你将不会得到任何东西,因为它没有在那里注册 - 注册“继承”树,而不是向上,因为你的测试证明了< /强>

有一个很好的primer on Autofac lifetime scopes and component resolution on Nick Blumhardt's blog。您可能会看到一个令人困惑的事情,即如果注册不在当前范围内,则会提到如何解析单例范围的组件。

假设您在“scope-1”中注册了一个单例,并且该单例具有依赖关系。此外,假设依赖性在“范围-1”和“范围-2”中都被注册。现在您从“scope-2”解析单例。由于单例在“范围-1”中注册,它将从“范围-1”解析其依赖关系而不是在“范围-2”中使用覆盖 - 因为一旦“范围-2”结束,单例仍然必须坚持在“范围-1”中供他人使用。

在您的方案的其他方面,您也可能最终遇到“最后赢”的事情。如果在“容器”中注册一个单例,然后在“范围-1”中注册另一个单例,当您从“scope-2”解析时,您将获得在“scope-1”中注册的那个,而不是从根目录中注册的那个。但是,您也应该能够解决所有问题,例如......

scope.Resolve<IEnumerable<IMySingleton>>();

如果你解决了IEnumerable&lt; T&gt;对于您的组件,您将获得所有已注册的组件,而不仅仅是您注册的最后一个组件。

我认为这是一个漫长的说法它不是一个错误,这就是Autofac的工作方式

如果您正在使用Autofac进行多租户,您是否看过AutofacContrib.Multitenant ?它是贡献的图书馆之一,对基于租户的单身人士等事物提供原生支持。 There is a wiki page on Autofac for that, too.您可以配置应用程序级默认值,然后配置特定于租户的替代值。你也可以做像......这样的事情。

containerBuilder.RegisterType<MyType>().As<IMyType>().InstancePerTenant();

...这使得租户单身人士的根级注册非常简单。

Autofac中没有用于contrib库的NuGet包(我们正在处理它)所以你必须去网站并手动下载,但它可能有助于解决你的问题。

[完全披露:我写了AutofacContrib.Multitenant。]