• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
PowerCoder
博客园    首页    新随笔    联系   管理    订阅  订阅

ASP.NET Core 运行原理剖析 (转载)

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应用程序。

namespace aspnetcoreapp
{

    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel() //指定宿主程序为Kestrel
                .UseStartup<Startup>()// 调用Startup.cs类下的Configure 和 ConfigureServices
                .Build();

            host.Run();
        }

    }

}

以上是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请求。

namespace aspnetcoreapp
{
    public class Startup
    {

        public Startup(IConfiguration configuration)
        {

            Configuration = configuration;

        }

        public IConfiguration Configuration { get; }


        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
            
            app.UseStaticFiles();
            
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

    }

}

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的服务添加到容器中:

public void ConfigureServices(IServiceCollection services)
{

    services.AddMvc();

}

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

 

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

public void ConfigureServices(IServiceCollection services){

    services.AddKendo();

}

接下来,在Configure中设置Kendo UI

public void Configure(IApplicationBuilder app, IHostingEnvironment env){    //...

    app.UseKendo(env);

}

 

1.2.2. appsetting.json配置

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

配置appsetting文件:

{
  "key1": "字符串",
  "key2": 2,
  "key3":true,

  "parentObj": {
    "key1": "sub-key1"
  },

  "members": [
    {
      "name": "Lily",
      "age": "18"
    },

    {
      "name": "Lucy",
      "age": "17"
    }
  ]
}

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


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

public static IConfigurationRoot Configuration { get; set; }

public static void Main(string[] args = null)
{
    var builder = new ConfigurationBuilder()
          .SetBasePath(Directory.GetCurrentDirectory())
          .AddJsonFile("appsettings.json");

    Configuration = builder.Build();

    Console.WriteLine($"key1 = {Configuration["key1"]}");
    Console.WriteLine($"key2 = {Configuration["key2"]}");
    Console.WriteLine($"subkey1 = {Configuration["parentObj:key1"]}");

    Console.WriteLine();
    
    Console.WriteLine("members:");
    Console.Write($"{Configuration["members:0:name"]}, ");
    Console.WriteLine($"age {Configuration["members:0:age"]}");
    Console.Write($"{Configuration["members:1:name"]}, ");
    Console.WriteLine($"age {Configuration["members:1:age"]}");
    Console.WriteLine();

    Console.WriteLine("Press a key...");

    Console.ReadKey();
}

由于加载的是一个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中间件类:

public class CustomizedMiddleware
{
    private readonly RequestDelegate next;

    public CustomizedMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task Invoke(Microsoft.AspNetCore.Http.HttpContext context)
    {
        //Do something...
        await next.Invoke(context);
        //Do something...
    }
}

假如现在有三个请求发送到了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方法示例代码

public void Configure(IApplicationBuilder app, IHostingEnvironment env){

    app.Run(async context =>
    {            
            await context.Response.WriteAsync("environment " + env);
    });

}

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

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{

    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("environment " + env);
    });

}

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

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    //其他代码省略...

    app.Map("/HelpPage", p =>
    {
        //这里的代码只会在Startup的Configure方法中执行一次,相当于是给"/HelpPage"这个路径映射(mapping)处理逻辑用的,当真的Http请求到来的时候,并不会执行这里的代码

        //真正要被每次Http请求执行的逻辑应该写在下面的IApplicationBuilder.Run方法中
        p.Run(async context =>
        {
            //每次匹配路径"/HelpPage"的请求,都会执行这里的代码
            await context.Response.WriteAsync("Help page");
        });
    });

    //其他代码省略...
}

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

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

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    //其他代码省略...

    //Map之前注册的中间件UsePipelineLogger
    app.UsePipelineLogger();

    app.Map("/UserProfile", p =>
    {
        //这里的代码只会在Startup的Configure方法中执行一次,相当于是给"/UserProfile"这个路径映射(mapping)处理逻辑用的,当真的Http请求到来的时候,并不会执行这里的代码

        //真正要被每次Http请求执行的逻辑应该写在下面的IApplicationBuilder.UseUserProfile中间件中,这样每次匹配路径"/UserProfile"的请求,都会执行中间件里的代码
        p.UseUserProfile();
    });

    //Map之后注册的中间件UseStaticFiles
    app.UseStaticFiles();

    //其他代码省略...
}

关于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'来进入分支:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    private static void HandleBranch(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Branch used.");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.MapWhen(context => {
            return context.Request.Query.ContainsKey("branch");
        }, HandleBranch);
    }
}

 

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介绍链接

 

posted @ 2018-10-11 11:30  PowerCoder  阅读(2741)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3