1.1. 概述

在ASP.NET Core之前,ASP.NET Framework应用程序由IIS加载。Web应用程序的入口点由InetMgr.exe创建并调用托管。以初始化过程中触发HttpApplication.Application_Start()事件。开发人员第一次执行代码的机会是处理Application_StartGlobal.asax中的事件。在ASP.NET Core中,Global.asax文件不再可用,已被新的初始化过程替代。

ASP.NET Core 应用程序是在.NET Core 控制台程序下调用特定的库,这是ASP.NET Core应用程序开发的根本变化。所有的ASP.NET托管库都是从Program开始执行,而不是由IIS托管。也就是说.NET工具链可以同时用于.NET Core控制台应用程序和ASP.NET Core应用程序。

  1. namespace aspnetcoreapp
  2. {
  3.  
  4. public class Program
  5. {
  6. public static void Main(string[] args)
  7. {
  8. var host = new WebHostBuilder()
  9. .UseKestrel() //指定宿主程序为Kestrel
  10. .UseStartup<Startup>()// 调用Startup.cs类下的Configure 和 ConfigureServices
  11. .Build();
  12.  
  13. host.Run();
  14. }
  15.  
  16. }
  17.  
  18. }

以上是Program类中Main方法的示例代码,Main方法负责初始化Web主机,调用Startup和执行应用程序。主机将调用Startup类下面的Configure和ConfigureServices方法。

1.2. 文件配置

1.2.1. Starup文件配置

对于一个ASP.NET Core 程序而言,Startup 类是必须的。ASP.NET Core在程序启动时会从Program类中开始执行,然后再找到UseStartup<Startup>中找到配置的Startup的类,如果不指定Startup类会导致启动失败。

在Startup中必须定义Configure方法,而ConfigureServices方法则是可选的,方法会在程序第一次启动时被调用,类似传统的ASP.NET MVC的路由和应用程序状态均可在Startup中配置,也可以在此初始化所需中间件。

Configure
在ASP.NET Core 应用程序中Configure方法用于指定中间件以什么样的形式响应HTTP请求。

  1. namespace aspnetcoreapp
  2. {
  3. public class Startup
  4. {
  5.  
  6. public Startup(IConfiguration configuration)
  7. {
  8.  
  9. Configuration = configuration;
  10.  
  11. }
  12.  
  13. public IConfiguration Configuration { get; }
  14.  
  15. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  16. {
  17.  
  18. if (env.IsDevelopment())
  19. {
  20. app.UseDeveloperExceptionPage();
  21. app.UseBrowserLink();
  22. }
  23. else
  24. {
  25. app.UseExceptionHandler("/Home/Error");
  26. }
  27.  
  28. app.UseStaticFiles();
  29.  
  30. app.UseMvc(routes =>
  31. {
  32. routes.MapRoute(
  33. name: "default",
  34. template: "{controller=Home}/{action=Index}/{id?}");
  35. });
  36. }
  37.  
  38. }
  39.  
  40. }

ASP.NET Core是通过对IApplicationBuilder进行扩展来构建中间件的, 上面代码中每个use扩展方法都是将中间件添加到请求管道。也可以给Configure方法附加服务(如:IHostingEnvironment)这些服务在ConfigureServices方法中被初始化。

用ASP.NET Core项目模板添加的应用程序,默认添加的几个中间件:

  • UseStaticFiles 允许应用程序提供静态资源。
  • UseMvc 将MVC添加到管道并允许配置路由。

ConfigureServices
ConfigureServices方法是应用程序运行时将服务添加到容器中,其实就是注册ASP.NET Core中Configure方法、中间件及Controller等地方需要用到的依赖注入关系,用ASP.NET Core项目模板的时候默认会将MVC的服务添加到容器中:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3.  
  4. services.AddMvc();
  5.  
  6. }

接下来举一个例子,在实际应用中ConfigureServices方法和Configure方法配合使用,在ASP.NET Core中有一个UI开发框架Telerik UI for ASP.NET Core,它有60多个UI组件,不仅支持ASP.NET Core的跨平台布署模式,而且还支持前端自适应渲染。

当在项目中应用Telerik UI的时候,首先在项目中引用相关的包,然后再在ConfigureServices方法中将Kendo UI服务添加到容器中:

  1. public void ConfigureServices(IServiceCollection services){
  2.  
  3. services.AddKendo();
  4.  
  5. }

