ASP.NET Core MVC — MVC执行概貌(0) 路由与MVC的执行流程

许多中间件都会为IApplicationBuilder添加一个扩展方法以简化调用,比如在2.x中常见的UseRoute()之类,其实就是简单调用UseMiddleware<Xxx>()。在ASP.NET CoreMVC也同样提供了一个UseMvc()扩展方法,但是和会转换为UseMiddleware<XMiddleware>()的那些方法不同,并没有一个与UseMvc()对应的、名称为MvcMiddleware的中间件存在。

其实,所谓的UseMvc()其实不过是对UseRouter()的封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// project: 
// aspnet/Mvc
// file:
// src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs

public static IApplicationBuilder UseMvc( this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
if (app == null){ throw new ArgumentNullException(nameof(app)); }
if (configureRoutes == null){ throw new ArgumentNullException(nameof(configureRoutes)); }

// throw an exception if there's no MvcMarkerService
VerifyMvcIsRegistered(app);

var routes = new RouteBuilder(app)
{
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
};

configureRoutes(routes);

routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));

return app.UseRouter(routes.Build());
}

可以看到,执行app.UseMvc(rb=>{ /**/ }) 之类的操作,其实是在配置一个路由中间件,其默认处理器是MvcRouterHandler的实例。这个MvcRouterHandler继承自IRouter,核心思想便是先进行MVC匹配,如果匹配不成功,则返回Task.CompletedTask(根据路由中间件的实现原理,之后会交由后续中间件进行处理);否则,获取路由匹配到的数据,设置处理器为调用相关动作。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
 public class MvcRouteHandler : IRouter{

private readonly IActionContextAccessor _actionContextAccessor;
private readonly IActionInvokerFactory _actionInvokerFactory;
private readonly IActionSelector _actionSelector;
private readonly ILogger _logger;
private readonly DiagnosticSource _diagnosticSource;

// ...

public Task RouteAsync(RouteContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }

var candidates = _actionSelector.SelectCandidates(context);
if (candidates == null || candidates.Count == 0)
{
_logger.NoActionsMatched(context.RouteData.Values);
return Task.CompletedTask;
}

var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates);
if (actionDescriptor == null)
{
_logger.NoActionsMatched(context.RouteData.Values);
return Task.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 Task.CompletedTask;
}

// ...
}

总结来说,这里的代码主要完成以下工作:

  1. 这里一旦路由匹配,则可以得知当前路由所命中ActionASP.NET Core使用ActionDescriptor描述该Action。其中包含了当前Action方法名、参数描述符表、过滤器描述表、Action约束、RouteValue等信息。
  2. 作为一个IRouter,路由匹配后自然还要设置Handler以示命中:这里也就是构建一个routeContext.Handler。该Handler执行以下基本处理过程:

    1. 根据当前HttpContext,配合当前路由数据、及所命中ActionActionDescriptor,构建上下文ActionContext:

      1
      var actionContext = new ActionContext(httpContext, routeData, actionDescriptor)
    2. 根据ActionContext,创建IActionInvoker

    3. 执行IActionInvoker.InvokeAsync(),触发 MVC pipeline的调用

具体的细节,比如IActionInvoker是如何创建的,在后续相关笔记中记录。