需要有关Autofac自定义生命周期范围与多租户的指南

lap*_*sus 2 c# inversion-of-control autofac object-lifetime

场景:

我需要为同一 Web应用程序(appdomain)中的相同接口定义提供不同的接口实现,但要提供不同的“作用域”

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

RootWeb (SPSite) (ctx here)
  |______SubWeb1 (SPWeb) (ctx here)
  |______SubWeb2 (SPWeb)
  |______SubWeb3 (SPWeb)
           |_______SubWeb3.1 (SPWeb) (ctx here)
           |_______SubWeb3.2 (SPWeb)
Run Code Online (Sandbox Code Playgroud)

RootWeb,SubWeb1和SubWeb3.1提供上下文。也就是说,我实现了一个AppIsolatedContext类,该类特定于某个层次结构级别。如果级别不提供上下文,则它从父节点继承上下文,依此类推。例如,SubWeb3将从RootWeb继承其上下文。但是,SubWeb3.1提供了自己的隔离上下文。

隔离的上下文只是静态的ConcurrentDictionary。

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

我倾向于做这样的事情:

RootWeb (SPSite) (ctx here)
  |______SubWeb1 (SPWeb) (ctx here)
  |______SubWeb2 (SPWeb)
  |______SubWeb3 (SPWeb)
           |_______SubWeb3.1 (SPWeb) (ctx here)
           |_______SubWeb3.2 (SPWeb)
Run Code Online (Sandbox Code Playgroud)

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

所以我的问题是我提出的建议还好吗?还是需要弄脏我的手?如果是这样,那么某个方向将是惊人的 ...

浏览Autofac的文档时,我偶然发现了其多租户功能。我相信这也可能适合我的目的。.有人可以确认吗?

// 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>();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果有任何东西不是水晶-请不要犹豫告诉我:)

Tra*_*lig 5

免责声明:这是一个相当重要的问题,并且鉴于这个问题以及我对SharePoint 2013的某种程度的不熟悉,我会尽力回答,但您需要在某种程度上适应您的需求。

我将使用命名生命周期作用域来构造它。与其使用自己的容器的上下文,不如使用命名作用域的层次结构。这就是多租户支持的工作方式;这也是ASP.NET每个Web请求支持的工作方式。

您首先要阅读有关实例范围的Autofac Wiki页面以及有关Autofac生存期的入门。这些都不是小文章,但是都有重要的概念需要理解。如果您了解生命周期范围,那么我在这里解释的一些内容才有意义。

生命周期作用域是可嵌套的,这就是您共享单例或按Web实例请求的方式的方式。在应用程序的根部是一个包含所有注册内容的容器,您可以从中生成作用域。

  • 容器
    • 子范围
      • 儿童范围的儿童

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

var builder = new ContainerBuilder();
var container = builder.Build();
using(var child = container.BeginLifetimeScope())
{
  using(var childOfChild = child.BeginLifetimeScope())
  {
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 您可以命名它们,从而使您在命名范围内拥有“单子”。
  • 您可以在拨打电话期间即时注册内容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"))
  {
  }
}
Run Code Online (Sandbox Code Playgroud)

注意,我将子网站范围的每个级别标记为“ SubWeb”-这将使您在容器级别和子网站级别的注册中都具有“每个子网站实例”类型的注册。

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

现在,显然,这是一个概念性的东西-您实际上无法使用这样的语句包装所有内容。您需要对创建和处置进行不同的管理,因为创建将在与处置不同的地方进行。

您可以查看ASP.NET和多租户源代码,以获得有关如何执行此操作的想法。通用算法为:

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

现在,您可以采取另一步骤,将子Web ID的根级别词典保留在范围内,从而根本不需要每个级别的“上下文”对象。它更像是DependencyResolver.Current.GetService<T>一种模式。如果您查看MultitenantContainerAutofac多租户支持中的工作方式,您会看到类似的租户ID范围字典。

实际上,多租户支持将是一个很好的模式,特别是如果您还希望具有每个Web请求范围的话。Autofac ASP.NET支持要求您传入一个父项,ILifetimeScope将从其派生子级Web请求生存期范围。多租户支持在那里增加了一些动态方面,因此当ASP.NET支持调用BeginLifetimeScope事物的多租户部分时(通过租户标识)会自动找出哪个租户应该是当前请求的父级。您可以对子站点的层次结构执行相同的操作。但是,同样,多租户支持是一个平面结构,而您的子站点是一个层次结构,因此多租户支持将不仅仅适用

这很长一段话可以说您在这里有一个有趣的用例,但是您会变得很脏