I am trying to use Simple Injector to create a plugin architecture that will allow me to configure a plugin "abc" (a tenant), and if I have provided ?tenant=abc
in the querystring of my request it will override a "core" plugin and use it's controllers instead.
For example, if I have the following controller in "core":
public HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "This is core.";
return View();
}
}
And, if I have specified ?tenant=abc
, then it should instead load the "abc" plugin controller:
public HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "This is abc.";
return View();
}
}
The problem is, I am not quite sure where to start. I have been reading the following posts, but still don't seem to have the "glue" to put all these pieces together.
Can anyone provide me the slightest "quick-start" so I can put together a basic "hello world" the supports the functionality outlined above?
Edit: I imagine the solution will be similar to this (Autofac's multi-tenant implementation):
// 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>();
Is there a way to do this sort of thing with SimpleInjector?
There are many ways to do this, and it all depends on what exactly you need. The application I'm currently working on uses a modularized approach, where we have one 'shell' MVC project with multiple 'module' MVC projects with each their own controller/view sets, while they sometimes use shared functionality (such as views and templates) from the shell. But this is not a tenant based approach. Here we try to isolate parts of the application, to lower the complexity. But we don't load our controllers dynamically; the shell just references the module projects. And each module project contains one or multiple areas and we copy the area folders to the /areas of the shell upon build. This is an approach that took an awful lot of time to get right. But I digress.
In your case, I think its best to start with a custom ControllerFactory
. From within this factory you can decide what controller to load based on certain conditions. We use that approach as well, and redirect the factory to a specific module assembly, based on the area.
This is not really a problem that you solve at the level of your DI container IMO. You can leave your DI container out of the picture here. Here's an example of cuch custom controller factory:
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;
}
}
In this example I assume that each tenant has its own assembly, or at least its own namespace, which starts with "MyComp.Plugins." followed by the name of the tenant. By setting the "Namespaces" data token of the Route Data we can construct MVC to search in certain namespaces.
You can replace MVC's default controller factory as follows:
ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());
If your plugin controller is in the "MyComp.Plugins.abc" namespace in an assembly that is in the web applications /bin folder, this should work.
UPDATE
About registering services based on the current tenant. There are multiple ways to approach this. First thing to note is that Simple Injector does not have any out of the box facilities for this, but I would say that there's no need for that. Here are two options of approaching this.
Both options use the same ITenantContext
abstraction. Here's that abstraction:
public interface ITenantContext {
string TenantId { get; }
}
And each abstraction should have an implementation. This one is specific to your (current) needs:
public class AspNetQueryStringTenantContext : ITenantContext {
public string TenantId {
get { return HttpContext.Current.Request.QueryString["tenant"]; }
}
}
Option 1: Use a proxy class.
A quite common thing to do is create a proxy for the given IDependency
abstraction, which will make the decision of to which specific implementation to forward (based on the current tenant). This could look like this:
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>();
}
}
}
The registration would look as follows:
ITenantContext tenantContext = new AspNetQueryStringTenantContext();
container.RegisterSingle<ITenantContext>(tenantContext);
container.Register<Tenant1Dependency>(Lifestyle.Transient);
container.Register<Tenant2Dependency>(Lifestyle.Singleton);
container.Register<IDependency, TenantDependencyProxy>(Lifestyle.Singleton);
Now everything in the application can simply depend on IDependency
and depending on some runtime conditions they will use either Tenant1Dependency
or Tenant2Dependency
.
Option 2: Implement that proxy functionality inside a factory delegate.
With this option you still implement the switch-case statement of the proxy, but you put that inside a factory delegate that you register:
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>();
}
});
This removes the need for the proxy class.
If you have many services that you need to switch, this code can easily be refactored in a way that you reuse this code for many abstractions. If you have many of these services that you want to switch like this, I'd suggest taking a good look at your architecture, because it seems unlikely to me that you'd ever need more than just a few of those.