c# - 带有简单注入(inject)器的插件架构

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

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

例如,如果我在“核心”中有以下 Controller :

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

而且,如果我指定了 ?tenant=abc,那么它应该加载“abc”插件 Controller :

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

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

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

编辑:我想解决方案将与此类似(Autofac 的 Multi-Tenancy 实现):

// 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 项目和多个“模块”MVC 项目,每个项目都有自己的 Controller / View 集,而它们有时使用共享功能(例如 View 和模板)从外壳。但这不是基于租户的方法。在这里,我们尝试隔离应用程序的各个部分,以降低复杂性。但是我们不会动态加载我们的 Controller ;外壳仅引用模块项目。每个模块项目包含一个或多个区域,我们在构建时将区域文件夹复制到 shell 的/areas 。这是一种花了很多时间才正确的方法。但我离题了。

在您的情况下,我认为最好从自定义 ControllerFactory 开始.在此工厂内,您可以根据特定条件决定加载哪个 Controller 。我们也使用这种方法,并根据区域将工厂重定向到特定的模块程序集。

这实际上不是您在 DI 容器 IMO 级别解决的问题。您可以在此处将 DI 容器从图片中移除。这是 cuch 自定义 Controller 工厂的示例:

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”开头。后面是租户的名字。通过设置路由数据的“命名空间”数据 token ,我们可以构建 MVC 以在特定命名空间中进行搜索。

您可以按如下方式替换 MVC 的默认 Controller 工厂:

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

如果您的插件 Controller 位于 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:在工厂委托(delegate)中实现该代理功能。

使用这个选项,您仍然可以实现代理的 switch-case 语句,但是您将它放在您注册的工厂委托(delegate)中:

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

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

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

关于c# - 带有简单注入(inject)器的插件架构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26822155/

相关文章:

c# - 在 Visual C# 中,如何禁用 WiX 项目? (使构建太慢)

c# - Windows 8 商店应用程序不支持 System.Threading.Thread

c# - 如何在 WCF 服务响应中正确实现自定义 soap 1.2 header 和 WS-Addressing

c# - Firefox 浏览器不会重新加载更新的 CSS/JS 文件

c# - 如何确定 Facebook 用户是否登录到 Facebook 从 C# 连接

c# - 找到了 Automapper 未映射的成员 - 即使在我映射了这些成员之后

c# - ASP.Net:将新文件部署到服务器时用户已注销

c# - 在回发时,如何检查 Page_Init 事件中哪个控件导致回发

ajax - 如果 AJAX 请求内容长度超过允许的最大长度,则在 MVC 中使用 HttpModule 返回新响应

javascript - jqGrid:为什么我为网格编辑定义的事件没有触发?