首先,很感谢在上篇文章 C# 管道式编程 中给我有小额捐助和点赞的朋友们,感谢你们的支持与肯定。希望我的每一次分享都能让彼此获得一些收获,当然如果我有些地方叙述的不正确或不当,还请不客气的指出。好了,下面进入正文。

前言

在开始之前,我们需要明确的一个概念是,在 Web 程序中,用户的每次请求流程都是线性的,放在 ASP.NET Core 程序中,都会对应一个 请求管道(request pipeline),在这个请求管道中,我们可以动态配置各种业务逻辑对应的 中间件(middleware),从而达到服务端可以针对不同用户做出不同的请求响应。在 ASP.NET Core 中,管道式编程是一个核心且基础的概念,它的很多中间件都是通过 管道式 的方式来最终配置到请求管道中的,所以理解这里面的管道式编程对我们编写更加健壮的 DotNetCore 程序相当重要。

剖析管道机制

在上面的论述中,我们提到了两个很重要的概念:请求管道(request pipeline)中间件(middleware)。对于它俩的关系,我个人的理解是,首先,请求管道服务于用户,其次,请求管道可以将多个相互独立的业务逻辑模块(即中间件)串联起来,然后服务于用户请求。这样做的好处是可以将业务逻辑层级化,因为在实际的业务场景中,有些业务的处理即相互独立,又依赖于其它的业务操作,各个业务模块之间的关系实际上是动态不固定的。

下面,我们尝试着来一步步解析 ASP.NET Core 中的管道机制。

理论解释

首先,我们来看一下官方的图例解释:

从上图中,我们不难看出,当用户发出一起请求后,应用程序都会为其创建一个请求管道,在这个请求管道中,每一个中间件都会按顺序进行处理(可能会执行,也可能不会被执行,取决于具体的业务逻辑),等最后一个中间件处理完毕后请求又会以相反的方向返回给用户最终的处理结果。

代码阐释

为了验证上述我们的理论解释,我们开始创建一个 DotNetCore 的控制台项目,然后引用如下包:

  • Microsoft.AspNetCore.App

编写如下示例代码:

class Program
{
static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
} private static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
} public class Startup
{
public void Configure(IApplicationBuilder app)
{
// Middleware A
app.Use(async (context, next) =>
{
Console.WriteLine("A (in)");
await next();
Console.WriteLine("A (out)");
}); // Middleware B
app.Use(async (context, next) =>
{
Console.WriteLine("B (in)");
await next();
Console.WriteLine("B (out)");
}); // Middleware C
app.Run(async context =>
{
Console.WriteLine("C");
await context.Response.WriteAsync("Hello World from the terminal middleware");
});
}
}

上述代码段展示了一个最简单的 ASP.NET Core Web 程序,尝试 F5 运行我们的程序,然后打开浏览器访问 http://127.0.0.1:5000 会看到浏览器显示了 Hello World from the terminal middleware 的信息。对应的控制台信息如下图所示:

上述示例程序成功验证了我们理论解释中的一些设想,这说明在 Configure 函数中成功构建了一个完成的请求管道,那既然这样,我们就可以将其修改为我们之前使用管道的方式,示例代码如下所示:

public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
Console.WriteLine("A (int)");
await next();
Console.WriteLine("A (out)");
}).Use(async (context, next) =>
{
Console.WriteLine("B (int)");
await next();
Console.WriteLine("B (out)");
}).Run(async context =>
{
Console.WriteLine("C");
await context.Response.WriteAsync("Hello World from the terminal middleware");
});
}
}

这两个方式都能让我们的请求管道正常运行,只是写的方式不同。至于采用哪种方式完全看个人喜好。需要注意的是,最后一个控制台中间件需要最后注册,因为它的处理是单向的,不涉及将用户请求修改后返回。

同样的,我们也可以对我们的管道中间件进行条件式组装(分叉路由),组装条件可以依据具体的业务场景而定,这里我以路由为条件进行组装,不同的访问路由最终访问的中间件是不一样的,示例代码如下所示:

public class Startup
{
public void Configure(IApplicationBuilder app)
{
// Middleware A
app.Use(async (context, next) =>
{
Console.WriteLine("A (in)");
await next();
Console.WriteLine("A (out)");
}); // Middleware B
app.Map(
new PathString("/foo"),
a => a.Use(async (context, next) =>
{
Console.WriteLine("B (in)");
await next();
Console.WriteLine("B (out)");
})); // Middleware C
app.Run(async context =>
{
Console.WriteLine("C");
await context.Response.WriteAsync("Hello World from the terminal middleware");
});
}
}

当我们直接访问 http://127.0.0.1:5000 时,对应的请求路由输出如下:

对应的页面会回显 Hello World from the terminal middleware

当我们直接访问 httP://127.0.0.1:5000/foo 时,对应的请求路由输出如下:

