本文主要是对.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. tar 的–exclude参数,实现不包括某些文件(转)

    最近有人问我tar压缩能不能不包含不想要的文件或者文件夹 压缩包很利于传送,备份等等.但比如大量的日志文件一般是不需要备份,或者需要分开备份. tar参数中的–exclude,这个是我在看鸟哥私房菜时 ...

  2. 最新Android & iOS设计尺寸规范

    Android 和 iPhone.iPad以及主流手机屏幕的分辨率和相关设计尺寸规范 <点击看大图>

  3. [100]awk运算-解决企业统计pv/ip问题

    awk运算 awk以脚本方式运行 #!/bin/awk BEGIN{ arr[1]="maotai"; arr[2]="maotai" for(k in arr ...

  4. Socket网络编程--聊天程序(2)

    上一节简单如何通过Socket创建一个连接,然后进行通信.只是每个人只能说一句话.而且还是必须说完才会接收到信息,总之是很不方便的事情.所以这一小节我们将对上一次的程序进行修改,修改成每个人可以多说话 ...

  5. 解决百度云推送通知,不显示默认Notification

    问题:百度云推送通知,不显示默认Notification 描述:采用推送消息的方式,可以在onMessage方法里面获取到推送的消息.另外推送通知也有获取到内容,后台日志也有show private ...

  6. 9最好的JavaScript压缩工具

    削减是一个从源代码中删除不必要的字符的技术使它看起来简单而整洁.这种技术也被称为代码压缩和最小化.在这里,我们为你收集了10个最好的JavaScript压缩工具将帮助您删除不必要的空格,换行符,评论, ...

  7. iOS开发支付宝支付

    iOS支付宝支付(Alipay)详细接入流程以及项目中遇到的问题分析    浏览: 149 发布日期: 2016-10-19  分类: ios 最近在项目中接入了微信支付和支付宝支付,总的来说没有那么 ...

  8. 【iCore4 双核心板_ARM】例程八:定时器PWM实验——呼吸灯

    实验原理: STM32的定时器有PWM功能,iCore4的蓝色LED连接在定时器的输出接口上, 可以通过定时器的PWM输出控制LED的亮度,从而实验呼吸灯的功能. 核心代码: int main(voi ...

  9. mySql存储emoji表情报错(mysql incorrect string value)

    问题分析 普通的字符串或者表情都是占位3个字节,所以utf8足够用了,但是移动端的表情符号占位是4个字节,普通的utf8就不够用了,为了应对无线互联网的机遇和挑战.避免 emoji 表情符号带来的问题 ...

  10. Java JPA小记

    什么是JPA JPA之于ORM(持久层框架,如MyBatis.Hibernate等)正如JDBC之于数据库驱动. JDBC是Java语言定义的一套标准,规范了客户端程序访问关系数据库(如MySQL.O ...