.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。
- 找到多个与名为“HOME”的控制器匹配的类型。如果为此请求(“{CONTROLLER}/{ACTION}/{ID}”)提供服务的路由在搜索匹配此请求的控制器时没有指定命名空间,则会发生此情况。如果是这样,请通过调用含有“NAMESPACES”参数的“MAPROUTE”方法的重载来注册此路由。
- Spring的Controller请求方法中参数名匹配,但是参数类型不同会报404
- 找到多个与名为“xxx”的控制器匹配的类型。如果为此请求(“{controller}/{action}/{id}”)提供服务的路由没有指定命名空间以搜索与此请求相匹配的控制器,则会发生这种情况。
- “/”应用程序中的服务器错误。 找到了多个与名为“Home”的控制器匹配的类型。如果为此请求(“{controller}/{action}/{id}”)提供服务的路由没有指定命名空间来搜索匹配此请求的
- nginx+tomcat 对于action请求返回404页面
- [置顶] PHP反射小试: 提取控制器的action方法
- 在Struts2的Action中取得请求参数值的几种方法
- struts2请求两次即action方法执行两次
- 无论url请求什么.都可以拼接class类名.实例化.传递get参数-->给当前控制器-->传递给抽象父类-->都交给抽象父类.这个方法去处理call_user_func_array()
- Struts2 的Action中取得请求参数值的几种方法
- SpringMvc教程(八)--请求如何映射到具体的Action中的方法
- Struts2一个Action内包含多个请求处理方法的处理
- MVC(二、控制器 Action方法参数与返回值)
- MVC 在action方法中获取当前action的控制器名和action名
- AngularJS控制器controller正确的通信的方法
- jfinal的绝对路径和action请求路径添加文件夹名称而导致"404 not found"的问题
- Struts2一个Action内包含多个请求处理方法的处理,method的使用方法,struts2中的路径问题,通配符映射 (
- 在Struts2的Action中取得请求参数值的几种方法
- ajax 请求php 报错404 但脚本能够正常输出数据 问题的解决方法
- Struts2一个Action内包含多个请求处理方法的处理(三种方式)