.NET Core请求控制器Action方法正确匹配,但为何404?

前言

有些时候我们会发现方法名称都正确匹配,但就是找不到对应请求接口,所以本文我们来深入了解下何时会出现接口请求404的情况。
匹配控制器Action方法(404)

首先我们创建一个web api应用程序,我们给出如下示例控制器代码
复制代码

[ApiController]
[Route("[controller]/[action]")]
public class WeatherController : ControllerBase
{
[HttpGet]
string Get()
{
return “Hello World”;
}
}

复制代码

当我们进行如上请求时会发现接口请求不到,这是为何呢?细心的你应该可能发现了,对于请求方法是私有,而不是公共的,当我们加上public就可以请求到了接口
http://www.sina.com.cn/mid/search.shtml?q=%E5%8D%8E%E7%BA%B3%E5%85%AC%E5%8F%B8%E5%AE%A2%E6%9C%8D_18183615678__m3
[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)
http://www.sina.com.cn/mid/search.shtml?q=%E5%8D%8E%E7%BA%B3%E5%85%AC%E5%8F%B8%E5%AE%A2%E6%9C%8D_18183615678__ft
//如果有属性访问器(无效)

//如果有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());

var actionName = attributes.OfType().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);
}

}
http://www.sina.com.cn/mid/search.shtml? q=%E5%8D%8E%E7%BA%B3%E5%85%AC%E5%8F%B8%E5%AE%A2%E6%9C%8D_18183615678__dy
复制代码
总结

本文我们只是单独针对查找Action方法名称匹配问题做了进一步的探讨,根据源码分析,对Acion方法名称指定会做3步操作:第一,根据方法定义进行过滤筛选,第二,若方法通过AcionName特性标识则以其所给名称为准,否则以方法名称为准,最终赋值给ActionModel上的ActionName属性,第三,将ActionModel上的ActionName值赋值给路由集合中的键Action。

你可能感兴趣的:(.NET Core请求控制器Action方法正确匹配,但为何404?)