How to inject IHttpContextAccessor into Autofac TenantIdentificationStrategy
Asked Answered
S

2

8

I am migrating my multitenant application from Webapi to aspnet core. In webapi version I was using TenantIdentificationStrategy that identified tenants based on request path on HttpContext.

Moving to aspnet core, I am able to wire-up autofac successfully. I am not able to figure out how to wireup the tenant strategy. I tried injecting IHttpContextAccessor in ConfigureServices as

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); 

and my strategy looks like this

public class AssetClassIdentificationStrategy: ITenantIdentificationStrategy {
    private readonly IHttpContextAccessor _accessor;
    public AssetClassIdentificationStrategy(IHttpContextAccessor httpContextAccessor)
    {
        _accessor = httpContextAccessor;
    }
    public bool TryIdentifyTenant(out object tenantId) {
        tenantId = null;
        var context = _accessor.HttpContext;
        if (context != null && context.Request != null )){
            var matchRegex = new Regex(@"\/[\d,\.,\w]*\/(\w*)\/.*");
            var match = matchRegex.Match(context.Request.Path.ToString());
            if (match.Success) {
                tenantId = match.Groups[1].Value.ToLower();
            }
        }
        return tenantId != null;
    }
}

What I am seeing is that HttpContextAccessor is being injected correctly, where as HttpContext within it always null. As a result of this none of the multitenant services are being resolved.

Looked around for samples, but couldn't find anything that fits the problem. There used to be a RequestParameterTenantIdentificationStrategy in Autofacv3 which is no longer supported. Appreciate any help with this.

Edit Fixed issue with code and adding Startup.cs as requested.

public class Startup
{
    public Startup(IHostingEnvironment env) {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

        Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; }

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.Configure<CacheConfig>(Configuration.GetSection("Caching"),false);
        services.AddMvc();
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddTransient<ITenantIdentificationStrategy,AssetClassIdentificationStrategy>();

        var builder = new ContainerBuilder();
        builder.Populate(services);
        builder.RegisterType<TenantInfo>().WithProperty("TenantName", "unknown").As<ITenantInfo>();

        var container = builder.Build();

        ITenantIdentificationStrategy tenantIdentificationStrategy;
        bool isMultiTenant = container.TryResolve(out tenantIdentificationStrategy);

        var mtc = new MultitenantContainer(tenantIdentificationStrategy, container);
        mtc.ConfigureTenant("pesonalLoans", b => {
            b.RegisterType<TenantInfo>().WithProperty("TenantName","pesonalLoans") .As<ITenantInfo>();
        });
        mtc.ConfigureTenant("retirement", b => {
            b.RegisterType<TenantInfo>().WithProperty("TenantName", "retirement").As<ITenantInfo>();
        });

        return mtc.Resolve<IServiceProvider>();

    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        LoggingConfig.Register(Configuration, loggerFactory);
        app.UseMvc();
    }
}


public class ValuesController : Controller {
    private ITenantInfo _tenant;
    public ValuesController(ITenantInfo tenant) {
        _tenant = tenant;
    }

    [HttpGet]
    public string Get()
    {
        return  _tenant.TenantName;
    }
}


public interface ITenantInfo {
    string TenantName { get; set; }
}
public class TenantInfo: ITenantInfo
{
    public string TenantName { get; set; }
}

Edit 3 project.json

{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0-rc2-3002702",
      "type": "platform"
    },
    "Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final",
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final",
    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final",
    "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0-rc2-final",
    "Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final",
    "Microsoft.Extensions.Logging": "1.0.0-rc2-final",
    "Microsoft.Extensions.Logging.Console": "1.0.0-rc2-final",
    "Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final",
    "Autofac": "4.0.0-rc2-240",
    "Autofac.Multitenant": "4.0.0-beta8-219",
    "System.IdentityModel.Tokens.Jwt": "5.0.0-rc2-305061149",
    "Autofac.Extensions.DependencyInjection": "4.0.0-rc2-240",
    "System.Reflection": "4.1.0-rc2-24027",
    "System.Reflection.Primitives": "4.0.1-rc2-24027",
    "System.Reflection.Extensions": "4.0.1-rc2-24027",
    "System.Reflection.TypeExtensions": "4.1.0-rc2-24027",
    "System.Reflection.Emit": "4.0.1-rc2-24027",
    "System.Reflection.Context": "4.0.1-rc2-24027",
    "System.Reflection.DispatchProxy": "4.0.1-rc2-24027",
    "System.Reflection.Emit.ILGeneration": "4.0.1-rc2-24027",
    "Microsoft.AspNetCore.Diagnostics": "1.0.0-rc2-final",
    "Microsoft.AspNet.Mvc.Formatters.Xml": "6.0.0-rc1-final",
    "Microsoft.AspNet.Mvc.Formatters.Json": "6.0.0-rc1-final",
  },

  "tools": {
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": {
      "version": "1.0.0-preview1-final",
      "imports": "portable-net45+win8+dnxcore50"
    }
  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "dnxcore50",
        "portable-net45+win8"
      ]
    }
  },

  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },

  "runtimeOptions": {
    "gcServer": true
  },

  "scripts": {
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}
Schopenhauerism answered 29/6, 2016 at 23:1 Comment(2)
Can you post your startup file?Titicaca
I have attached the startup fileSchopenhauerism
I
6

There's not currently a way to inject things into a tenant identification strategy because the strategy itself doesn't go through the DI pipeline.

IHttpContextAccessor is usually just backed with HttpContextAccessor which is a singleton anyway and acts by getting info from async/thread local context. You could just new-up your strategy with one of these directly when you're in startup:

var strat = new MyStrategy(new HttpContextAccessor());

Note that at the time the question was originally asked there was an issue with the way multitenancy interacted with the ASP.NET Core IServiceProvider system, which is to say, it didn't.

Since then, we've released 4.0.0-rc3-309 for the Autofac.Extensions.DependencyInjection package which remedies the issue.

The change is that you need to update ConfigureServices to return new AutofacServiceProvider(mtc); and no longer do return mtc.Resolve<IServiceProvider>();.

Inclusive answered 30/6, 2016 at 15:28 Comment(4)
I tried this approach now and I found HttoContext in HttpContextAccessor is null. Another thing I found was, if I place a break point in TryIdentifyTenant in strategy the break-point doesnt hit for page each request. Any ideas what I am doing wrong?Schopenhauerism
I haven't personally tried the new ASP.NET Core stuff with the multitenant support yet. I'm not sure what's up there, but chances are the current Core integration is doing something that bypasses multitenancy. I'll have to check it out. The context accessor thing should work, though.Inclusive
I've added a comment to the overall Autofac to do list for Core. We'll look at it.Inclusive
Fixed in the latest release. Updated my answer accordingly.Inclusive
L
1

This became to long for a comment.

First off your class is named SampleIdentificationStrategy but your constructor references AssetClassIdentificationStrategy. From this issue the project shouldnt even compile.

Next (as you havent provided a startup file) make sure you populating the registered services in AutoFac by calling the below code in the ConfigureServices method.

builder.Populate(services);
builder.Update(container);

Note this method must be run after you have registered all your services to the IServiceCollection

Next make sure you are not mixing framework versions. There are big differences between RC2 RC1, beta-x etc. This was noted here and here on the GitHub issue logs.

Other than this we need to see your startup.cs file (notably extracts from the ConfigureServices method, your project.json file (notably the frameworks and dependency nodes).

Laodicean answered 30/6, 2016 at 6:28 Comment(1)
I have added the statup file and project.json, please take a lookSchopenhauerism

© 2022 - 2024 — McMap. All rights reserved.