剖析ASP.NET Core(Part 3)- UseMvc(译)
原文:https://www.stevejgordon.co.uk/asp-net-core-anatomy-part-3-addmvc
发布于:2017年4月
环境:ASP.NET Core 1.1
本系列前面两篇文章介绍了ASP.NET Core中IServiceCollection两个主要扩展方法(AddMvcCore与AddMvc)。当你准备在程序中使用MVC中间件时,它们用来添加所需的MVC服务。
接下来,要在ASP.NET Core程序中启动MVC还需要在Startup类的Configure方法中执行UseMvc IApplicationBuilder扩展方法。该方法注册MVC中间件到应用程序管道中,以便MVC框架能够处理请求并返回响应(通常是view result或json)。本文我将分析一下在应用程序启动时UserMvc方法做了什么。
和先前文章一样,我使用rel/1.1.2 MVC版本库作为分析对象。代码是基于原来的project.json,因为在VS2017中似乎没有简单办法实现调试多个ASP.NET Core源代码。
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方法:
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(, 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会创建并返回自身的新实例副本。
再下来,UserMvc初始化一个新的RouteBuilder,同时设定默认处理程序(default handler)为已注册的MvcRouteHandler。此时DI就像有魔力,一堆依赖对象开始实例化。这里请求MvcRouteHandler是因为其构造函数中有一些依赖关系,所以相关的其它类也会被初始化。每个这些类的构造函数都会请求额外的依赖关系,然后创建它们。这是DI系统工作的一个完美例子。虽然在ConfigureServices中所有的接口和具体实现已在container中注册,但实际对象只会在被注入时创建。通过创建RouteBuilder,对象创建就像滚雪球,直到所有依赖关系都被构造。一些对象作为单例模式注册,无论ServiceProvider是否请求都会创建,其生命周期贯穿整个应用程序。其它对象可能是在每次请求时通过构造函数创建或者其它情况,每次创建的对象都是特有的。了解依赖注入是如何工作的,特别是ASP.NET Core ServiceProvider的详细信息超出了这篇文章的范围。
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());
}
剖析ASP.NET Core(Part 3)- UseMvc(译)的更多相关文章
- 剖析ASP.NET Core(Part 2)- AddMvc(译)
原文:https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore发布于:2017年3月环境:ASP.NET Core 1.1 ...
- 剖析ASP.NET Core(Part 4)- 调用MVC中间件(译)
原文:https://www.stevejgordon.co.uk/invoking-mvc-middleware-asp-net-core-anatomy-part-4 发布于:2017年5月环境: ...
- 剖析ASP.NET Core MVC(Part 1)- AddMvcCore(译)
原文:https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore发布于:2017年3月环境:ASP.NET Core 1.1 ...
- [译]ASP.NET Core 2.0 系列文章目录
基础篇 [译]ASP.NET Core 2.0 中间件 [译]ASP.NET Core 2.0 带初始参数的中间件 [译]ASP.NET Core 2.0 依赖注入 [译]ASP.NET Core 2 ...
- .Net Core 学习 (1) - ASP.NET Core 总览
什么是ASP.NET 1.0 开源 - GitHub 跨平台 - 支持Windows, Mac, Linux 从底层进行了优化 - 使用最小开销的模块化组件 - 给与了开发人员很大的灵活性 为什么要使 ...
- [ASP.NET Core 2.0 前方速报]Core 2.0.3 已经支持引用第三方程序集了
发现问题 在将 FineUIMvc(支持ASP.NET MVC 5.2.3)升级到 ASP.NET Core 2.0 的过程中,我们发现一个奇怪的现象: 通过项目引用 FineUICore 工程一切正 ...
- 《ASP.NET Core项目开发实战入门》带你走进ASP.NET Core开发
<ASP.NET Core项目开发实战入门>从基础到实际项目开发部署带你走进ASP.NET Core开发. ASP.NET Core项目开发实战入门是基于ASP.NET Core 3.1 ...
- ASP.NET Core 运行原理剖析
1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...
- C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志
C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...
随机推荐
- java 内部类和静态内部类的区别
private class InnerClass { // 只有在静态内部类中才能够声明或定义静态成员 // private static String tt = &quo ...
- Eclipse IDE下的Spring框架使用简单实例
Eclipse IDE下的Spring框架使用简单实例 1 准备Java jdk安装. Eclipse软件安装.根据系统安装32/64版本,选择Eclipse IDE for Java Develop ...
- 论文笔记-RCNN
CAFFE玩了也有段时间了,现在开始准备研究一下物体检测,现在知道的有RCNN.spp-net.Fast-RCNN和Faster-RCNN,作为菜鸟我还是从头学习,决定先看RCNN,因为有项目要做还要 ...
- HDU 6186 CS Course【前后缀位运算枚举/线段树】
[前后缀枚举] #include<cstdio> #include<string> #include<cstdlib> #include<cmath> ...
- 洛谷P1074 靶形数独 [搜索]
题目传送门 题目描述 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他 们想用数独来一比高低.但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教, Z 博士拿出了 ...
- 01Trie【p4551(poj3764)】 最长异或路径
题目描述 给定一棵 n 个点的带权树,结点下标从 1 开始到 N .寻找树中找两个结点,求最长的异或路径. 异或路径指的是指两个结点之间唯一路径上的所有边权的异或. 个人: 首先强推一下01字典树(T ...
- 洛谷—— P1908 逆序对
https://www.luogu.org/problem/show?pid=1908 题目描述 猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏, ...
- 03、NavMesh--导航网格寻路
一.概述: NavMesh是3D游戏世界中用于实现动态物体自动寻路的一种技术,他将游戏场景中复杂的结构组织关系简化为带有一定信息的网格, 进而在这些网格的基础上通过一些列的计算来实现自动寻路. 二.简 ...
- 【数位dp】UVA - 11361 - Investigating Div-Sum Property
经典数位dp!而且这好像是数位dp的套路板子……不需要讨论原来我很头疼的一些边界. 改天用这个板子重做一下原来的一些数位dp题目. http://blog.csdn.net/the_useless/a ...
- 给lnmp一键包中的nginx安装openresty的lua扩展
lnmp一键包(https://lnmp.org)本人在使用之后发现确实好用,能帮助我们快速搭建起lnmp.lamp和lnmpa的web生产环境,因此推荐大家可以多试试.但有的朋友可能需要使用open ...