带有简单注入器的插件体系结构

本文关键字:插件 体系结构 注入器 简单 | 更新日期: 2023-09-27 18:29:53

我正在尝试使用Simple Injector创建一个插件架构,该架构将允许我配置插件"abc"(租户),如果我在请求的查询字符串中提供了?tenant=abc,它将覆盖的"core"plugin,并使用它的控制器。

例如,如果我在"核心"中有以下控制器:

public HomeController : Controller
{
    public ActionResult Index()
    {
        ViewBag.Message = "This is core.";
        return View();
    }
}

而且,如果我指定了?tenant=abc,那么它应该加载"abc"插件控制器:

public HomeController : Controller
{
    public ActionResult Index()
    {
        ViewBag.Message = "This is abc.";
        return View();
    }
}

问题是,我不太确定从哪里开始。我一直在阅读下面的帖子,但似乎仍然没有"胶水"把所有这些部分放在一起。

  • https://simpleinjector.readthedocs.org/en/latest/advanced.html#registering-动态插件
  • 带有asp.net mvc 4的Simple Injector,从另一个程序集加载控制器
  • 多租户IoC策略
  • 多租户场景中的租户注入

有人能为我提供最轻微的"快速启动"吗?这样我就可以建立一个基本的"你好世界",支持上面列出的功能?

编辑:我想解决方案将类似于此(Autofac的多租户实现):

// First, create your application-level defaults using a standard 
// ContainerBuilder, just as you are used to. 
var builder = new ContainerBuilder(); 
builder.RegisterType<Consumer>().As<IDependencyConsumer>().InstancePerDependency();
builder.RegisterType<BaseDependency>().As<IDependency>().SingleInstance();
var appContainer = builder.Build();  
// Once you've built the application-level default container, you 
// need to create a tenant identification strategy. 
var tenantIdentifier = new MyTenantIdentificationStrategy();  
// Now create the multitenant container using the application 
// container and the tenant identification strategy. 
var mtc = new MultitenantContainer(tenantIdentifier, appContainer);  
// Configure the overrides for each tenant by passing in the tenant ID 
// and a lambda that takes a ContainerBuilder. 
mtc.ConfigureTenant('1', b => b.RegisterType<Tenant1Dependency>().As<IDependency>().InstancePerDependency()); 
mtc.ConfigureTenant('2', b => b.RegisterType<Tenant2Dependency>().As<IDependency>().SingleInstance());  
// Now you can use the multitenant container to resolve instances. 
// Resolutions will be tenant-specific. 
var dependency = mtc.Resolve<IDependency>();
  • 参考。http://www.paraesthesia.com/archive/2010/07/28/introducing-autofaccontrib.multitenant-multitenant-dependency-injection-with-autofac.aspx/
  • 参考。https://code.google.com/p/autofac/wiki/MultitenantIntegration

有没有一种方法可以用SimpleInjector做这种事情?

带有简单注入器的插件体系结构

有很多方法可以做到这一点,这完全取决于您到底需要什么。我目前正在开发的应用程序使用模块化方法,其中我们有一个"shell"MVC项目和多个"module"MVC项目,每个项目都有自己的控制器/视图集,而它们有时使用shell中的共享功能(如视图和模板)。但这不是一种基于租户的方法。在这里,我们尝试隔离应用程序的各个部分,以降低复杂性。但我们不会动态加载控制器;shell只是引用模块项目。每个模块项目都包含一个或多个区域,我们在构建时将区域文件夹复制到shell的/areas中。这是一种需要花费大量时间才能正确处理的方法。但我离题了。

在您的情况下,我认为最好从自定义ControllerFactory开始。在此工厂内,您可以根据特定条件决定加载什么控制器。我们也使用这种方法,并根据区域将工厂重定向到特定的模块组件。

这并不是你在DI容器IMO层面上真正解决的问题。你可以在这里把你的DI容器排除在外。以下是cuch自定义控制器工厂的示例:

