.net core 源码解析-mvc route的注册,激活,调用流程(三)

简介: .net core mvc route的注册,激活,调用流程mvc的入口是route,当前请求的url匹配到合适的route之后,mvc根据route所指定的controller和action激活controller并调用action完成mvc的处理流程。

.net core mvc route的注册,激活,调用流程

mvc的入口是route,当前请求的url匹配到合适的route之后,mvc根据route所指定的controller和action激活controller并调用action完成mvc的处理流程。下面我们看看服务器是如何调用route的。
core mvc startup基本代码。重点在AddMvc和UseMvc

public class Startup
{
    public IConfigurationRoot Configuration { get; }
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }
    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseStaticFiles();
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

AddMvc:把各种service加入IOC容器。比如格式化提供程序,action定位器,controllerFactory,controller激活器等等,一应服务全部在这里加入。
UseMvc:最重要的一行代码:builder.UseMiddleware(router); 看到这行代码就清楚的知道route 这个handler 在这里加入到请求委托链拉

public static IMvcBuilder AddMvc(this IServiceCollection services)
{
    var builder = services.AddMvcCore();
    builder.AddJsonFormatters();
    builder.AddCors();
    return new MvcBuilder(builder.Services, builder.PartManager);
}
public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services, Action<MvcOptions> setupAction)
{
    var builder = services.AddMvcCore();
    services.Configure(setupAction);
    return builder;
}
internal static void AddMvcCoreServices(IServiceCollection services)
{
    services.TryAddSingleton<IActionSelector, ActionSelector>();
    services.TryAddSingleton<ActionConstraintCache>();
    services.TryAddSingleton<IActionSelectorDecisionTreeProvider, ActionSelectorDecisionTreeProvider>();

    // This has a cache, so it needs to be a singleton
    services.TryAddSingleton<IControllerFactory, DefaultControllerFactory>();

    // Will be cached by the DefaultControllerFactory
    services.TryAddTransient<IControllerActivator, DefaultControllerActivator>();
    services.TryAddEnumerable(ServiceDescriptor.Transient<IControllerPropertyActivator, DefaultControllerPropertyActivator>());

    // Route Handlers
    services.TryAddSingleton<MvcRouteHandler>(); // Only one per app
    services.TryAddTransient<MvcAttributeRouteHandler>(); // Many per app
}
public static IApplicationBuilder UseMvc(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
    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());
}
public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router)
{
    return builder.UseMiddleware<RouterMiddleware>(router);
}

如此,mvc的入口route handler就加入了我们的请求委托链中。后续服务器接收到的请求就能交由route匹配,查找action,激活action处理。

router middleware的激活调用

middleware 请求调用委托链的激活调用请看这篇文章

//middleware加入_components请求处理委托链
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
    _components.Add(middleware);
    return this;
}
public static class UseMiddlewareExtensions
{
    private const string InvokeMethodName = "Invoke";
    private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static);
    //注册middleware    
    public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
    {
        var applicationServices = app.ApplicationServices;
        //将middleware 加入请求处理委托链
        return app.Use(next =>
        {
        //解析方法和参数。查找类的Invoke方法作为入口方法。所以middleware只要是个class就行。只要有一个功公共的Invoke方法即可。
            var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
            var invokeMethods = methods.Where(m => string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)).ToArray();
            var methodinfo = invokeMethods[0];
            var parameters = methodinfo.GetParameters();
            var ctorArgs = new object[args.Length + 1];
            ctorArgs[0] = next;
            Array.Copy(args, 0, ctorArgs, 1, args.Length);
            //创建middleware的实例。并且通过构造函数注入相关的service
            var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
            //如果方法只有一个参数,默认它就是httpcontext。
            if (parameters.Length == 1)
                {
                    return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
                }
            //多余一个参数的则构建一个func。并从ioc容器解析参数注入
            var factory = Compile<object>(methodinfo, parameters);
            return context =>
            {
                var serviceProvider = context.RequestServices ?? applicationServices;
                return factory(instance, context, serviceProvider);
            };
        });
    }
