本文主要是对.NET Core开发日志——Middleware的补遗,但是会从看起来平平无奇的RequestDelegate开始叙述,所以以其作为标题,也是合情合理。

RequestDelegate是一种委托类型,其全貌为public delegate Task RequestDelegate(HttpContext context),MSDN上对它的解释,"A function that can process an HTTP request."——处理HTTP请求的函数。唯一参数,是最熟悉不过的HttpContext,返回值则是表示请求处理完成的异步操作类型。

可以将其理解为ASP.NET Core中对一切HTTP请求处理的抽象(委托类型本身可视为函数模板,其实现具有统一的参数列表及返回值类型),没有它整个框架就失去了对HTTP请求的处理能力。

并且它也是构成Middleware的基石。或者更准确地说参数与返回值都是其的Func<RequestDelegate, RequestDelegate>委托类型正是维持Middleware运转的核心齿轮。

组装齿轮的地方位于ApplicationBuilder类之内,其中包含着所有齿轮的集合。

private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();

以及添加齿轮的方法:

public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
return this;
}

在Startup类的Configure方法里调用以上ApplicationBuilder的Use方法,就可以完成一个最简单的Middleware。

public void Configure(IApplicationBuilder app)
{
app.Use(_ =>
{
return context =>
{
return context.Response.WriteAsync("Hello, World!");
}; });
}

齿轮要想变成Middleware,在完成添加后,还需要经过组装。

public RequestDelegate Build()
{
RequestDelegate app = context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
}; foreach (var component in _components.Reverse())
{
app = component(app);
} return app;
}

Build方法里先定义了最底层的零件——app,context => { context.Response.StatusCode = 404; return Task.CompletedTask; },这段代码意味着,如果没有添加任何Middleware的话,ASP.NET Core站点启动后,会直接出现404的错误。

接下的一段,遍历倒序排列的齿轮,开始正式组装。

在上述例子里,只使用了一个齿轮:

_ =>
{
return context =>
{
return context.Response.WriteAsync("Hello, World!");
}; }

那么第一次也是最后一次循环后,执行 component(app)操作,app被重新赋值为:

context => context.Response.WriteAsync("Hello, World!");

组装的结果便是app的值。

这个组装过程在WebHost进行BuildApplication时开始操作。从此方法的返回值类型可以看出,虽然明义上是创建Application,其实生成的是RequestDelegate。

private RequestDelegate BuildApplication()
{
try
{
... var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
var builder = builderFactory.CreateBuilder(Server.Features);
...
Action<IApplicationBuilder> configure = _startup.Configure;
... configure(builder); return builder.Build();
}
...
}

而这个RequestDelegate最终会在HostingApplication类的ProcessRequestAsync方法里被调用。

public virtual async Task StartAsync(CancellationToken cancellationToken = default)
{
... var application = BuildApplication(); ...
var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
...
} public HostingApplication(
RequestDelegate application,
ILogger logger,
DiagnosticListener diagnosticSource,
IHttpContextFactory httpContextFactory)
{
_application = application;
_diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource);
_httpContextFactory = httpContextFactory;
} public Task ProcessRequestAsync(Context context)
{
return _application(context.HttpContext);
}

上例中的执行结果即是显示Hello, World!字符。

404的错误不再出现,意味着这种Middleware只会完成自己对HTTP请求的处理,并不会将请求传至下一层的Middleware。

要想达成不断传递请求的目的,需要使用另一种Use扩展方法。

public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
{
return app.Use(next =>
{
return context =>
{
Func<Task> simpleNext = () => next(context);
return middleware(context, simpleNext);
};
});
}

在实际代码中可以这么写:

public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("I am a Middleware!\n");
await next.Invoke();
}); app.Use(_ =>
{
return context =>
{
return context.Response.WriteAsync("Hello, World!");
};
});
}

现在多了个Middleware,继续上面的组装过程。app的值最终被赋值为:

async context =>
{
Func<Task> simpleNext = () => context.Response.WriteAsync("Hello, World!"); await context.Response.WriteAsync("I am a Middleware!\n");
await simpleNext.Invoke();
};

显示结果为:

I am a Middleware!
Hello, World!

下面的流程图中可以清楚地说明这个过程。

如果把await next.Invoke()注释掉的话,

public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("I am a Middleware!\n");
//await next.Invoke();
}); app.Use(_ =>
{
return context =>
{
return context.Response.WriteAsync("Hello, World!");
}; });
}

