• .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。

  • 相关阅读:
    unity代码加密for Android,mono编译
    php __invoke 和 __autoload
    VC只运行一个程序实例
    VC单文档对话框添加托盘图标
    技术文档应该怎么写
    项目管理学习
    cannot download, /home/azhukov/go is a GOROOT, not a GOPATH
    Go语言学习
    appium键盘事件
    appium-doctor
  • 原文地址:https://www.cnblogs.com/CreateMyself/p/13186026.html
Copyright © 2020-2023  润新知