.NET Core请求控制器Action方法正确匹配,但为何404?
前言
有些时候我们会发现方法名称都正确匹配,但就是找不到对应请求接口,所以本文我们来深入了解下何时会出现接口请求404的情况。
匹配控制器Action方法(404)
首先我们创建一个web api应用程序,我们给出如下示例控制器代码
[ApiController] [Route("[controller]/[action]")] public class WeatherController : ControllerBase { [HttpGet] string Get() { return "Hello World"; } }
当我们进行如上请求时会发现接口请求不到,这是为何呢?细心的你应该可能发现了,对于请求方法是私有,而不是公共的,当我们加上public就可以请求到了接口
[HttpGet("get")] public string Get() { return "Hello World"; }
匹配控制器Action方法本质
经过如上示例,那么对于Action方法的到底要满足怎样的定义才能够不至于请求不到呢?接下来我们看看源码怎么讲。我们找到DefaultApplicationModelProvider类,在此类中有一个OnProvidersExecuting方法用来构建控制器和Action方法模型,当我们构建完毕所有满足条件的控制器模型后,紧接着势必会遍历控制器模型去获取对应控制器模型下的Action方法,这里只截取获取Action方法片段,源码如下:
foreach (var controllerType in context.ControllerTypes) { //获取控制器模型下的Action方法 foreach (var methodInfo in controllerType.AsType().GetMethods()) { var actionModel = CreateActionModel(controllerType, methodInfo); if (actionModel == null) { continue; } actionModel.Controller = controllerModel; controllerModel.Actions.Add(actionModel); } }
上述红色标记则是创建Action模型的重点,我们继续往下看到底满足哪些条件才创建Action模型呢?
protected virtual ActionModel CreateActionModel(TypeInfo typeInfo, MethodInfo methodInfo) { if (typeInfo == null) { throw new ArgumentNullException(nameof(typeInfo)); } if (methodInfo == null) { throw new ArgumentNullException(nameof(methodInfo)); } if (!IsAction(typeInfo, methodInfo)) { return null; } ...... }
到了这个方法里面,我们找到了如何确定一个方法为Action方法的源头,由于该方法有点长,这里我采用文字叙述来作为判断逻辑,如下:
protected virtual bool IsAction(TypeInfo typeInfo, MethodInfo methodInfo) { //如果有属性访问器(无效) //如果有NonAction特性标识无效) //如果重写Equals(Object), GetHashCode()方法(无效) //如果实现Dispose方法(无效) //如果是静态方法(无效) //如果是抽象方法(无效) //如果是构造函数(无效) //如果是泛型方法(无效) //必须为公共方法 return methodInfo.IsPublic; }
如上是从方法定义的角度来过滤而获取Action方法,除此之外,我们请求方法的名称还可以自定义,比如通过路由、ActionName特性指定,那么这二者是否存在优先级呢?比如如下示例:
[ApiController] [Route("[controller]/[action]")] public class WeatherController : ControllerBase { [HttpGet] [ActionName("get1")] public string get() { var routeValue = HttpContext.Request.RouteValues.FirstOrDefault(); return routeValue.Value.ToString(); } }
我们可以看到此时将以ActionName特性作为方法名称。所以在上述过滤方法定义后开始构建方法模型,在此之后还会再做一步操作,那就是查找该方法是否通过ActionName特性标识,若存在则以ActionName特性标识给定的名称作为请求方法名称,否则以方法定义名称为准,源码如下:
var actionModel = new ActionModel(methodInfo, attributes); AddRange(actionModel.Filters, attributes.OfType<IFilterMetadata>()); var actionName = attributes.OfType<ActionNameAttribute>().FirstOrDefault(); if (actionName?.Name != null) { actionModel.ActionName = actionName.Name; } else { actionModel.ActionName = methodInfo.Name; }
还没完,若是将路由特性放到Action方法上,如下,此时请求接口应该是weather/get还是weather/get1呢?
[ApiController] public class WeatherController : ControllerBase { [HttpGet] [Route("weather/get")] [ActionName("get1")] public string get() { var routeValue = HttpContext.Request.RouteValues.FirstOrDefault(); return routeValue.Value.ToString(); } }
此时若我们以weather/get1请求将出现404,还是以路由特性模板给定为准进行请求,但最终会将路由上Action方法名称通过ActionName特性上的名称赋值给Action模型中的ActionName进行覆盖,源码如下,所以上述我们得到的action名称为get1,,当然这么做没有任何实际意义。
public static void AddRouteValues(ControllerActionDescriptor actionDescriptor,ControllerModel controller,ActionModel action) { foreach (var kvp in action.RouteValues) { if (!actionDescriptor.RouteValues.ContainsKey(kvp.Key)) { actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value); } } if (!actionDescriptor.RouteValues.ContainsKey("action")) { actionDescriptor.RouteValues.Add("action", action.ActionName ?? string.Empty); } if (!actionDescriptor.RouteValues.ContainsKey("controller")) { actionDescriptor.RouteValues.Add("controller", controller.ControllerName); } }
总结
本文我们只是单独针对查找Action方法名称匹配问题做了进一步的探讨,根据源码分析,对Action方法名称指定会做3步操作:第一,根据方法定义进行过滤筛选,第二,若方法通过AcionName特性标识则以其所给名称为准,否则以方法名称为准,最终赋值给ActionModel上的ActionName属性,第三,将ActionModel上的ActionName值赋值给路由集合中的键Action。

为了方便大家在移动端也能看到我分享的博文,现已注册个人公众号,扫描上方左边二维码即可,欢迎大家关注,有时间会及时分享相关技术博文。
感谢花时间阅读此篇文章,如果您觉得这篇文章你学到了东西也是为了犒劳下博主的码字不易不妨打赏一下吧,让楼主能喝上一杯咖啡,在此谢过了!
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!
本文版权归作者和博客园共有,来源网址:http://www.cnblogs.com/CreateMyself)/欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构