接下来,在Configure中设置Kendo UI

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env){ //...
  2.  
  3. app.UseKendo(env);
  4.  
  5. }

1.2.2. appsetting.json配置

Configuration API 提供了一个基于键-值对来配置应用程序的方法,在运行时可以从多个来源来读取配置。键-值对可以分组形成多层结构。键-值对可以配置在不同的地方,如:文件、内存等,其中放在内存中不能持久化,这里笔者选择将其配置在appsetting.json文件里面。

配置appsetting文件:

  1. {
  2. "key1": "字符串",
  3. "key2": 2,
  4. "key3":true,
  5.  
  6. "parentObj": {
  7. "key1": "sub-key1"
  8. },
  9.  
  10. "members": [
  11. {
  12. "name": "Lily",
  13. "age": "18"
  14. },
  15.  
  16. {
  17. "name": "Lucy",
  18. "age": "17"
  19. }
  20. ]
  21. }

一个分层结构的JSON文件,键(如:key1)作为索引器,值作为参数,类型可以为:字符串、数字、布尔、对象、数组。下面具体来看下在应用中怎样使用。

在应用程序加加载和应用配置文件

  1. public static IConfigurationRoot Configuration { get; set; }
  2.  
  3. public static void Main(string[] args = null)
  4. {
  5. var builder = new ConfigurationBuilder()
  6. .SetBasePath(Directory.GetCurrentDirectory())
  7. .AddJsonFile("appsettings.json");
  8.  
  9. Configuration = builder.Build();
  10.  
  11. Console.WriteLine($"key1 = {Configuration["key1"]}");
  12. Console.WriteLine($"key2 = {Configuration["key2"]}");
  13. Console.WriteLine($"subkey1 = {Configuration["parentObj:key1"]}");
  14.  
  15. Console.WriteLine();
  16.  
  17. Console.WriteLine("members:");
  18. Console.Write($"{Configuration["members::name"]}, ");
  19. Console.WriteLine($"age {Configuration["members::age"]}");
  20. Console.Write($"{Configuration["members::name"]}, ");
  21. Console.WriteLine($"age {Configuration["members::age"]}");
  22. Console.WriteLine();
  23.  
  24. Console.WriteLine("Press a key...");
  25.  
  26. Console.ReadKey();
  27. }

由于加载的是一个JSON文件,所以文件加载进来以后程序可以直接将它当作一个JSON对象来使用。如果有过动态语言使用经验的同学来说这种方式就比较熟悉了。只在这里访问属性的时候将平时常见的.变成了:,这和写的JSON对象更接近。

1.3. 处理管道(中间件)

在ASP.NET Core应用程序中使用中间件,应用程序所做的任何事情(包括服务器中的静态文件)都是由中间件来完成的。没有任何中间件的应用程序在请求的出错时候简单返回404 Not Found。中间件可以让您完全控制请求的处理方式,并且让您的应用程序更加精简。

当接收到一个请求时,请求会交给中间件构成的中间件管道进行处理,管道就是多个中间件构成,请求从一个中间件的一端进入,从中间件的另一端出来,每个中间件都可以对HttpContext请求开始和结束进行处理:

另外,需要注意的是,每当有请求到达ASP.NET Core服务器时,虽然ASP.NET Core的中间件(Middleware)都会按照注册顺序依次执行(如上面图所示),但是ASP.NET Core的中间件只会创建一次,并不是每当有请求到达ASP.NET Core服务器时,中间件也都会被重新创建。
举个例子,假如我们有下面CustomizedMiddleware中间件类:

  1. public class CustomizedMiddleware
  2. {
  3. private readonly RequestDelegate next;
  4.  
  5. public CustomizedMiddleware(RequestDelegate next)
  6. {
  7. this.next = next;
  8. }
  9.  
  10. public async Task Invoke(Microsoft.AspNetCore.Http.HttpContext context)
  11. {
  12. //Do something...
  13. await next.Invoke(context);
  14. //Do something...
  15. }
  16. }

假如现在有三个请求发送到了ASP.NET Core服务器,那么上面CustomizedMiddleware类中的Invoke方法相应地会执行三次,分别处理三个请求,但是CustomizedMiddleware类的构造函数只会执行一次,因为ASP.NET Core并不会为每次请求都重新创建CustomizedMiddleware中间件,每次请求都会复用ASP.NET Core进程中已经创建好的CustomizedMiddleware中间件对象实例。