上例中第一个Middleware处理完后,不会继续交给第二个Middleware处理。注意以下simpleNext的方法只被定义而没有被调用。

async context =>
{
Func<Task> simpleNext = () => context.Response.WriteAsync("Hello, World!"); await context.Response.WriteAsync("I am a Middleware!\n");
};

这种情况被称为短路(short-circuiting)。

做短路处理的Middleware一般会放在所有Middleware的最后,以作为整个pipeline的终点。

并且更常见的方式是用Run扩展方法。

public static void Run(this IApplicationBuilder app, RequestDelegate handler)
{
... app.Use(_ => handler);
}

所以可以把上面例子的代码改成下面的形式:

public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("I am a Middleware!\n");
await next.Invoke();
}); app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}

除了短路之外,Middleware处理时还可以有分支的情况。

public void Configure(IApplicationBuilder app)
{
app.Map("/branch1", ab => {
ab.Run(async context =>
{
await context.Response.WriteAsync("Map branch 1");
});
}); app.Map("/branch2", ab => {
ab.Run(async context =>
{
await context.Response.WriteAsync("Map branch 2");
});
}); app.Use(async (context, next) =>
{
await context.Response.WriteAsync("I am a Middleware!\n");
await next.Invoke();
}); app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}

URL地址后面跟着branch1时:

URL地址后面跟着branch2时:

其它情况下:

Map扩展方法的代码实现:

public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration)
{
... // create branch
var branchBuilder = app.New();
configuration(branchBuilder);
var branch = branchBuilder.Build(); var options = new MapOptions
{
Branch = branch,
PathMatch = pathMatch,
};
return app.Use(next => new MapMiddleware(next, options).Invoke);
}

创建分支的办法就是重新实例化一个ApplicationBuilder。

public IApplicationBuilder New()
{
return new ApplicationBuilder(this);
}

对分支的处理则是封装在MapMiddleware类之中。

public async Task Invoke(HttpContext context)
{
... PathString matchedPath;
PathString remainingPath; if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matchedPath, out remainingPath))
{
// Update the path
var path = context.Request.Path;
var pathBase = context.Request.PathBase;
context.Request.PathBase = pathBase.Add(matchedPath);
context.Request.Path = remainingPath; try
{
await _options.Branch(context);
}
finally
{
context.Request.PathBase = pathBase;
context.Request.Path = path;
}
}
else
{
await _next(context);
}
}

说到MapMiddleware,不得不提及各种以Use开头的扩展方法,比如UseStaticFiles,UseMvc,UsePathBase等等。

这些方法内部都会调用UseMiddleware方法以使用各类定制的Middleware类。如下面UsePathBase的代码:

public static IApplicationBuilder UsePathBase(this IApplicationBuilder app, PathString pathBase)
{
... // Strip trailing slashes
pathBase = pathBase.Value?.TrimEnd('/');
if (!pathBase.HasValue)
{
return app;
} return app.UseMiddleware<UsePathBaseMiddleware>(pathBase);
}

而从UseMiddleware方法中可以获知,Middleware类需满足两者条件之一才能被有效使用。其一是实现IMiddleware,其二,必须有Invoke或者InvokeAsync方法,且方法至少要有一个HttpContext类型参数(它还只能是放第一个),同时返回值需要是Task类型。

internal const string InvokeMethodName = "Invoke";
internal const string InvokeAsyncMethodName = "InvokeAsync"; public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
{
if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
{
... return UseMiddlewareInterface(app, middleware);
} var applicationServices = app.ApplicationServices;
return app.Use(next =>
{
var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
var invokeMethods = methods.Where(m =>
string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
|| string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
).ToArray(); ... var ctorArgs = new object[args.Length + 1];
ctorArgs[0] = next;
Array.Copy(args, 0, ctorArgs, 1, args.Length);
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
if (parameters.Length == 1)
{
return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
} var factory = Compile<object>(methodinfo, parameters); return context =>
{
var serviceProvider = context.RequestServices ?? applicationServices;
...
return factory(instance, context, serviceProvider);
};
});
}

对ASP.NET Core中Middleware的介绍到此终于可以告一段落,希望这两篇文章能够为读者提供些许助力。

