我正在尝试使用Simple Injector创建一个插件架构,允许我配置插件“abc”(租户),如果我在其中提供了?tenant=abc
我的请求的查询字符串将覆盖“core”插件并改为使用它的控制器。
例如,如果我在“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做这种事情?
答案 0 :(得分:4)
有很多方法可以做到这一点,这一切都取决于你究竟需要什么。我目前正在开发的应用程序使用模块化方法,我们有一个“shell”MVC项目,其中包含多个“模块”MVC项目,每个项目都有自己的控制器/视图集,而它们有时使用共享功能(例如视图和模板) )从壳。但这不是基于租户的方法。在这里,我们尝试隔离应用程序的各个部分,以降低复杂性。但我们不会动态加载我们的控制器; shell只引用了模块项目。每个模块项目包含一个或多个区域,我们在构建时将区域文件夹复制到shell的/区域。这是一种花费大量时间才能做到正确的方法。但我离题了。
在您的情况下,我认为最好从自定义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
,并且根据某些运行时条件,它们将使用Tenant1Dependency
或Tenant2Dependency
。
选项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>();
}
});
这消除了对代理类的需要。
如果您需要切换许多服务,则可以通过将此代码重用于许多抽象的方式轻松地重构此代码。如果您有许多这样的服务需要像这样切换,我建议您好好看看您的架构,因为我似乎不太需要您只需要其中的一些。