ASP.NET Core 6如何根据服务端配置动态选择调用的控制器Action?
我有一个ASP.NET Core 6 Web应用程序,其中AuthController包含如下两个控制器Action:
[Route("auth")] [ApiController] public class AuthController: ControllerBase { [HttpPost] [Route("login/credentials")] public async Task<IActionResult> LoginWithCredentials(...) { //... } [HttpPost] [Route("login/token")] public async Task<IActionResult> LoginWithToken(...) { //... } }
我希望所有发往/auth/login的请求,能够根据服务端配置转发至LoginWithCredentials()或LoginWithToken()中的任意一个,即实现根据特定路由匹配的请求动态选择要调用的控制器Action。
请问这是否可行?若可行,该如何实现?
注:我曾尝试使用DynamicRouteValueTransformer,但似乎无法实现需求——它仅能修改路由参数值,无法更改要调用的控制器Action(也可能是我的使用方式有误,恳请指正)。
补充说明:有人询问为何不合并两个Action为一个并通过内部判断处理,原因是其中一个Action带有自定义TypeFilterAttribute(示例中为简洁起见省略),该属性来自我无权修改的第三方库,合并会导致属性应用不符合需求(要么需要属性的Action无法获取,要么不需要的Action被错误应用)。
可行,以下是两种适配你场景的可靠实现方式:
方式一:自定义IActionSelector(推荐,精准控制)
ASP.NET Core允许替换默认的Action选择逻辑,通过实现IActionSelector接口,你可以完全根据服务端配置指定要执行的目标Action,且无需修改原有Action的路由或特性配置。
步骤1:实现自定义Action选择器
public class ConfigurableAuthActionSelector : IActionSelector { private readonly IActionSelector _innerSelector; private readonly IConfiguration _configuration; public ConfigurableAuthActionSelector(IActionSelector innerSelector, IConfiguration configuration) { _innerSelector = innerSelector; _configuration = configuration; } public IReadOnlyList<ActionDescriptor> SelectCandidates(RouteContext context) { var candidates = _innerSelector.SelectCandidates(context); // 仅处理/auth/login路径的请求 if (context.HttpContext.Request.Path.StartsWithSegments("/auth/login")) { // 从配置读取目标Action名称,支持切换为LoginWithCredentials/LoginWithToken var targetActionName = _configuration["Auth:DefaultLoginAction"]; // 筛选出匹配的目标Action描述符 var targetCandidate = candidates.FirstOrDefault( a => a.RouteValues.TryGetValue("action", out var actionName) && actionName.Equals(targetActionName, StringComparison.OrdinalIgnoreCase)); if (targetCandidate != null) { return new List<ActionDescriptor> { targetCandidate }; } } // 其他请求沿用默认选择逻辑 return candidates; } public ActionDescriptor? SelectBestCandidate(RouteContext context, IReadOnlyList<ActionDescriptor> candidates) { return _innerSelector.SelectBestCandidate(context, candidates); } }
步骤2:替换默认的IActionSelector
在Program.cs中注册自定义选择器,覆盖默认实现:
builder.Services.Replace(ServiceDescriptor.Singleton<IActionSelector, ConfigurableAuthActionSelector>());
步骤3:添加配置项
在appsettings.json中配置默认转发的目标Action:
{ "Auth": { "DefaultLoginAction": "LoginWithCredentials" // 可随时切换为"LoginWithToken" } }
方式二:改进DynamicRouteValueTransformer的用法
你之前尝试的DynamicRouteValueTransformer其实可以实现需求,关键是通过修改路由值中的action字段,让路由系统匹配到目标Action:
步骤1:实现动态路由转换器
public class AuthRouteTransformer : DynamicRouteValueTransformer { private readonly IConfiguration _configuration; public AuthRouteTransformer(IConfiguration configuration) { _configuration = configuration; } public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values) { if (httpContext.Request.Path.StartsWithSegments("/auth/login")) { var targetAction = _configuration["Auth:DefaultLoginAction"]; // 修改路由值,指向目标Action和控制器 values["action"] = targetAction; values["controller"] = "Auth"; // 补充路由段参数,匹配原Action的路由规则(login/credentials或login/token) values["loginType"] = targetAction.Replace("LoginWith", "").ToLower(); } return new ValueTask<RouteValueDictionary>(values); } }
步骤2:注册动态路由
在Program.cs的路由配置中添加动态路由规则:
app.MapDynamicControllerRoute<AuthRouteTransformer>("auth/login");
步骤3:调整原Action的路由定义
为了让修改后的路由值能匹配到原有Action,需要将原Action的路由改为参数化形式:
[HttpPost] [Route("login/{loginType:regex(credentials|token)}")] public async Task<IActionResult> LoginWithCredentials(...) { //... } [HttpPost] [Route("login/{loginType:regex(credentials|token)}")] public async Task<IActionResult> LoginWithToken(...) { //... }
(注:loginType参数无需在Action方法中接收,仅用于路由匹配)
方案对比
- 方式一:无需修改原有Action的任何配置,直接通过选择逻辑精准控制,完全保留原Action的特性(包括第三方Filter),最适配你的场景。
- 方式二:基于原生路由系统实现,代码量稍少,但需要调整原Action的路由规则。
内容的提问来源于stack exchange,提问作者Master_T