.NET Core开发日志——RequestDelegate的更多相关文章

  1. .NET Core开发日志——Entity Framework与PostgreSQL

    Entity Framework在.NET Core中被命名为Entity Framework Core.虽然一般会用于对SQL Server数据库进行数据操作,但其实它还支持其它数据库,这里就以Po ...

  2. C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志

    C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...

  3. .NET Core开发日志——从搭建开发环境开始

    .NET Core自2016年推出1.0版本开始,到目前已是2.1版本,在其roadmap计划里明年更会推出3.0版本,发展不可不谓之迅捷.不少公司在经过一个谨慎的观望期后,也逐步开始将系统升级至最新 ...

  4. .NET Core开发日志——结构化日志

    在.NET生态圈中,最早被广泛使用的日志库可能是派生自Java世界里的Apache log4net.而其后来者,莫过于NLog.Nlog与log4net相比,有一项较显著的优势,它支持结构化日志. 结 ...

  5. .NET Core开发日志——Edge.js

    最近在项目中遇到这样的需求:要将旧有系统的一部分业务逻辑集成到新的自动化流程工具中.这套正在开发的自动化工具使用的是C#语言,而旧有系统的业务逻辑则是使用AngularJS在前端构建而成.所以最初的考 ...

  6. .NET Core开发日志——Linux版本的SQL Server

    SQL Server 2017版本已经可以在Linux系统上安装,但我在尝试.NET Core跨平台开发的时候使用的是Mac系统,所以这里记录了在Mac上安装SQL Server的过程. 最新的SQL ...

  7. .NET Core开发日志——Model Binding

    ASP.NET Core MVC中所提供的Model Binding功能简单但实用,其主要目的是将请求中包含的数据映射到action的方法参数中.这样就避免了开发者像在Web Forms时代那样需要从 ...

  8. .NET Core开发日志——简述路由

    有过ASP.NET或其它现代Web框架开发经历的开发者对路由这一名字应该不陌生.如果要用一句话解释什么是路由,可以这样形容:通过对URL的解析,指定相应的处理程序. 回忆下在Web Forms应用程序 ...

  9. .NET Core开发日志——Startup

    一个典型的ASP.NET Core应用程序会包含Program与Startup两个文件.Program类中有应用程序的入口方法Main,其中的处理逻辑通常是创建一个WebHostBuilder,再生成 ...

随机推荐

  1. Linux下利用backtrace追踪函数调用堆栈以及定位段错误[转]

    来源:Linux社区  作者:astrotycoon 一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序 ...

  2. OpenCV 学习笔记 05 人脸检测和识别

    本节将介绍 Haar 级联分类器,通过对比分析相邻图像区域来判断给定图像或子图像与已知对象是否匹配. 本章将考虑如何将多个  Haar 级联分类器构成一个层次结构,即一个分类器能识别整体区域(如人脸) ...

  3. 11G新特性 -- RMAN Multisection Backups

    Oracle 11g支持以sections的方式来备份和还原数据文件.在section级别进行备份,被称作multisection backup(多段备份).section是一个数据文件中连续的块.m ...

  4. 【转】AlphaGo Zero 和强人工智能

    AlphaGo Zero 和强人工智能 前段时间比较热门的是 AlphaGo(阿法狗)的升级版:AlphaGo Zero(阿法狗零).跟阿法狗不同,阿法狗零不依赖于任何人类对弈记录,完全从围棋的规则出 ...

  5. 解决Redis之MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist o...

    解决Redis之MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist o... ...

  6. 使用python抓取58手机维修信息

    之前在ququ的博客上看到说 python 中的BeautifulSoup 挺好玩的,今天下午果断下载下来,看了下api,挺好用的,完了2把,不错. 晚上写了一个使用python抓取58手机维修信息的 ...

  7. 配置Django

    第一步,安装Python,在这里下载,如果你安装在C:\Python27, 把C:\Python27;C:\Python27\Scripts;C:\Python27\Lib 加到你的Path 第二步: ...

  8. webstorm开发vue项目环境配置

    1.首先安装nodejs,官网下载nodejs安装包,默认安装NPM包管理器(国内使用npm需要FQ,也可以使用淘宝的镜像:npm install -g cnpm –registry=https:// ...

  9. Java知多少(39)interface接口

    在抽象类中,可以包含一个或多个抽象方法:但在接口(interface)中,所有的方法必须都是抽象的,不能有方法体,它比抽象类更加“抽象”. 接口使用 interface 关键字来声明,可以看做是一种特 ...

  10. C语言之单元测试

    在ITOO高校云平台项目实践中,我们模板的模块因为在调别人的接口时出现了问题,为了弄明白是不是接口出了问题,就必须学会单元测试. WHAT? 单元测试(unit testing),是指对软件中的最小可 ...