Autofac:带有 RouteValues 的 ITenantIdentificationStrategy

Autofac: ITenantIdentificationStrategy with RouteValues

我在进行多租户工作时遇到了问题。我已尝试按照示例 here 进行操作,但看不出我的实现有何不同。

租户由地址字段中的路由参数标识。这似乎没有问题(调用 TryIdentifyTenant returns 是正确的)。我正在使用 ASP.NET Core 3.1,以及 Autofac.AspNetCore-Multitenant v3.0.1 和 Autofac.Extensions.DependencyInjection v6.0.0.

我对代码进行了简化(已经过测试,仍然不行)。配置了两个租户,"terminal1" 和 "terminal2"。输出应因租户而异。但是,它始终是 return 的基本实现。在下面的示例中,输入“https://localhost/app/terminal1" returns "base : terminal1" and "https://localhost/app/terminal2”returns "base : terminal2"。它应该 return "userhandler1 : terminal1" 和 "userhandler2 : terminal2".

家庭控制器:

     public class HomeController : Controller
    {
        private readonly IUserHandler userHandler;
        private readonly TerminalResolverStrategy terminalResolverStrategy;

        public HomeController(IUserHandler userHandler, TerminalResolverStrategy terminalResolverStrategy)
        {
            this.userHandler = userHandler;
            this.terminalResolverStrategy = terminalResolverStrategy;
        }

        public string Index()
        {
            terminalResolverStrategy.TryIdentifyTenant(out object tenant);
            return userHandler.ControllingVncUser + " : " + (string)tenant;
        }
    }

用户处理程序:

public interface IUserHandler
    {
        public string ControllingVncUser { get; set; }
    }

    public class UserHandler : IUserHandler
    {
        public UserHandler()
        {
            ControllingVncUser = "base";
        }

        public string ControllingVncUser { get; set; }
    }


    public class UserHandler1 : IUserHandler
    {
        public UserHandler1()
        {
            ControllingVncUser = "userhandler1";
        }

        public string ControllingVncUser { get; set; }
    }

    public class UserHandler2 : IUserHandler
    {
        public UserHandler2()
        {
            ControllingVncUser = "userhandler2";
        }

        public string ControllingVncUser { get; set; }
    }

启动:

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

           public void ConfigureServices(IServiceCollection services)
        {

            services.AddHttpContextAccessor();

            services.AddControllersWithViews();
            services.AddAutofacMultitenantRequestServices();
        }

        public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterType<TerminalResolverStrategy>();
            builder.RegisterType<UserHandler>().As<IUserHandler>();
        }

        public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
        {
            var strategy = new TerminalResolverStrategy(
                container.Resolve<IOptions<TerminalAppSettings>>(), 
                container.Resolve<IHttpContextAccessor>());

            var mtc = new MultitenantContainer(strategy, container);

            mtc.ConfigureTenant("terminal1", b => b.RegisterType<UserHandler1>().As<IUserHandler>());
            mtc.ConfigureTenant("terminal2", b => b.RegisterType<UserHandler2>().As<IUserHandler>());

            return mtc;
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }

            loggerFactory.AddLog4Net();

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();


            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{terminal}/{controller=Home}/{action=Index}/{id?}");
            });
        }
}

节目:

        public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseServiceProviderFactory(new AutofacMultitenantServiceProviderFactory(Startup.ConfigureMultitenantContainer))
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }

ITenantIdentificationStrategy:

        public class TerminalResolverStrategy : ITenantIdentificationStrategy
    {
        public IHttpContextAccessor Accessor { get; private set; }

        private readonly TerminalAppSettings settings;

        public TerminalResolverStrategy(
            IOptions<TerminalAppSettings> options,
            IHttpContextAccessor httpContextAccessor
            )
        {
            Accessor = httpContextAccessor;
            settings = options.Value;
        }

        public bool TryIdentifyTenant(out object terminal)
        {
            HttpContext httpCtx = Accessor.HttpContext;//
            terminal = null;
            try
            {
                if (httpCtx != null &&
                    httpCtx.Request != null &&
                    httpCtx.Request.RouteValues != null &&
                    httpCtx.Request.RouteValues.ContainsKey("terminal"))
                {
                    string requestedTerminal = httpCtx.Request.RouteValues["terminal"].ToString();
                    bool terminalExists = settings.Terminals.ContainsKey(requestedTerminal);
                    if (terminalExists)
                    {
                        terminal = requestedTerminal;
                    }
                }
            }
            catch (Exception) {}
            return terminal != null;
        }
    }
}

