.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
相关产品推荐
相关产品推荐

