How to inject IHttpContextAccessor into Autofac Te

2019-04-08 10:32发布

问题:

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%" ]
  }
}

回答1:

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



回答2:

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).