当我们尝试查看对应的请求页面,发现对应的页面却是 HTTP ERROR 404 ,通过上述输出我们可以找到原因,是由于最后一个注册的终端路由未能成功调用,导致不能返回对应的请求结果。针对这种情况有两种解决方法。

一种是在我们的 路由B 中直接返回请求结果,示例代码如下所示:

app.Map(
new PathString("/foo"),
a => a.Use(async (context, next) =>
{
Console.WriteLine("B (in)");
await next();
await context.Response.WriteAsync("Hello World from the middleware B");
Console.WriteLine("B (out)");
}));

这种方式不太推荐,因为它极易导致业务逻辑的不一致性,违反了 单一职责原则 的思想。

另一种解决办法是通过路由匹配的方式,示例代码如下所示:

app.UseWhen(
context => context.Request.Path.StartsWithSegments(new PathString("/foo")),
a => a.Use(async (context, next) =>
{
Console.WriteLine("B (in)");
await next();
Console.WriteLine("B (out)");
}));

通过使用 UseWhen 的方式,添加了一个业务中间件对应的业务条件,在该中间件执行完毕后会自动回归到主的请求管道中。最终对应的日志输出入下图所示:

同样的,我们也可以自定义一个中间件,示例代码如下所示:

public class Startup
{
public void Configure(IApplicationBuilder app)
{
// app.UseMiddleware<CustomMiddleware>();
//等价于下述调用方式
app.UseCustomMiddle(); // Middleware C
app.Run(async context =>
{
Console.WriteLine("C");
await context.Response.WriteAsync("Hello World from the terminal middleware");
});
}
} public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
_next = next;
} public async Task InvokeAsync(HttpContext httpContext)
{
Console.WriteLine("CustomMiddleware (in)");
await _next.Invoke(httpContext);
Console.WriteLine("CustomMiddleware (out)");
}
} public static class CustomMiddlewareExtension
{
public static IApplicationBuilder UseCustomMiddle(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CustomMiddleware>();
}
}

日志输出如下图所示:

由于 ASP.NET Core 中的自定义中间件都是通过 依赖注入(DI) 的的方式来进行实例化的。所以对应的构造函数,我们是可以注入我们想要的数据类型,不光是 RequestDelegate;其次,我们自定义的中间件还需要实现一个公有的 public void Invoke(HttpContext httpContext)public async Task InvokeAsync(HttpContext httpContext) 的方法,该方法内部主要处理我们的自定义业务,并进行中间件的连接,扮演着 枢纽中心 的角色。

源码分析

由于 ASP.NET Core 是完全开源跨平台的,所以我们可以很容易的在 Github 上找到其对应的托管仓库。最后,我们可以看一下 ASP.NET Core 官方的一些实现代码。如下图所示:

官方开源了内置中间件的全部实现代码,这里我以 健康检查(HeathChecks) 中间件为例,来验证一下我们上面说的自定义中间件的实现。

通过查阅源码,我们可以看出,我们上述自定义的中间件是符合官方的实现标准的。同样的,当我们以后使用某个内置中间件时,如果对其具体实现感兴趣,可以通过这种方式来进行查看。

总结

当我们对 ASP.NET Core 的请求管道进行中间件配置的时候,有一个地方需要注意一下,就是中间件的配置一定要具体的业务逻辑顺序进行,比如用户认证配置一定要先于路由配置,结合到代码就是下述示例:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//......
app.UseAuthentication();
//......
app.UseMvc();
}

如果当我们的中间件顺序配置不当的话,极有可能导致相应的业务出现问题。

就 ASP.NET Core 的技术架构而言,管道式编程只是其中很小很基础的一部分,整个技术框架设计与实现,用到了很多优秀的技术和架构思想。但是这些高大上的实现都是基于基础技术衍化而来的,所以,基础很重要,只有把基础打扎实了,才不会被技术浪潮所淘汰。

上述所有内容就是我个人对 ASP.NET Core 中的管道式编程的一些理解和拙见,如果有不正确或不当的地方,还请斧正。

望共勉!

相关参考

赞赏

日期 赞赏者 金额 备注
2019-07-29 O*z 2元(微信)

