2

设想:

我需要为同一个 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);
        }
    }
}

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

4

1 回答 1

5

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

我构造它的方式是使用命名的生命周期范围。与其使用具有自己容器的上下文,不如使用命名范围的层次结构。这就是多租户支持的工作方式;这也是 ASP.NET per-web-request 支持的工作方式。

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

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

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

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

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

现在,显然,这是一个概念性的东西——你实际上不能用这样的语句来包装所有东西。您需要以不同的方式管理您的创作和处置,因为创作将发生在与处置不同的地方。

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

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

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

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

这就是说你在这里有一个有趣的用例,但是你会弄脏你的手

于 2013-05-29T17:56:04.823 回答