我做错了什么?提前致谢。

"Multitenancy doesn't seem to work at all" 是一个有点模棱两可的说法,很难解决。不幸的是,我 个人 没有时间下载您的所有示例代码并尝试重现整个代码并对其进行调试以查看到底出了什么问题。也许其他人会。但是,我可以提供一些关于我会去的地方和我会尝试查看的东西的提示。

Tenant ID 策略设置两次。 我在 Startup.ConfigureContainer 中看到有一个 builder.RegisterType<TerminalResolverStrategy>(); 行 - 这会将您的策略​​类型注册为实例 -每个依赖项,因此每次需要时都会重新解决。我还在 Startup.ConfigureMultitenantContainer 中看到您正在手动实例化多租户容器使用的策略。那里有东西被弄乱的可能性不为零。我会选择一种方法来完成它——要么注册策略,要么手动创建它——我会确保它是一个无状态的单例。 (它没有在示例中注册。)

路由模式可能有问题。 我看到您注册的路由模式如下所示:{terminal}/{controller=Home}/{action=Index}/{id?}。我还看到您的 URL 如下所示:https://localhost/app/terminal1 您是否已介入您的租户 ID 策略以确保路由解析机制正常工作?也就是说,app 没有被选为 terminal 值?路线 parsing/handling 可能很棘手。

可能是错误的设置。 如果有指定特定 terminal 值存在的选项,租户 ID 策略只会成功识别租户。我没有看到任何这些选项的配置位置,这意味着在此 repo 中没有定义任何租户。如果没有它,您的策略将无法识别任何内容。

如果是我,我可能会从该租户 ID 策略中的一个断点开始,看看哪些已解决,哪些未解决。从外部的角度来看,这似乎有些复杂,这就是我要开始的地方。如果这有效,那么我可能还会考虑清理注册,这样 ID 策略就不会被注册两次。最后,我的印象是这不是应用程序中的所有代码;我可能会考虑制作一个与您在此处实际发布的尺寸相当的超小型复制品。然后我会专注于制作最小的复制工作;然后一旦它工作了,我就会弄清楚 repro 和我的大型应用程序之间的区别。

不幸的是,这就是我能为您提供的全部。正如我所提到的,在当前环境和我当前的工作量下,我无法真正坐下来用您的代码重现整个过程。我知道集成是有效的,因为我有生产应用程序在使用它;并且有很多测试(单元和集成)来验证它是否有效;所以不起作用的部分可能在您的代码中的某个地方......而这些是我要开始的地方。

因此,在将租户标识确定为问题之后,RouteValues 似乎直到请求链的后期才通过 HttpContext 得到解决。因此,没有租户得到解决。在我看来,这像是 .NET Core 中的一个错误。通过使用请求路径来绕过这个问题:

public class TerminalResolverStrategy : ITenantIdentificationStrategy
{

    private readonly TerminalAppSettings settings;
    private readonly IHttpContextAccessor httpContextAccessor;

    public TerminalResolverStrategy(
        IOptions<TerminalAppSettings> options,
        IHttpContextAccessor httpContextAccessor
        )
    {
        this.httpContextAccessor = httpContextAccessor;
        settings = options.Value;
    }

    public bool TryIdentifyTenant(out object terminal)
    {
        var httpCtx = httpContextAccessor.HttpContext;
        terminal = null;
        try
        {
            if (httpCtx != null)
            {
                string thisPath = httpCtx.Request.Path.Value;
                var allTerminals = settings.Terminals.GetEnumerator();
                while (allTerminals.MoveNext())
                {
                    if (thisPath.Contains(allTerminals.Current.Key)) { 
                    terminal = allTerminals.Current.Key;
                    return true;
                    }
                }
            }
        }
        catch (Exception) { }
        return false;
    }
}