【问题标题】:Need guidance on Autofac custom lifetimescopes vs. multi tenancy需要有关 Autofac 自定义生命周期范围与多租户的指导
【发布时间】:2013-05-28 15:54:29
【问题描述】:

场景:

我需要为 same Web 应用程序(appdomain)中的 same 接口定义提供 不同 接口实现,但要提供给不同的 “范围”

想象一下这样的简单分层 Web 内容结构(如果您不熟悉 SharePoint):

RootWeb (SPSite) (ctx here)
  |______SubWeb1 (SPWeb) (ctx here)
  |______SubWeb2 (SPWeb)
  |______SubWeb3 (SPWeb)
           |_______SubWeb3.1 (SPWeb) (ctx here)
           |_______SubWeb3.2 (SPWeb)

RootWeb、SubWeb1 和 SubWeb3.1 提供上下文。那就是我实现了一个特定于某个层次结构级别的 AppIsolatedContext 类。如果一个级别不提供上下文,它会从父节点继承上下文,依此类推。例如,SubWeb3 将从 RootWeb 继承其上下文。然而,SubWeb3.1 提供了自己的独立上下文。

孤立的上下文只是一个静态的 ConcurrentDictionary。

好的,到目前为止一切都很好。现在关于 Autofac(我是 Autofac 和任何其他 DI 容器的新手——虽然不是 IoC 的原则)......我不确定我如何正确设置它以正确处理对象。实际上,这应该不是什么大问题,因为对象(一旦创建)应该一直存在,直到 appdomain 被回收(将它们视为“每个独立上下文单例”)。

我倾向于做这样的事情:

// For completeness.. a dummy page which creates a "dummy" context
public partial class _Default : Page
{
    private static AppIsolatedContext _dummyContainer = new AppIsolatedContext();

    public _Default()
    {
        _dummyContainer.ExceptionHandler.Execute("Test Message");            
    }
}

// The isolated context which holds all the "context" specific objects
public class AppIsolatedContext
{
    public static IContainer Container { get; set; }

    public IExceptionHandler ExceptionHandler { get; set; }
    //public ISomething Something { get; set; }
    //public ISomethingElse SomethingElse { get; set; }

    public AppIsolatedContext()
    {
        // set up autofac
        // Create your builder.
        ContainerBuilder builder = new ContainerBuilder();

        // Usually you're only interested in exposing the type
        // via its interface:
        builder.RegisterType<MailNotificationHandler>().As<INotificationHandler>();
        builder.RegisterType<ExceptionHandler>().As<IExceptionHandler>();

        Container = builder.Build();

        using (ILifetimeScope scope = Container.BeginLifetimeScope())
        {
            ExceptionHandler = scope.Resolve<IExceptionHandler>();
            //Something = scope.Resolve<ISomething>();
            //SomethingElse = scope.Resolve<ISomethingElse>();
        }
    }
}

当然,我的应用程序不限于这些“上下文单例”实例。我也会有每个请求的生命周期实例。但这就是 ASP.NET 集成模块的用途吗?我希望它们也可以无缝集成到 SharePoint (2013) 中:)

所以我的问题是我提出的建议可以吗?或者我需要自己动手吗?如果是这样,某些方向将是惊人的...

翻阅 Autofac 的文档后,我偶然发现了它的多租户功能。 我相信这也可能适合我的目的。任何人都可以证实这一点吗?

using System;
using System.Web;
using Autofac.Extras.Multitenant;

namespace DemoNamespace
{
    public class RequestParameterStrategy : ITenantIdentificationStrategy
    {
        public bool TryIdentifyTenant(out object tenantId)
        {
            tenantId = AppIsolatedContext.Current.Id; // not implemented in the dummy class above, but present in the real thing.
            return !string.IsNullOrWhiteSpace(tenantId);
        }
    }
}

如果有什么不是水晶的 - 请不要犹豫告诉我 :)