//代码中的创建实例注入service,创建有多个参数的invoke方法注入service具体代码就不贴上来了,占地方。
//构造函数就是匹配最适合的构造函数,然后从IServiceProvider get实例,注入。
//多个参数的invoke就更简单了。直接从IServiceProvider get实例注入。

上述源代码git地址,aspnet/HttpAbstractions项目

route handler middleware代码

public class RouterMiddleware
{
    private readonly ILogger _logger;
    private readonly RequestDelegate _next;
    private readonly IRouter _router;
    //创建middleware的实例。并且通过构造函数注入相关的service
    public RouterMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IRouter router)
    {
        _next = next;
        _router = router;
        _logger = loggerFactory.CreateLogger<RouterMiddleware>();
    }
    //被调用的方法。从这里开始进入mvc route。
    public async Task Invoke(HttpContext httpContext)
    {
        //此处的 IRouter router对象。是我们在Startup中routes.MapRoute...配置的route集合对象:RouteCollection。当然也还有比如attributeroute等等好几种route。
        
        var context = new RouteContext(httpContext);
        context.RouteData.Routers.Add(_router);
        
        await _router.RouteAsync(context);
        if (context.Handler == null)
        {
        //没有匹配到route的情况
            _logger.RequestDidNotMatchRoutes();
            await _next.Invoke(httpContext);
        }
        else
        {
            httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature()
            {
                RouteData = context.RouteData,
            };
            //匹配到路由处理
            await context.Handler(context.HttpContext);
        }
    }
}
//Microsoft.AspNetCore.Routing.RouteCollection
public async virtual Task RouteAsync(RouteContext context)
{
    // Perf: We want to avoid allocating a new RouteData for each route we need to process.
    // We can do this by snapshotting the state at the beginning and then restoring it
    // for each router we execute.
    var snapshot = context.RouteData.PushState(null, values: null, dataTokens: null);
    for (var i = 0; i < Count; i++)
    {
        var route = this[i];
        context.RouteData.Routers.Add(route);
        try
        {
          //循环所有routes规则,逐一匹配,匹配到一个自然就break。
            await route.RouteAsync(context);
            if (context.Handler != null)
                break;
        }
        finally
        {
            if (context.Handler == null)
                snapshot.Restore();
        }
    }
}

UseMvc中有一行非常重要的代码。给RouteBuilder的DefaultHandler赋值一个handler。记住这行代码,我们继续往下看。

public static IApplicationBuilder UseMvc(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
    var routes = new RouteBuilder(app)
    {
        DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
    };
}
//我们在Startup中routes.MapRoute的所有调用最终调用方法都是这个。new Route( routeBuilder.DefaultHandler,....)
//全部都指定了_target为routeBuilder.DefaultHandler
public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder, string name, string template, object defaults, object constraints, object dataTokens)
{
    if (routeBuilder.DefaultHandler == null)
        throw new RouteCreationException(Resources.FormatDefaultHandler_MustBeSet(nameof(IRouteBuilder)));
    var inlineConstraintResolver = routeBuilder.ServiceProvider.GetRequiredService<IInlineConstraintResolver>();
    routeBuilder.Routes.Add(new Route(
        routeBuilder.DefaultHandler,
        name, template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(dataTokens), inlineConstraintResolver));
    return routeBuilder;
}

