まず第一に、私は感謝しています記事C#プログラミングパイプライン、私は小さな貢献と斬の友人を持っているポイントにして、ご支援と肯定していただきありがとうございます。私はいくつかの場所が正しくない、または不適切な物語がある場合、また、ぶっきらぼうに指摘してください、もちろん、私はお互いに共有してもらうたびに、いくつかの利益を作ることができると思います。さて、ここにテキストを入力します。
序文
我々が開始する前に、我々は明確なコンセプトを必要とする、Webアプリケーションでは、ユーザーのすべての要求プロセスは、ASP.NETのコアプログラムに、直線的であるということであるに対応し、要求パイプライン(パイプライン・リクエスト)のリクエストで、パイプライン、我々は動的に様々に対応したビジネスロジックを設定することができますミドルウェア(ミドルウェア)を別のサーバーが異なるユーザの要求に応じて行うことができる達成するように、。ASP.NETコアでは、パイプのプログラミングは、そのミドルウェアの多くのコアと基礎の概念がでているあるパイプラインので、私たちはプログラミングを書くためのパイプラインがあります理解し、最終的な構成の要求パイプラインの道へより堅牢なDotNetCoreプログラムは非常に重要です。
パイプライン分析メカニズム
上記の議論では、我々は2つの非常に重要な概念を述べた:要求パイプライン(要求パイプライン)とミドルウェア(ミドルウェア)。タリアの関係については、私の個人的な理解はまず、要求パイプラインサービスのユーザーに、そして第二に、要求パイプラインは、複数の独立したビジネス・ロジック・モジュール(つまりミドルウェア)を一緒にし、ユーザの要求にサービスを提供することができ、ということです。この利点は、なぜなら実際のビジネスシナリオでは、互いに独立した処理動作のいくつかのビジネスロジックのレベルであるが、他の業務に依存して、サービスモジュールとの間の関係は、動的固定されていません。
ここでは、ASP.NETコアのステップにより、パイプライン機構のステップを解決してみてください。
理論的な説明
まず、我々は公式のイラストレーターを見て:
从上图中,我们不难看出,当用户发出一起请求后,应用程序都会为其创建一个请求管道,在这个请求管道中,每一个中间件都会按顺序进行处理(可能会执行,也可能不会被执行,取决于具体的业务逻辑),等最后一个中间件处理完毕后请求又会以相反的方向返回给用户最终的处理结果。
代码阐释
为了验证上述我们的理论解释,我们开始创建一个 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 中的管道式编程的一些理解和拙见,如果有不正确或不当的地方,还请斧正。
望共勉!