需要有关Autofac定制生命望远镜与多租户的指导

时间:2013-05-28 15:54:29

标签: 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)

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);
        }
    }
}

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

1 个答案:

答案 0 :(得分:5)

免责声明:这是一个相当重要的问题,考虑到这一点以及我对SharePoint 2013的熟悉程度,我会尽力回答,但你需要在某种程度上适应你的答案需求。

我将使用命名的生命周期范围来构建它。使用命名范围的层次结构,而不是使用自己的容器进行上下文。这就是多租户支持的工作方式;它也是ASP.NET每网络请求支持的工作方式。

首先,您需要阅读the Autofac wiki page on instance scopes以及this 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级别注册中具有“每子网站实例”类型的注册。

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

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

您可以查看ASP.NET和多租户来源,以获取有关如何执行此操作的建议。一般算法将是:

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

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

事实上,多租户支持将是一个很好的模式,特别是如果您还希望拥有每个Web请求范围。 Autofac ASP.NET支持要求您传入父ILifetimeScope,从中生成子Web请求生存期范围。多租户支持在那里添加了一些动态方面,所以当ASP.NET支持调用BeginLifetimeScope时,多租户部分会自动计算(通过租户标识)哪个租户应该是当前请求的父级。您可以使用子网的层次结构执行相同的操作。但是,再次,多租户支持是一个扁平结构,而您的子网是层次结构,因此多租户支持不会只是工作

这里有很长的路要说你有一个有趣的用例,但是你的手会很脏