ASP.NET Core基本原理(2)-中间件

ASP.NET Core基本原理(2)-中间件

原文链接:Application Startup

什么是中间件

中间件是装配到应用管道中用来处理请求和响应的软件组件。管道中的每一个组件都可以选择是否将请求移交给下一个组件,并且可以在管道中调用下一个组件之前或者之后执行指定的操作。请求委托被用于构建请求管道。请求委托会处理每一个HTTP请求。

请求委托通过在传递给Startup类中的Configure方法的IApplicationBuilder类型上使用Run,Map,Use扩展方法进行配置。一个单独的请求委托可以被指定为一个内嵌的匿名方法,或定义在一个可重用的类中。这些可重用的类就是中间件中间件组件。每个请求管道中的中间件负责调用管道中的下一个组件,或者在适当的时候将调用链短路。

通过IApplicationBuilder创建中间件管道

ASP.NET请求管道由一系列的请求委托所组成,一个接着一个被调用,如图所示,执行线程跟随着黑色箭头:

每一个委托在下一个委托调用之前或之后都有机会去执行操作。任何委托都可以选择停止传递请求到下一个委托,而自己处理该请求。这就是请求管道的短路,这种设计可以避免一些不必要的工作。例如,一个授权(Authorization)中间件只有在请求通过身份验证之后才能调用下一个委托;否则它就会被短路并返回"Not Authorized"的响应。异常处理委托必须在管道的早期被调用,这样它们就可以捕获到发生在管道内更深层次的异常。

打开默认的网站模板,Configure方法添加了如下中间件组件:

  1. 错误处理(针对开发环境和非开发环境)
  2. 静态文件服务器
  3. 身份验证
  4. MVC
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseIdentity();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

上述代码(在非开发环境),UseExceptionHandler是第一个被添加到管道的中间件,因此会捕获之后调用中出现的任何异常。

Static File Module提供了无需授权检查的功能,由它服务的任何文件,包含在那些位于wwwroot文件夹中的文件都是可被公开访问的。如果你想让基于授权来提供这些文件:

  1. 将它们存放在wwwroot文件夹之外以及静态文件中间件可以访问的任何目录。
  2. 交给控制器方法,通过返回一个FileResult表示授权被应用的地方。

被静态文件模块处理的请求会在管道中被短路。如果请求不是通过静态文件模块来处理,那么它会被传给Identity module来执行身份验证。如果请求未通过验证,则管道将被短路。否则,将会调用管道的最后一站-MVC框架。

注:你添加中间件的顺序通常会影响它们对请求产生影响的顺序,然后在响应时会以相反的顺序。这对应用程序的安全、性能和功能都非常关键。在上面的代码中,Static File Module在管道的早期被调用,这样可以及时短路,避免了请求进行到不必要的组件中。身份验证中间件在任何需要身份验证的处理请求之前被添加进来。异常处理必须在其它中间件之前被注册以便捕获其它组件的异常。

最简单的ASP.NET应用设置一个简单的请求委托来处理所有的请求。这样就不是一个真实的请求管道,通过调用一个简单的匿名函数来响应每一个HTTP请求。

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello, World!");
});

App.Run委托会终止管道,下面的盒子中,只有第一个委托会被运行。

public void Configure(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello, World!");
    });

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello, World, Again!");
    });
}

将多个请求委托链接在一起,next参数表示管道内的下一个委托。你可以通过不调用next参数来终止(短路)管道。你通常可以在调用下一个委托之前和之后执行操作:

public void ConfigureLogInline(IApplicationBuilder app, ILoggerFactory loggerfactory)
{
    loggerfactory.AddConsole(minLevel: LogLevel.Information);
    var logger = loggerfactory.CreateLogger(_environment);
    app.Use(async (context, next) =>
    {
        logger.LogInformation("Handling request.");
        await next.Invoke();
        logger.LogInformation("Finished handling request.");
    });

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

当应用程序运行的环境设置成LogInline时,ConfigureLogInline方法会被调用。

在上述例子中,调用await next.Invoke()将会调用下一个委托await context.Response.WriteAsync("Hello from " + _environment);。客户端会收到所期望的响应("Hello from LogInline")。

Run、Map和Use

你可以通过Run,MapUse来配置HTTP管道。Run方法将会短路管道(因为它不会调用next请求委托),因此Run方法应该只在管道结尾被调用。Run方法是一种惯例,有些中间件也会暴露它们自己的Run[Middleware]方法,你也必须在管道的末尾进行运行。下面两个中间件是相同的,其中一个使用Use版本的没有使用到next参数:

public void ConfigureEnvironmentOne(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

public void ConfigureEnvironmentTwo(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

Map*扩展被用于分支管道,当前的实现支持基于请求路径或使用谓词进行分支。Map扩展方法被用于匹配基于请求路径的请求委托。Map只接受一个路径和配置了一个单独中间件的管道的功能。在下面的例子中,任何基于/maptest基本路径的请求都会被HandleMapTest方法中所配置的管道所处理。

private static void HandleMapTest(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test Successful");
    });
}

public void ConfigureMapping(IApplicationBuilder app)
{
    app.Map("/maptest", HandleMapTest);

}

当使用了Map,每一个请求所匹配的路径段将从HttpRequest.Path中移除,并附加到HttpRequest.PathBase中。

除了基于路径的映射外,MapWhen方法还支持基于谓词的中间件分支,允许以一种非常灵活的方式构造单独的管道。任何Func<HttpContext, bool>类型的谓词都可被用于将请求映射到一个新的管道分支。在下面的例子中,一个简单的谓词被用来检测字符变量branch是否存在:

private static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Branch used.");
    });
}

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

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

上述配置中,任何包含branch的查询字符串的请求将使用定义在HandleBranch方法中的管道(将得到"Branch used."的响应)。其它请求(不包含branch的查询字符串的请求)将被await context.Response.WriteAsync("Hello from " + _environment);所定义的委托所处理。

内置中间件

ASP.NET带来了下列中间件组件:

中间件 �描述 � � � � � � � � � � � � � � � � �
身份验证(Authentication) �提供身份验证支持
跨域资源共享(CORS) �配置跨域资源共享
路由(Routing) �定义和约定请求路由
会话(Session) �对管理用户会话提供支持
静态文件(Static Files) ��对服务静态文件和目录浏览提供支持

个人博客

我的个人博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容