public class CustomControllerFactory : DefaultControllerFactory {
    protected override Type GetControllerType(RequestContext requestContext, 
        string controllerName) {
        string tenant = requestContext.HttpContext.Request.QueryString["tenant"];
        string[] namespaces;
        if (tenant != null) {
            namespaces = new[] { "MyComp.Plugins." + tenant };
        } else {
            namespaces = new[] { typeof(HomeController).Namespace };
        }
        requestContext.RouteData.DataTokens["Namespaces"] = namespaces;
        var type = base.GetControllerType(requestContext, controllerName);
        return type;
    }
}

在这个例子中,我假设每个租户都有自己的程序集,或者至少有自己的命名空间,它以"MyComp.Plugins"开头,后跟租户的名称。通过设置Route data的"Namespaces"数据令牌,我们可以构造MVC来在某些命名空间中进行搜索。

您可以如下替换MVC的默认控制器工厂:

ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());

如果您的插件控制器位于web应用程序/bin文件夹中的程序集中的"MyComp.Plugins.abc"命名空间中,这应该可以工作。

更新

关于基于当前租户注册服务。有多种方法可以解决这个问题。首先要注意的是,Simple Injector没有任何现成的设施,但我想说没有必要这样做。这里有两种方法。

两个选项都使用相同的ITenantContext抽象。以下是抽象:

public interface ITenantContext {
    string TenantId { get; }
}

每个抽象都应该有一个实现。这是针对您(当前)需求的:

public class AspNetQueryStringTenantContext : ITenantContext {
    public string TenantId {
        get { return HttpContext.Current.Request.QueryString["tenant"]; }
    }
}

选项1:使用代理类

一件很常见的事情是为给定的IDependency抽象创建一个代理,它将决定转发到哪个特定实现(基于当前租户)。这可能看起来像这样:

public class TenantDependencyProxy : IDependency {
    private readonly Containt container;
    private readonly ITenantContext context;
    public TenantDependencyProxy(Container container, ITenantContext context) {
        this.container = container;
        this.context = context;
    }
    object IDependency.DependencyMethod(int x) {
        return this.GetTenantDependency().DependencyMethod(x);
    }
    private IDependency GetTenantDependency() {
        switch (this.context.TenantId) {
            case "abc": return this.container.GetInstance<Tenant1Dependency>();
            default: return this.container.GetInstance<Tenant2Dependency>();
        }
    }
}

注册情况如下:

ITenantContext tenantContext = new AspNetQueryStringTenantContext();
container.RegisterSingle<ITenantContext>(tenantContext);
container.Register<Tenant1Dependency>(Lifestyle.Transient);
container.Register<Tenant2Dependency>(Lifestyle.Singleton);
container.Register<IDependency, TenantDependencyProxy>(Lifestyle.Singleton);

现在,应用程序中的所有内容都可以简单地依赖于IDependency,并且根据某些运行时条件,它们将使用Tenant1DependencyTenant2Dependency

选项2:在工厂委托中实现代理功能

使用此选项,您仍然可以实现代理的switchcase语句,但您将其放入您注册的工厂委托中:

ITenantContext tenantContext = new AspNetQueryStringTenantContext();
container.RegisterSingle<ITenantContext>(tenantContext);
container.Register<Tenant1Dependency>(Lifestyle.Transient);
container.Register<Tenant2Dependency>(Lifestyle.Singleton);
container.Register<IDependency>(() => {
    switch (tenantContext.TenantId) {
        case "abc": return this.container.GetInstance<Tenant1Dependency>();
        default: return this.container.GetInstance<Tenant2Dependency>();
    }
});

这样就不需要代理类了。

如果您有许多需要切换的服务,那么可以很容易地对这些代码进行重构,以便在许多抽象中重用这些代码。如果你有很多这样的服务想要像这样切换,我建议你好好看看你的架构,因为在我看来,你不太可能只需要其中的几个。