【问题讨论】:

    标签: c# inversion-of-control autofac object-lifetime


    【解决方案1】:

    免责声明:这是一个相当重要的问题,鉴于此以及我对 SharePoint 2013 的熟悉程度,我会尽力回答,但您需要根据您的情况调整答案需要。

    我将使用命名生命周期范围来构建它。 使用命名范围的层次结构,而不是使用它们自己的容器的上下文。这就是多租户支持的工作方式;这也是 ASP.NET per-web-request 支持的工作原理。

    您首先要阅读the Autofac wiki page on instance scopesthis primer on Autofac lifetimes。这些都不是小文章,但都有重要的概念需要理解。我在这里解释的一些内容只有在您了解生命周期范围时才有意义。

    生命周期范围是可嵌套的,这是您共享单例或每网络请求实例的方式。应用程序的根目录是一个包含所有注册的容器,您可以从中生成作用域。

    • 容器
      • 子范围
        • 子范围的子级

    在更与代码相关的格式中,是这样的:

    var builder = new ContainerBuilder();
    var container = builder.Build();
    using(var child = container.BeginLifetimeScope())
    {
      using(var childOfChild = child.BeginLifetimeScope())
      {
      }
    }
    

    您实际上是在范围之外解析组件 - 容器本身就是一个范围。

    关于生命周期范围的关键事项:

    • 您可以命名它们,从而允许您在命名范围内拥有“单例”。
    • 您可以在致电BeginLifetimeScope 的过程中即时注册。

    这就是 Autofac 的多租户支持的工作原理。每个租户都有自己命名的生命周期范围。

    不幸的是,多租户支持是一级的:应用程序容器产生特定于租户的“根”范围,但仅此而已。您拥有这些上下文的站点层次结构不止一个级别,因此多租户支持不起作用。但是,您可以查看该源代码以获取想法。

    我要做的是在每个级别命名范围。每个站点都会通过ILifetimeScope 来解决问题。在代码中,它看起来有点像:

    var builder = new ContainerBuilder();
    // RootWeb will use the container directly and build its per-web-request
    // scope from it.
    var container = builder.Build();
    
    // Each sub web will get its own scope...
    using(var sw1Scope = container.BeginLifetimeScope("SubWeb"))
    {
      // Each child of the sub web will get a scope...
      using(var sw11Scope = sw1Scope.BeginLifetimeScope("SubWeb"))
      {
      }
      using(var sw12Scope = sw1Scope.BeginLifetimeScope("SubWeb"))
      {
      }
    }
    

    请注意,我将每个级别的子 Web 范围标记为“SubWeb” - 这将允许您在容器级别和子 Web 级别的注册中拥有“每个子 Web 的实例”类型的注册。

    // Register a "singleton" per sub-web:
    builder.RegisterType<Foo>()
           .As<IFoo>()
           .InstancePerMatchingLifetimeScope("SubWeb");
    

    现在,显然,这是一个概念性的东西 - 你实际上无法将所有内容都包含在这样的 using 语句中。您需要以不同的方式管理您的创建和处置,因为创建将发生在与处置不同的地方。

    您可以查看 ASP.NET 和多租户源代码,以了解如何做到这一点。一般算法是:

    • 在应用程序启动时,构建根容器。
    • 随着子网站的启动,生成一个以子网站命名的嵌套生命周期范围。
    • 如果子网站需要注册特定组件,请在调用BeginLifetimeScope 期间执行此操作
    • 如果您需要每个子网站级别的“上下文”,您可以将为该子网站创建的范围传递给它,而不是创建一个完整的单独容器。

    现在,您可以更进一步,将子 Web ID 的根级字典保留在范围内,这样您就根本不需要每个级别的“上下文”对象。它更像是一种DependencyResolver.Current.GetService&lt;T&gt; 的模式。如果您查看 Autofac 多租户支持中的 MultitenantContainer 是如何工作的,您会看到类似的租户 ID 到范围字典。

    事实上,多租户支持将是一个很好的模式,特别是如果您还想拥有每个网络请求的范围。 Autofac ASP.NET 支持要求您传入父 ILifetimeScope,从该父 Web 请求生命周期范围将产生。多租户支持在其中添加了一些动态方面,因此当 ASP.NET 支持调用 BeginLifetimeScope 时,事物的多租户部分会自动确定(通过租户标识)哪个租户应该是当前请求的父级。您可以对子网站的层次结构做同样的事情。然而,多租户支持是一个平面结构,而您的子网站是一个层次结构,所以多租户支持不会正常工作

    这完全可以说明您在这里有一个有趣的用例,但是您会弄脏自己的手

    【讨论】:

    • 感谢您用冗长的方式告诉我我需要弄脏自己的手 :) 我会看看您向我建议的各个章节/实现。不过有个小提示:鉴于我总是可以获得“当前自定义上下文的 id”这一事实,这不足以让多租户工作吗?对于 AutoFac,它只是一个“ID”列表,它必须为其保存不同的类型映射。
    • 问题在于层次结构——如前所述,多租户是平面的,而您的站点是层次结构的。类似,但租户范围总是脱离容器,而您希望子 Web 范围脱离适当的父范围 - 而不仅仅是根容器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多