asp.net core mvc剖析:mvc動作選擇
一個http請求過來後,首先經過路由規則的匹配,找到最符合條件的的IRouter,然後呼叫IRouter.RouteAsync來設定RouteContext.Handler,最後把請求交給RouteContext.Handler來處理。在MVC中提供了兩個IRouter實現,分別如下:
1,MvcAttributeRouteHandler
2,MvcRouteHandler
我們再來看一下UseMvc的實現邏輯
public static IApplicationBuilder UseMvc( this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes) { 。。。。。。 //例項化路由構造器 var routes = new RouteBuilder(app) { //設定預設處理器,就是路由符合條件時使用MvcRouteHandler來處理請求 DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(), }; //配置路由規則 configureRoutes(routes); //這句很重要,上面配置的全域性的路由規則,我們同樣可以在控制器或者控制器方法上使用RouteAttribute配置路由規則,這些規則會優先採用 routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)); //routes.Build方法生成IRouter物件,一會我們在看具體細節,然後通過UseRouter註冊一個RouterMiddleware中介軟體 return app.UseRouter(routes.Build()); }
上面的configureRoutes(routes)語句註冊Route關聯上了MvcRouteHandler,而routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices))註冊AttributeRoute關聯上了MvcAttributeRouteHandler,並且後者優先被匹配選擇。
在這兩個RouterHanlder裡做的一個最重要的工作就是進行動作選擇,以MvcRouterHandler為例,程式碼如下:
public Task RouteAsync(RouteContext context) { 。。。。。。 //根據路由資訊查詢符合要求的ActionDescriptor集合 var candidates = _actionSelector.SelectCandidates(context); if (candidates == null || candidates.Count == 0) { _logger.NoActionsMatched(context.RouteData.Values); return TaskCache.CompletedTask; } //按照約束規則選擇最符合要求的一個ActionDescriptor var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates); if (actionDescriptor == null) { _logger.NoActionsMatched(context.RouteData.Values); return TaskCache.CompletedTask; } context.Handler = (c) => { var routeData = c.GetRouteData(); var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor); if (_actionContextAccessor != null) { _actionContextAccessor.ActionContext = actionContext; } var invoker = _actionInvokerFactory.CreateInvoker(actionContext); if (invoker == null) { throw new InvalidOperationException( Resources.FormatActionInvokerFactory_CouldNotCreateInvoker( actionDescriptor.DisplayName)); } return invoker.InvokeAsync(); }; return TaskCache.CompletedTask; }
在這裡面藉助一個ActionSelector來根據路由資料進行動作選擇,得到符合要求的ActionDescriptor。ActionDescriptor是什麼?它是一個動作的描述類,包含動作名稱,約束條件等。它是如何得到的?
我們從IActionSelector.SelectCandidates開始進行跟蹤,並根據MvcCoreServiceCollectionExtensions提供的AddMvcCoreServices的依賴注入配置,我們不難得到下面的呼叫關係:
在DefaultApplicationModelProvider.OnProvidersExecuting方法中通過反射方式解析程式集中的類資訊。我們分析下控制器類分析的程式碼,這部分程式碼在CreateControllerModel方法中
protected virtual ControllerModel CreateControllerModel(TypeInfo typeInfo)
{
。。。。。。
//獲取RouteAttribute特性資訊,這裡是採用迴圈的方式,直到找到第一個定義了IRouteTemplateProvider特性的類為止,所以如果子類沒有配置RouteAttribute,就會採用父類的配置
IRouteTemplateProvider[] routeAttributes = null;
do
{
routeAttributes = currentTypeInfo
.GetCustomAttributes(inherit: false)
.OfType<IRouteTemplateProvider>()
.ToArray();
if (routeAttributes.Length > 0)
{
// Found 1 or more route attributes.
break;
}
currentTypeInfo = currentTypeInfo.BaseType.GetTypeInfo();
}
while (currentTypeInfo != objectTypeInfo);
。。。。。。
var controllerModel = new ControllerModel(typeInfo, attributes);
//建立Selectors,這個要在查詢ActionDescriptor時使用
AddRange(controllerModel.Selectors, CreateSelectors(attributes));
//獲取控制器名稱
controllerModel.ControllerName =
typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ?
typeInfo.Name.Substring(0, typeInfo.Name.Length - "Controller".Length) :
typeInfo.Name;
//把過濾器特性加入到Filters集合中
AddRange(controllerModel.Filters, attributes.OfType<IFilterMetadata>());
//獲取路由規則資料,要求請求時某個路由資料必須等於設定的值,比如在控制器上設定[Area("test")],那只有當路由資料中包含了area且值等於test才用當前這個動作處理,當然大家可以自定義一些限制
foreach (var routeValueProvider in attributes.OfType<IRouteValueProvider>())
{
controllerModel.RouteValues.Add(routeValueProvider.RouteKey, routeValueProvider.RouteValue);
}
//api相關配置
var apiVisibility = attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault();
if (apiVisibility != null)
{
controllerModel.ApiExplorer.IsVisible = !apiVisibility.IgnoreApi;
}
var apiGroupName = attributes.OfType<IApiDescriptionGroupNameProvider>().FirstOrDefault();
if (apiGroupName != null)
{
controllerModel.ApiExplorer.GroupName = apiGroupName.GroupName;
}
// 分析控制器是否實現了動作過濾器和結果過濾器介面,如果我們需要對一個控制器實現一個特殊的動作過濾器或結果過濾器,就不用再單獨建立過濾器特性類了,直接讓控制器實現介面即可,這個很方便
if (typeof(IAsyncActionFilter).GetTypeInfo().IsAssignableFrom(typeInfo) ||
typeof(IActionFilter).GetTypeInfo().IsAssignableFrom(typeInfo))
{
controllerModel.Filters.Add(new ControllerActionFilter());
}
if (typeof(IAsyncResultFilter).GetTypeInfo().IsAssignableFrom(typeInfo) ||
typeof(IResultFilter).GetTypeInfo().IsAssignableFrom(typeInfo))
{
controllerModel.Filters.Add(new ControllerResultFilter());
}
return controllerModel;
}
上面的方法結束後,就得到了一個ControllerModel物件,CreateActionModel方法是建立ActionModel物件,分析過程基本跟CreateControllerModel方法類似,就不再介紹,結果分析後,最後得到了下面的層次關係物件:
ApplicationModel->ControllerModel->ActionModel
得到最終的ApplicationModel後,再有ControllerActionDescriptorBuilder.Build(applicationModel)生成對應的ControllerActionDescriptor集合。
再回到ActionSelector.SelectCandidates方法,在這個方法裡面通過呼叫ActionSelectionDecisionTree.Select方法來選擇符合要求的ActionDescriptor,實現程式碼如下:
public IReadOnlyList<ActionDescriptor> Select(IDictionary<string, object> routeValues)
{
var results = new List<ActionDescriptor>();
Walk(results, routeValues, _root);
return results;
}
_root是一個DecisionTreeNode<ActionDescriptor>型別,它是通過DecisionTreeBuilder<ActionDescriptor>.GenerateTree生成的一個查詢樹。
DecisionTreeNode定義如下:
internal class DecisionTreeNode<TItem>
{
//符合條件的ActionDescriptor集合
public IList<TItem> Matches { get; set; }
// 分支規則
public IList<DecisionCriterion<TItem>> Criteria { get; set; }
}
一個分支規則定義如下:
internal class DecisionCriterion<TItem>
{
public string Key { get; set; }
public Dictionary<object, DecisionTreeNode<TItem>> Branches { get; set; }
}
查詢邏輯:
1,判斷當前結點上是否存在Matches,如果存在放到查詢結果集裡。
2,迴圈分支規則,獲取分支規則key,按照key從路由資料中獲取對應key的值,在通過這個值從Branches字典中查詢對應DecisionTreeNode<TItem>
3,在分支Node上執行1,2步
4,返回最後的查詢結果
通過上面的步驟會得到所有符合要求的ActionDescriptor,然後呼叫SelectBestCandidate獲取最符合條件的ActionDescriptor,如果最後查詢到的ActionDescriptor不是一個,則報AmbiguousActionException異常。
回到MvcRouteHandler,在查詢到ActionDescriptor之後,就設定context.Handler
context.Handler = (c) =>
{
var routeData = c.GetRouteData();
//根據actiondescriptor例項化ActionContext物件
var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
if (_actionContextAccessor != null)
{
_actionContextAccessor.ActionContext = actionContext;
}
//建立IActionInvoker
var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
if (invoker == null)
{
throw new InvalidOperationException(
Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(
actionDescriptor.DisplayName));
}
//執行invoker處理請求
return invoker.InvokeAsync();
};
在Hander中,根據查詢到的ActionDescriptor例項化ActionContext,然後通過ActionInvokerFactory建立invoker,通過invoker執行ActionDescriptor對應的動作,並返回結果。
先到這裡,下面再繼續介紹Invoker相關內容。