ASP.NET Core 中的管道机制的更多相关文章

  1. ASP.NET Core中,UseDeveloperExceptionPage扩展方法会吃掉异常

    在ASP.NET Core中Startup类的Configure方法中,有一个扩展方法叫UseDeveloperExceptionPage,如下所示: // This method gets call ...

  2. 从Asp .net到Asp core (第一篇)《回顾Asp .net生命周期与管道机制》

    从2016年微软收购了Xamarin整合到Visual Studio里并将其开源到现在已有三年多时间,从.net core 1.0 到现在的2.2,以及即将问世的3.0,我们看到微软正在跨平台之路越走 ...

  3. ASP.NET Core 中的那些认证中间件及一些重要知识点

    前言 在读这篇文章之间,建议先看一下我的 ASP.NET Core 之 Identity 入门系列(一,二,三)奠定一下基础. 有关于 Authentication 的知识太广,所以本篇介绍几个在 A ...

  4. ASP.NET Core 中文文档 第三章 原理(1)应用程序启动

    原文:Application Startup 作者:Steve Smith 翻译:刘怡(AlexLEWIS) 校对:谢炀(kiler398).许登洋(Seay) ASP.NET Core 为你的应用程 ...

  5. [转]ASP.NET Core 中的那些认证中间件及一些重要知识点

    本文转自:http://www.qingruanit.net/c_all/article_6645.html 在读这篇文章之间,建议先看一下我的 ASP.NET Core 之 Identity 入门系 ...

  6. Asp.net Core中SignalR Core预览版的一些新特性前瞻,附源码(消息订阅与发送二进制数据)

    目录 SignalR系列目录(注意,是ASP.NET的目录.不是Core的) 前言 一晃一个月又过去了,上个月有个比较大的项目要验收上线.所以忙的脚不沾地.现在终于可以忙里偷闲,写一篇关于Signal ...

  7. ASP.NET Core - 中间件与管道(1)

    今天来讨论一个ASP.NET Core 很重要概念管道和中间件,在ASP.NET Core中,针对HTTP请求采用pipeline也就是通常说的管道方式来处理,而管道容器内可以挂载很多中间件(处理逻辑 ...

  8. ASP.NET Core 中的中间件

    前言   由于是第一次写博客,如果您看到此文章,希望大家抱着找错误.批判的心态来看. sky! 何为中间件? 在 ASP.NET Framework 中应该都知道请求管道.可参考:浅谈 ASP.NET ...

  9. ASP.NET Core中的数据保护

    在这篇文章中,我将介绍ASP.NET Core 数据保护系统:它是什么,为什么我们需要它,以及它如何工作. 为什么我们需要数据保护系统? 数据保护系统是ASP.NET Core使用的一组加密api.加 ...

随机推荐

  1. c# winform快捷键实现

    我们在软件中经常用到快捷键,这里整理备份一下. 首先我们要定义可以作为快捷键的按键,以下是整理的 一些,自己可以根据情况来修改 public static Dictionary<int, str ...

  2. 08 Javascript的函数

    函数:就是将一些语句进行封装,然后通过调用的形式,执行这些语句. 函数的作用: 将大量重复的语句写在函数里,以后需要这些语句的时候,可以直接调用函数,避免重复劳动. 简化编程,让编程模块化. cons ...

  3. Spring-Boot + MyBatis-Plus 踩坑记录

    这两天在学SpringBoot+MyBatis的开发,配置开发环境和DEMO的过程中踩了很多坑,在这里记录一下. 我的开发环境是idea + JDK 1.8.0.211. 首先展示一下demo的项目整 ...

  4. MyBatis从入门到精通(三):MyBatis XML方式的基本用法之多表查询

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. 多表查询 上篇博客中,我们示例的 ...

  5. 纯CSS制作加<div>制作动画版哆啦A梦

    纯CSS代码加上<div>制作动画版哆啦A梦(机器猫) 哆啦A梦(机器猫)我们大家一定都很熟悉,今天给大家演示怎么用纯CSS代码,来做一个动画版的哆啦A梦. 效果图: ###下面代码同学可 ...

  6. Adboe Flash远程代码执行_CVE-2018-4878漏洞复现

    Adboe Flash远程代码执行_CVE-2018-4878漏洞复现 一.漏洞描述 该漏洞可针对windows用户发起定向攻击.攻击者可以诱导用户打开包含恶意Flash代码文件的Microsoft ...

  7. 跟我学SpringCloud | 第四篇:熔断器Hystrix

    跟我学SpringCloud | 第四篇:熔断器Hystrix 1. 熔断器 服务雪崩 在正常的微服务架构体系下,一个业务很少有只需要调用一个服务就可以返回数据的情况,这种比较常见的是出现在demo中 ...

  8. 【Spring源码解析】—— 策略模式在Spring中的应用

    一.         什么是策略模式 策略模式的定义/含义:策略本身就是为了实现某一个目标而采取的一种工作方式,因此只要能够达成目标,则采取哪一种策略都可以:因此多种实际的策略之间是相互平行的. 注意 ...

  9. C# 位运算及实例计算

    前言: 平时在实际工作中很少用到这个,虽然都是一些比较基础的东西,但一旦遇到了,又不知所云.刚好最近接触了一些相关这方面的项目,所以也算是对 这些内容重新温习实践了一遍.所以这篇不仅作为个人备忘,也分 ...

  10. Google play中下载apk

    在 Google play中下载apk:先在Google play中找到该apk,再去找APK downloader(https://www.allfreeapk.com/),Google play的 ...