原文:https://www.stevejgordon.co.uk/asp-net-core-anatomy-part-3-addmvc
发布于:2017年4月
环境:ASP.NET Core 1.1json
本系列前面两篇文章介绍了ASP.NET Core中IServiceCollection两个主要扩展方法(AddMvcCore与AddMvc)。当你准备在程序中使用MVC中间件时,它们用来添加所需的MVC服务。数组
接下来,要在ASP.NET Core程序中启动MVC还须要在Startup类的Configure方法中执行UseMvc IApplicationBuilder扩展方法。该方法注册MVC中间件到应用程序管道中,以便MVC框架可以处理请求并返回响应(一般是view result或json)。本文我将分析一下在应用程序启动时UserMvc方法作了什么。mvc
和先前文章同样,我使用rel/1.1.2 MVC版本库做为分析对象。代码是基于原来的project.json,由于在VS2017中彷佛没有简单办法实现调试多个ASP.NET Core源代码。app
UseMvc是IApplicationBuilder的一个扩展方法,带有一个Action<IRouteBuilder>委托参数。IRouteBuilder将被用于配置MVC的路由。UserMvc还有一个重载方法,不须要任何参数,它只是简单的传递一个空委托调用主函数。以下:框架
public static IApplicationBuilder UseMvc(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } return app.UseMvc(routes => { }); }
主UseMvc方法:ide
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)); } // Verify if AddMvc was done before calling UseMvc // We use the MvcMarkerService to make sure if all the services were added. if (app.ApplicationServices.GetService(typeof(MvcMarkerService)) == null) { throw new InvalidOperationException(Resources.FormatUnableToFindServices( nameof(IServiceCollection), "AddMvc", "ConfigureServices(...)")); } var middlewarePipelineBuilder = app.ApplicationServices.GetRequiredService<MiddlewareFilterBuilder>(); middlewarePipelineBuilder.ApplicationBuilder = app.New(); 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()); }
咱们来分析一下此方法。首先检查IServiceProvider中是否有MvcMarkerService服务注册。为此使用ApplicationServices属性暴露IServiceProvider,再调用GetService,尝试查找已注册的MvcMarkerService。MvcMarkerService在AddMvcCore执行时已被注册,所以若是没有找到,则代表在ConfigureServices执行前没有调用过AddMvc或AddMvcCore。这样的Marker Services在不少地方使用,它能够帮助在代码执行以前,检查是否存已存在正确的依赖关系。函数
接下来,UserMvc从ServiceProvider请求一个MiddlewareFilterBuilder,并使用IApplicationBuilder.New()方法设定其ApplicationBuilder。当调用此方法时,ApplicationBuilder会建立并返回自身的新实例副本。ui
再下来,UserMvc初始化一个新的RouteBuilder,同时设定默认处理程序(default handler)为已注册的MvcRouteHandler。此时DI就像有魔力,一堆依赖对象开始实例化。这里请求MvcRouteHandler是由于其构造函数中有一些依赖关系,因此相关的其它类也会被初始化。每一个这些类的构造函数都会请求额外的依赖关系,而后建立它们。这是DI系统工做的一个完美例子。虽然在ConfigureServices中全部的接口和具体实现已在container中注册,但实际对象只会在被注入时建立。经过建立RouteBuilder,对象建立就像滚雪球,直到全部依赖关系都被构造。一些对象做为单例模式注册,不管ServiceProvider是否请求都会建立,其生命周期贯穿整个应用程序。其它对象多是在每次请求时经过构造函数建立或者其它状况,每次建立的对象都是特有的。了解依赖注入是如何工做的,特别是ASP.NET Core ServiceProvider的详细信息超出了这篇文章的范围。this
RouteBuilder建立以后,Action<IRouteBuilder>委托被调用,使用RouteBuilder做为惟一参数。在MvcSandbox示列中,咱们称之为UseMvc方法,经过一个lambda传递给代理方法。此功能将映射一个名为“default”的路由,以下所示:编码
app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });
MapRoute是IrouteBuilder的一个扩展方法,主要的MapRoute方法以下所示:
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; }
这将请求一个由ServiceProvider解析的IinlineConstraintResolver实例给DefaultInlineConstraintResolver。该类使用IOptions <RouteOptions>参数初始化构造函数。
Microsoft.Extensions.Options程序集调用RouteOptions做为参数的MvcCoreRouteOptionsSetup.Configure方法。这将添加一个KnownRouteValueConstraint类型的约束映射到约束映射字典。当首次构建RouteOptions时,该字典将初始化多个默认约束。我会在之后的文章中详细介绍路由代码。
经过提供的名称和模板构建一个新的Route对象。在咱们的例子中,新对象添到了RouteBuilder.Routes列表中。至此,在MvcSandbox示列程序运行时,咱们的router builder就有了一条路由记录。
请注意,MvcApplicationBuilderExtensions类还包括一个名为UseMvcWithDefaultRoute的扩展方法。此方法会调用UseMvc,经过硬编码设定默认路由。
public static IApplicationBuilder UseMvcWithDefaultRoute(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } return app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
该路由使用与MvcSandbox程序中定义的相同名称和模板进行定义。所以,它可能被用做MvcSandbox Startup类中的轻微代码保护程序(slight code saver)。对于最基本的Mvc程序,使用该扩展方法可能足以知足路由的需求。但在大多实际使用场景中,我相信你会传递一套更加完整的路由。这是一个很好的方式(shorthand),若是你想从基本路由模板开始,就能够直接调用UseMvc()而不使用任何参数。
接下来,调用静态AttributeRouting.CreateAttributeMegaRoute方法,同时把生成的路由添加到RouteBuilder中的Routes List(索引位置为0)。CreateAttributeMegaRoute已在“Creates an attribute route using the provided services and provided target router”文中讲述,它看起来像这样:
public static IRouter CreateAttributeMegaRoute(IServiceProvider services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } return new AttributeRoute( services.GetRequiredService<IActionDescriptorCollectionProvider>(), services, actions => { var handler = services.GetRequiredService<MvcAttributeRouteHandler>(); handler.Actions = actions; return handler; }); }
该方法建立了一个实现了IRoute接口的新AttributeRoute。其构造函数看起来想象这样:
public AttributeRoute( IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, IServiceProvider services, Func<ActionDescriptor[], IRouter> handlerFactory) { if (actionDescriptorCollectionProvider == null) { throw new ArgumentNullException(nameof(actionDescriptorCollectionProvider)); } if (services == null) { throw new ArgumentNullException(nameof(services)); } if (handlerFactory == null) { _handlerFactory = handlerFactory; } _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; _services = services; _handlerFactory = handlerFactory; }
该构造函数须要一个IactionDescriptorCollectionProvider,一个IServiceProvider和一个接受ActionDescriptor数组参数,并返回IRouter的Func。默认状况下,经过AddMvc注册服务时将得到一个ActionDescriptorCollectionProvider实例,它是一个经过ServiceProvider注册的单例(singleton)对象。ActionDescriptors表示在应用程序中建立和发现可用的MVC actions。这些对象咱们另文分析。
建立新的AttributeRoute代码(CreateAttributeMegaRoute方法内部)使用lambda来定义Func <ActionDescriptor [],IRouter>的代码。在这种状况下,委托函数从ServiceProvider请求一个MvcAttributeRouteHandler。由于被注册为transient,因此每次请求ServiceProvider将返回一个新的MvcAttributeRouteHandler实例。委托代码而后使用传入的ActionDescriptions数组在MvcAttributeRouteHandler上设置Actions属性(ActionDescriptions的数组),最后返回新的处理程序。
返回UserMvc,IapplicationBuilder中的UseRouter扩展方法完成调用。传递给UseRouter的对象是经过调用RouteBuilder的Build方法来建立的。该方法将添加路由到路由集合。RouteCollection内部将追踪哪些是已命名,哪些是未命名。在咱们的MvcSandbox示例中,咱们会获得一个命名为“default”的路由和一个未命名的AttributeRoute。
UseRouter有两个签名。此时咱们传递一个IRouter,因此调用如下方法:
public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (router == null) { throw new ArgumentNullException(nameof(router)); } if (builder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null) { throw new InvalidOperationException(Resources.FormatUnableToFindServices( nameof(IServiceCollection), nameof(RoutingServiceCollectionExtensions.AddRouting), "ConfigureServices(...)")); } return builder.UseMiddleware<RouterMiddleware>(router); }
该方法实现检查ServiceProvider中的RoutingMarkerService。假设这是按预期注册的,它将RouterMiddleware添加到中间件管道(middeware pipeline)中。正是这个中间件,使用IRouter来尝试将控制器的请求和MVC中的动做(action)进行匹配,以便处理它们。这个过程的细节将在将来的博文中展示。
到此,应用程序管道已配置,应用程序已准备好接收请求。本文也能够结束了。
本文咱们分析了UseMvc而且看到它设置了将在稍后使用的MiddlewareFilterBuilder。而后它还经过RouteBuilder得到一个IRouter,在这个阶段的大部分工做都是注册路由。一旦设置完成,路由中间件就被注册到管道中。这个代码(中间件)知道如何检查传入的请求,并将路径映射到适当的控制器和能够处理它们的操做。
configureRoutes(routes);
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
return app.UseRouter(routes.Build());
}