Search code examples
c#autofacmulti-tenantautofac-module

How to use Autofac Modules with Muti-Tenant container?


How is it possible to use Autofac's Modules combined with the Multitenant package?

This is my bootstrapping part for Autofac:

var builder = new ContainerBuilder();

// only very basic common registrations here... 
// everything else is in modules

// Register the Autofac module for xml configuration as last 
// module to allow (emergency) overrides.
builder.RegisterModule(new ConfigurationSettingsReader());

// create container for IoC
this.container = builder.Build();

// check for tenant strategy -> if exists, go multi-tenant
if (this.container.IsRegistered<ITenantIdentificationStrategy>())
{
    var tenantIdentificationStrategy = this.container
                                           .Resolve<ITenantIdentificationStrategy>();
    this.container = new MultitenantContainer(tenantIdentificationStrategy,
                                              this.container);

    // how to use xml modules instead of manually register at this point?
} 
else 
{ ... }

I don't want to call something like

container.ConfigureTenant('1', b => b.RegisterType<Tenant1Dependency>()
                                     .As<IDependency>()
                                     .InstancePerDependency());

within the bootstrapper (which don't need to know something about this).

Instead I want to do something like this:

public class TenantModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
         container.ConfigureTenant('1', b => b.RegisterType<Tenant1Dependency>()
                                              .As<IDependency>()
                                              .InstancePerDependency());

         container.ConfigureTenant('2', b => b.RegisterType<Tenant2Dependency>()
                                              .As<IDependency>()
                                              .InstancePerDependency());
         ...

My first idea was to check when registering something for a special tenant:

public class TenantModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.Register((c, p) => {

            var s = c.Resolve<ITenantIdentificationStrategy>();
            if (s != null)
            {
               var id = s.IdentifyTenant<string>();
               ...
            }
        });

I could check the id and register only if id fits, but what to return if id is not matching? null? DoNothingObject? Looks strange/horrible in code to me and something like

builder.Register("1", (context, parameters) => ...

feels more natural. But I don't know how to achieve this out of the box with Autofac. Did I miss something? How did you solved that problem?


Solution

  • Missed the point that ConfigureTenant also passes the containerBuilder which allows the registration of a tenant specific ConfigurationSectionReader. So an option is to have an additional customer/tenant specific ConfigurationSection:

       mtc.ConfigureTenant("mytenant1",
             containerBuilder =>
             {
                 containerBuilder.RegisterModule(new ConfigurationSettingsReader("mytenant1"));
             }
       );
    
       mtc.ConfigureTenant("mytenant2",
             containerBuilder =>
             {
                 containerBuilder.RegisterModule(new ConfigurationSettingsReader("mytenant2"));
             }
       );
    

    This also leads me to the point that I can resolve the tenant identification strategy first, get the identifier and make only the registrations for the current tenant (like a customer specific bootstrap):

       var tenantIdentificationStrategy = container.Resolve<ITenantIdentificationStrategy>();
       var tid = tenantIdentificationStrategy.IdentifyTenant<string>();
    
       mtc.ConfigureTenant(tid ,
             containerBuilder =>
             {
                 containerBuilder.RegisterModule(new ConfigurationSettingsReader(tid));
                 // or something like that which identifies the tenant config section
             }
       );
    

    Or I could put this into a simple foreach to make registrations for all available tenants (but this sounds curious in most scenarios here because only one tenant will be resolved at time at its a bootstrapping part, maybe for other special cases worth to mention).

    Any better ideas? I'll vote up for better ideas... ;-)