前言
有些時候我們會發現方法名稱都正確匹配,但就是找不到對應請求接口,所以本文我們來深入了解下何時會出現接口請求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。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※帶您來了解什麼是 USB CONNECTOR ?
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?