在ASP.NET Core中可以用Run、Map和Use三种方式来配置HTTP管道。Run 方法称为短路管道(因为它不会调用 next 请求委托)。因此,Run方法一般在管道尾部被调用。Run 是一种惯例,有些中间件组件可能会暴露他们自己的 Run方法,而这些方法只能在管道末尾处运行。下面两段代码是等效的,因为Use没有调用next方法

Run方法示例代码

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env){
  2.  
  3. app.Run(async context =>
  4. {
  5. await context.Response.WriteAsync("environment " + env);
  6. });
  7.  
  8. }

Use方法不执行next时示例代码

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  2. {
  3.  
  4. app.Use(async (context, next) =>
  5. {
  6. await context.Response.WriteAsync("environment " + env);
  7. });
  8.  
  9. }

在.NET Core 中约定了Map*扩展被用于分支管道,当前的实现已支持基于请求路径或使用谓词来进入分支。Map扩展方法用于匹配基于请求路径的请求委托。Map只接受路径,并配置单独的中间件管道的功能。

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  2. {
  3. //其他代码省略...
  4.  
  5. app.Map("/HelpPage", p =>
  6. {
  7. //这里的代码只会在Startup的Configure方法中执行一次,相当于是给"/HelpPage"这个路径映射(mapping)处理逻辑用的,当真的Http请求到来的时候,并不会执行这里的代码
  8.  
  9. //真正要被每次Http请求执行的逻辑应该写在下面的IApplicationBuilder.Run方法中
  10. p.Run(async context =>
  11. {
  12. //每次匹配路径"/HelpPage"的请求,都会执行这里的代码
  13. await context.Response.WriteAsync("Help page");
  14. });
  15. });
  16.  
  17. //其他代码省略...
  18. }

上例中是一个用Map方法来接受路径进入分支管道,也就是说所有基于/HelpPage路径请求都会被管道中的p.Run方法所处理。

同样我们也可以在Map方法中使用其它中间件:

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  2. {
  3. //其他代码省略...
  4.  
  5. //Map之前注册的中间件UsePipelineLogger
  6. app.UsePipelineLogger();
  7.  
  8. app.Map("/UserProfile", p =>
  9. {
  10. //这里的代码只会在Startup的Configure方法中执行一次,相当于是给"/UserProfile"这个路径映射(mapping)处理逻辑用的,当真的Http请求到来的时候,并不会执行这里的代码
  11.  
  12. //真正要被每次Http请求执行的逻辑应该写在下面的IApplicationBuilder.UseUserProfile中间件中,这样每次匹配路径"/UserProfile"的请求,都会执行中间件里的代码
  13. p.UseUserProfile();
  14. });
  15.  
  16. //Map之后注册的中间件UseStaticFiles
  17. app.UseStaticFiles();
  18.  
  19. //其他代码省略...
  20. }

关于Map方法需要注意,上面的代码由于在app.Map调用之前我们注册了app.UsePipelineLogger这个中间件,所以如果现在有一个Http请求匹配Url路径/UserProfile,那么会先执行UsePipelineLogger中间件,然后ASP.NET Core的Pipeline就跳入app.Map中的分支了,所以执行完app.Map中的UseUserProfile中间件后,将不会再执行在app.Map后注册的中间件app.UseStaticFiles,所以需要注意如果Url匹配路径/UserProfile成功,app.Map之后注册的中间件会被忽略。

如果想用谓词来进入中间件分支,则要使用MapWhen方法。MapWhen方法允许以一种非常灵活的方式构建中间管道。比如可以检测查询字符串是否具有'branch'来进入分支:

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  2. {
  3. private static void HandleBranch(IApplicationBuilder app)
  4. {
  5. app.Run(async context =>
  6. {
  7. await context.Response.WriteAsync("Branch used.");
  8. });
  9. }
  10.  
  11. public void Configure(IApplicationBuilder app)
  12. {
  13. app.MapWhen(context => {
  14. return context.Request.Query.ContainsKey("branch");
  15. }, HandleBranch);
  16. }
  17. }

