【问题标题】:Plugin Architecture with Simple Injector带有简单注入器的插件架构
【发布时间】:2014-11-08 21:13:55
【问题描述】:

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

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

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

问题是,我不太确定从哪里开始。我一直在阅读以下帖子,但似乎仍然没有将所有这些部分组合在一起的“胶水”。

谁能给我一点“快速入门”,这样我就可以组建一个基本的“hello world”,支持上述功能?

编辑:我想解决方案将与此类似(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>();

有没有办法用 SimpleInjector 做这种事情?

【问题讨论】:

  • 如果使用查询字符串有要求吗?我认为使用 MVC 的常用方法是使用区域。这意味着您的租户仍在 url 中,但不在查询字符串中。你会得到 '/abc/home' 而不是 '/home?tenant=abc'。
  • 查询字符串是必需的,因此我可以创建最基本的实现。最终,我想从请求中提供的身份验证令牌中检索租户信息。最初,我想使用区域,但我想探索将单独的 MVC 站点作为插件的选项;而不是在一个 MVC 站点中托管所有文件,这些站点分为不同的区域。
  • 这个“插件控制器”是从哪里来的?同一个大会?你有一个不同的项目吗?该程序集是动态加载的,还是允许预加载(我强烈建议)?
  • 你想达到什么目的?两个控制器是否使用相同的视图?他们都有自己的看法吗?这些视图位于何处?
  • 按照我的设想,我将拥有三个独立的 MVC 项目。 1.“abc”插件。 2.“核心”插件。 3.插件演示。根据我的研究,看来我必须将插件程序集输出到 PluginDemo 项目。当没有指定租户时,我希望“核心”公开它的控制器和视图。指定租户后,我希望该租户覆盖所请求的任何控制器;前提是控制器存在。如果不存在控制器,那么它应该只使用“核心”控制器。

标签: c# asp.net asp.net-mvc simple-injector


【解决方案1】:

有很多方法可以做到这一点,这完全取决于您到底需要什么。我目前正在开发的应用程序使用模块化方法,我们有一个“shell”MVC 项目和多个“模块”MVC 项目,每个项目都有自己的控制器/视图集,而它们有时使用共享功能(例如视图和模板) 从外壳。但这不是基于租户的方法。在这里,我们尝试隔离应用程序的各个部分,以降低复杂性。但是我们不会动态加载我们的控制器;外壳只是引用模块项目。每个模块项目包含一个或多个区域,我们在构建时将区域文件夹复制到 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:在工厂委托中实现该代理功能。

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

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

这消除了对代理类的需要。

如果您有许多需要切换的服务,则可以轻松地重构此代码,以便您将此代码重用于许多抽象。如果您有许多这样的服务想要像这样切换,我建议您仔细看看您的架构,因为在我看来,您需要的不仅仅是其中的几个。

【讨论】:

  • 有没有办法只使用简单注射器注入正确的控制器?我用取自 Autofac 的代码示例更新了这个问题。这样的事情可能吗?
  • @user1477388:“注入正确的控制器”是什么意思?控制器没有注入,它们是应用程序的根类型。一切都注入其中。你能详细说明一下吗?
  • 对不起,你是对的。我想我对以下内容感到困惑: A. 如何将不同的插件程序集放入“shell”项目中。 B. 在这个模型下视图是如何工作的?例如,如果“abc”插件的索引视图与“core”不同。
  • 为了澄清上述内容,如果“abc”插件的索引视图与“核心”不同,则插件视图应覆盖核心视图。鉴于您描述的模型,这可能吗?我在这里有点不合时宜;因此,如果可行,只需询问某种 hello world 示例。我假设这将是一个自定义的 Razor 引擎实现;但不确定如何将所有这些部分组合在一起以构成整体。
  • @user1477388:我在回答中给出的示例实际上是从我为此创建的工作测试项目中复制而来的。所以我的例子有效。对于您所描述的问题,我真的建议转移到区域,每个租户都映射到一个区域,每个区域都有自己的一组控制器和视图。
猜你喜欢
  • 1970-01-01
  • 2016-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-06
相关资源
最近更新 更多