到这里,我们的逻辑有点绕了,让我们理理清楚:
1.请求进到RouterMiddleware.Invoke()方法
2.调用RouteCollection.RouteAsync()方法,RouteCollection.RouteAsync方法中循环注册的每一个route对象。
并调用route对象的RouteAsync()方法(route对象的RouteAsync方法在它的父类中Microsoft.AspNetCore.Routing.RouteBase)。
这里说的route对象即时Startup中routes.MapRoute生成的route对象(Microsoft.AspNetCore.Routing.Route)。Route继承RouteBase,RouteBase实现IRouter接口
3.RouteBase.RouteAsync()判断当前请求是否符合当前route规则,如果匹配的话,则调用抽象方法OnRouteMatched
4.RouteBase的抽象方法OnRouteMatched,又回到Route对象的OnRouteMatched方法中。调用_target.RouteAsync();_target对象即上面代码中的routeBuilder.DefaultHandler。
5.来到Microsoft.AspNetCore.Mvc.Internal.MvcRouteHandler.RouteAsync()方法中。最重要的一行代码: context.Handler =....
6.调用堆栈最终返回到1中(RouterMiddleware.Invoke())。判断context.Handler == null。为null没找到route匹配的action。不为null则await context.Handler(context.HttpContext)
7.context.Handler即为5中赋值的func。即下面的代码,定位action,调用action。

//Microsoft.AspNetCore.Mvc.Internal.MvcRouteHandler.RouteAsync
public Task RouteAsync(RouteContext context)
{
    var candidates = _actionSelector.SelectCandidates(context);
    if (candidates == null || candidates.Count == 0)
    {
        _logger.NoActionsMatched();
        return TaskCache.CompletedTask;
    }
    var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates);
    if (actionDescriptor == null)
    {
        _logger.NoActionsMatched();
        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;
}

至此,route的处理流程大约交代清楚了。包括route的注册,route的激活,route的选择等。

目录
相关文章
|
5月前
|
监控 安全 开发工具
鸿蒙HarmonyOS应用开发 | HarmonyOS Next-从应用开发到上架全流程解析
HarmonyOS Next是华为推出的最新版本鸿蒙操作系统,强调多设备协同和分布式技术,提供丰富的开发工具和API接口。本文详细解析了从应用开发到上架的全流程,包括环境搭建、应用设计与开发、多设备适配、测试调试、应用上架及推广等环节,并介绍了鸿蒙原生应用开发者激励计划,帮助开发者更好地融入鸿蒙生态。通过DevEco Studio集成开发环境和华为提供的多种支持工具,开发者可以轻松创建并发布高质量的鸿蒙应用,享受技术和市场推广的双重支持。
735 11
|
2月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
3月前
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
241 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
3月前
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
152 12
|
3月前
|
域名解析 弹性计算 负载均衡
新手上云教程参考:阿里云服务器租用、域名注册、备案及域名解析流程图文教程
对于想要在阿里云上搭建网站或应用的用户来说,购买阿里云服务器和注册域名,绑定以及备案的流程至关重要。本文将以图文形式为您介绍阿里云服务器购买、域名注册、备案及绑定的全流程,以供参考,帮助用户轻松上手。
|
5月前
|
监控 前端开发 API
一款基于 .NET MVC 框架开发、功能全面的MES系统
一款基于 .NET MVC 框架开发、功能全面的MES系统
126 5
|
5月前
|
域名解析 弹性计算 安全
阿里云服务器租用、注册域名、备案及域名解析完整流程参考(图文教程)
对于很多初次建站的用户来说,选购云服务器和注册应及备案和域名解析步骤必须了解的,目前轻量云服务器2核2G68元一年,2核4G4M服务器298元一年,域名注册方面,阿里云推出域名1元购买活动,新用户注册com和cn域名2年首年仅需0元,xyz和top等域名首年仅需1元。对于建站的用户来说,购买完云服务器并注册好域名之后,下一步还需要操作备案和域名绑定。本文为大家展示阿里云服务器的购买流程,域名注册、绑定以及备案的完整流程,全文以图文教程形式为大家展示具体细节及注意事项,以供新手用户参考。
|
7月前
|
JavaScript 前端开发 开发者
Vue执行流程及渲染解析
【10月更文挑战第2天】
160 58
|
6月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
130 12
|
6月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
515 2

热门文章

最新文章

推荐镜像

更多