1.4 总结

  • 本文讲解了ASP.NET Core在运行时首先加载Program类下面的Main方法,在Main方法中指定托管服务器,并调用Startup类中的Configure和ConfigureServices方法等完成初始化
  • 在ASP.NET Core中 HTTP请求是以中间件管道的形式进行处理,每个中间件都可以在HTTP请求开始和结束处理对它进行处理。
  • ASP.NET Core可以构建跨平台应用,服务运行在Http.Sys(仅适用于Windows平台)和Kestrel上,不需要用IIS进行托管,所以相比传统ASP.NET来说性能更高效也更加灵活。

原文链接

ASP.NET Core 中间件 MSDN介绍链接

ASP.NET Core 运行原理剖析 (转载)的更多相关文章

  1. ASP.NET Core 运行原理剖析

    1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...

  2. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

  3. ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行

    ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...

  4. ASP.NET Core 运行原理解剖[1]:Hosting

    ASP.NET Core 是新一代的 ASP.NET,第一次出现时代号为 ASP.NET vNext,后来命名为ASP.NET 5,随着它的完善与成熟,最终命名为 ASP.NET Core,表明它不是 ...

  5. ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍

    在上一章中,我们介绍了 ASP.NET Core 的启动过程,主要是对 WebHost 源码的探索.而本文则是对上文的一个补充,更加偏向于实战,详细的介绍一下我们在实际开发中需要对 Hosting 做 ...

  6. ASP.NET Core 运行原理解剖[3]:Middleware-请求管道的构成

    在 ASP.NET 中,我们知道,它有一个面向切面的请求管道,有19个主要的事件构成,能够让我们进行灵活的扩展.通常是在 web.config 中通过注册 HttpModule 来实现对请求管道事件监 ...

  7. ASP.NET Core 运行原理解剖[4]:进入HttpContext的世界

    HttpContext是ASP.NET中的核心对象,每一个请求都会创建一个对应的HttpContext对象,我们的应用程序便是通过HttpContext对象来获取请求信息,最终生成响应,写回到Http ...

  8. ASP.NET Core 运行原理解剖[5]:Authentication

    在现代应用程序中,认证已不再是简单的将用户凭证保存在浏览器中,而要适应多种场景,如App,WebAPI,第三方登录等等.在 ASP.NET 4.x 时代的Windows认证和Forms认证已无法满足现 ...

  9. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

随机推荐

  1. 解决JVM内存溢出问题

    今天遇到了一个问题,当我在增加配置文件(*.xml)内容的时候,重新启动tomcat6时,控制台报错:java.lang.StackOverflowError: 即,栈溢出错误. 内存溢出,即程序运行 ...

  2. 全面理解Java内存模型(JMM)及volatile关键字(转)

    原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...

  3. PL/SQL: numeric or value error: character to number conversion error

    在最简单的plsql块编程中出现这个错误,是因为 DBMS_OUTPUT.PUT_LINE('the x is '+x);这里面不能用“+”,而是要用“||” DECLARE x number; ; ...

  4. jQuery源码分析系列 : 整体架构

    query这么多年了分析都写烂了,老早以前就拜读过, 不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery又给扫一遍 我也不会照本宣科的翻译源码,结合自己的实际经验一起拜读吧! ...

  5. IndexedDB(一:基本使用)

    在HTML5本地存储--Web SQL Database提到过Web SQL Database实际上已经被废弃,而HTML5的支持的本地存储实际上变成了 Web Storage(Local Stora ...

  6. AngularJS之控制器

    控制器在Angularjs中的作用是增强视图,它实际就是一个函数,用来向视图中的作用域添加额外的功能,我们用它来给作用域对象设置初始状态,并添加自定义行为. 当我们在页面上创建一个控制器时,Angul ...

  7. cnpm 安装

    国内npm 安装比较慢,可选择cnpm npm install -g cnpm --registry=https://registry.npm.taobao.org

  8. 解决 spring cloud 项目的 com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect 错误信息

    在项目中引入:引入hystrix依赖,如下 <dependency> <groupId>org.springframework.cloud</groupId> &l ...

  9. 第四次作业-第一次scrum冲刺

    团队成员 周斌 舒 溢 许嘉荣 唐 浩 黄欣欣 1.第一次冲刺任务安排 对Github上的HUSTOJ开源项目进行Fork,搭建基本环境 2.用户需求 ①基本功能显示在首页 ②能够提交题目并判题,并对 ...

  10. CefSharp开发

    CefSharp是用chromium内核开发的.net版本浏览器工具.目前只支持X86模式.所以在调试的时候要把平台改为X86 CefSharp开发指引:https://ourcodeworld.co ...