You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 6多租户应用:如何基于子域名匹配对应控制器?

.NET 6多租户按子域名匹配Area控制器的路由解决方案

问题背景

使用.NET 6开发多租户应用,每个租户拥有独立控制器(继承自BaseController),控制器通过[Area("租户标识")]标记,并绑定专属授权方案。需求是:来自testTenantOne.domain.com的请求仅能路由到标记Area("testTenantOne")的控制器,且使用对应授权方案。但默认路由会选择匹配度最高的控制器,过滤器执行时机过晚(控制器已选定),无法满足需求。

解决方案

1. 解析子域名获取租户标识

首先实现工具方法,从请求Host中提取子域名作为租户ID:

public static class TenantResolver
{
    public static string GetTenantFromHost(HttpRequest request)
    {
        var host = request.Host.Host;
        if (host.Contains('.'))
        {
            // 假设子域名是Host的第一个部分,如testTenantOne.domain.com提取testTenantOne
            return host.Split('.')[0];
        }
        return null;
    }
}

2. 自定义Action约束(适配属性路由)

由于项目使用属性路由,常规路由约束无法生效,需通过应用模型约定添加Action级别的约束,提前过滤不匹配租户的控制器:

创建自定义Action约束

public class TenantAreaActionConstraint : IActionConstraint
{
    private readonly string _controllerArea;

    public TenantAreaActionConstraint(string controllerArea)
    {
        _controllerArea = controllerArea;
    }

    // 设置优先级为0,确保该约束最先执行
    public int Order => 0;

    public bool Accept(ActionConstraintContext context)
    {
        var tenantId = TenantResolver.GetTenantFromHost(context.RouteContext.HttpContext.Request);
        // 匹配控制器Area与当前租户标识
        return string.Equals(_controllerArea, tenantId, StringComparison.OrdinalIgnoreCase);
    }
}

注册应用模型约定

public class TenantAreaApplicationModelConvention : IApplicationModelConvention
{
    public void Apply(ApplicationModel application)
    {
        foreach (var controller in application.Controllers)
        {
            var areaAttribute = controller.Attributes.OfType<AreaAttribute>().FirstOrDefault();
            if (areaAttribute == null) continue;

            // 为控制器的所有路由添加租户匹配约束
            foreach (var selector in controller.Selectors)
            {
                selector.ActionConstraints.Add(new TenantAreaActionConstraint(areaAttribute.RouteValue));
            }
        }
    }
}

3. 配置服务启用约束

在Program.cs中注册约定和相关服务:

var builder = WebApplication.CreateBuilder(args);

// 注册应用模型约定
builder.Services.AddControllers(options =>
{
    options.Conventions.Add(new TenantAreaApplicationModelConvention());
});

// 其他服务配置...

var app = builder.Build();

// 常规中间件配置...

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

app.Run();

4. 动态匹配授权方案(可选)

如果需要确保每个租户使用对应的授权方案,可添加自定义授权过滤器动态调整:

public class TenantAuthorizationFilter : IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var tenantId = TenantResolver.GetTenantFromHost(context.HttpContext.Request);
        if (string.IsNullOrEmpty(tenantId)) return;

        // 动态设置授权方案(假设命名规则为"{租户标识}Jwt")
        var authorizeAttribute = context.ActionDescriptor.EndpointMetadata.OfType<AuthorizeAttribute>().FirstOrDefault();
        if (authorizeAttribute != null)
        {
            authorizeAttribute.AuthenticationSchemes = $"{tenantId}Jwt";
        }
    }
}

在AddControllers中注册过滤器:

builder.Services.AddControllers(options =>
{
    options.Conventions.Add(new TenantAreaApplicationModelConvention());
    options.Filters.Add<TenantAuthorizationFilter>();
});

原理说明

  • 应用模型约定会在应用启动时遍历所有控制器,为带Area属性的控制器添加自定义Action约束。
  • 当请求到达时,TenantAreaActionConstraint会优先执行,只有Area与当前子域名匹配的控制器才会被选中,直接过滤掉其他租户的控制器,避免默认路由的匹配问题。
  • 授权过滤器则确保当前请求使用对应租户的授权方案,实现租户隔离。

内容的提问来源于stack exchange,提问作者muhmuhhum

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 10:23:13