当前位置: 首页 > 编程日记 > 正文

.net 温故知新【17】:Asp.Net Core WebAPI 中间件

一、前言

到这篇文章为止,关于.NET "温故知新"系列的基础知识就完结了,从这一系列的系统回顾和再学习,对于.NET core、ASP.NET CORE又有了一个新的认识。

不光是从使用,还包括这些知识点的原理,虽然深入原理谈不上,但对于日常使用也够了,我想的是知其然,知其所以然。

在实际开发过程中可能是知道怎么使用就行,但系统学习了这些基本的框架、组件、或者说原理后,对于我们软件设计、开发、扩展和解决问题还是有帮助的。

刚好到2023新年前赶着写完,也算对自己这个系列的一个交代,实际上我平时基本不使用ASP.NET CORE,目前我主要开发桌面程序,还是用的winform。

写这个系列的初衷是想紧跟.NET的发展进程,同时储备基础知识,平时还搞一些微服务(Java)、NLP、OCR、知识图谱、前端(Vue3),只要需要反正啥都搞,没必要固执,技术只是手段,不是目的。

那么接下来就继续简单的梳理一下中间件,欢迎对这个系列拍砖!

二、中间件

中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:

  • 选择是否将请求传递到管道中的下一个组件。
  • 可在管道中的下一个组件前后执行工作。

这个是关于中间件概念的概括,官方的概括是相当精准,那么我们就围绕管道、传递、组件来看看中间件。

请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。使用 Run、Map 和 Use 扩展方法来配置请求委托。

我们照例新建一个ASP.NET CORE Web API 项目:WebAPI_Middleware

namespace WebAPI_Middleware
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.

            builder.Services.AddControllers();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();


            app.MapControllers();

            app.Run();
        }
    }
}

在Program.cs 中我们看到前面部分builder是配置依赖注入的东西,这部分可以参看.net 温故知新【13】:Asp.Net Core WebAPI 使用依赖注入DI

app 使用Use扩展用于中间件添加到管道中

Map 基于给定请求路径的匹配项来创建请求管道分支

Run 委托始终为终端,用于终止管道。

中间件的执行顺序过程如下:

image

三、Map

我们将上面自动创建的东西全都删除,用Map来匹配路由,然后通过不同的代理处理请求。

    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            var app = builder.Build();
           
            //匹配map1 请求
            app.Map("/map1", new Action<IApplicationBuilder>((app) =>
            {
                app.Run(async context =>
                {
                    await context.Response.WriteAsync("map1 run");
                });
            }));
            //匹配map2 请求
            app.Map("/map2", new Action<IApplicationBuilder>((app) =>
            {
                app.Run(async context =>
                {
                    await context.Response.WriteAsync("map2 run");
                });
            }));

            app.Run();
        }
    }
  • 请求map1 我们输出:map1 run

image

  • 请求map2 我们输出:map2 run

image

Asp.Net Core MapControllers 的扩展方法也是类似道理,用来匹配路由调用处理程序。

四、Run

在上面的 Map 后面我们使用的处理方法中 Run 用于终止管道。也就是说在该管道中如果调用了 Run 那么就直接返回了,即使你后面还添加了 Use 也不会执行。

app.Run(async context =>
{
    await context.Response.WriteAsync("map1 run");
});

Map 相当于是迎客进门,Map 上了就用指定的管道进行处理,如果没有 Map 上就调用主管道,也就是主管道上的其他中间件也会执行处理。比如我们再加一个 Run 用于没匹配上路由也输出点信息。

image

加了context.Response.ContentType = "text/plain; charset=utf-8"; 不然中文会乱码。

image

因为 Run 是终结点,那这个管道中我还想加其他处理怎么办呢,这个时候就该轮到 Use 出场了。

五、Use

用 Use 将多个请求委托链接在一起。 next 参数表示管道中的下一个委托。 可通过不调用 next 参数使管道短路。

首先我们在外面添加两个 Use,不放到 Map 中,这样的话就只有未匹配到的路由会调用

    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            var app = builder.Build();

            

            app.Map("/map1", new Action<IApplicationBuilder>((app) =>
            {
                app.Run(async context =>
                {
                    await context.Response.WriteAsync("map1 run");
                });
            }));

            app.Map("/map2", new Action<IApplicationBuilder>((app) =>
            {
                app.Run(async context =>
                {
                    await context.Response.WriteAsync("map2 run");
                });
            }));
            //Use1
            app.Use(async (context, next) =>
            {
                context.Response.ContentType = "text/plain; charset=utf-8";

                await context.Response.WriteAsync("第 1 个Use   开始!\r\n", Encoding.UTF8);

                await next();

                await context.Response.WriteAsync("第 1 个Use   结束!\r\n", Encoding.UTF8);

            });
            
            //Use2
            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("第 2 个Use   开始!\r\n", Encoding.UTF8);

                await next();

                await context.Response.WriteAsync("第 2 个Use   结束!\r\n", Encoding.UTF8);

            });
            //结束管道处理
            app.Run(async context =>
            {
                await context.Response.WriteAsync("未匹配处理!\r\n", Encoding.UTF8);
            });

            app.Run();
        }
    }

最后执行的路径和最开始的图是一致的。

image

为什么将context.Response.ContentType = "text/plain; charset=utf-8"; 放到第一个 Use 呢,因为如果放到 Run 里面会报错,改变了 Header 标头。所以理论上也不要在 Use 里面发送响应WriteAsync,此处为了演示所以这么写。

image

六、中间件类

上面的代理方法可以移动到类中,这个类就是中间件类。中间件类需要如下要求:

  • 具有类型为 RequestDelegate 的参数的公共构造函数。
  • 名为 Invoke 或 InvokeAsync 的公共方法。 此方法必须:
    返回 Task。
    接受类型 HttpContext 的第一个参数。

构造函数和 Invoke/InvokeAsync 的其他参数由依赖关系注入 (DI) 填充。

将上面的未匹配路由处理逻辑移动到中间件类中:

  • TestMiddleware1:
    public class TestMiddleware1
    {
        private readonly RequestDelegate _next;

        public TestMiddleware1(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {

            context.Response.ContentType = "text/plain; charset=utf-8";

            await context.Response.WriteAsync("第 1 个Use   开始!\r\n", Encoding.UTF8);

            await _next(context);

            await context.Response.WriteAsync("第 1 个Use   结束!\r\n", Encoding.UTF8);
        }
    }
  • TestMiddleware2
    public class TestMiddleware2
    {
        private readonly RequestDelegate _next;

        public TestMiddleware2(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {


            await context.Response.WriteAsync("第 2 个Use   开始!\r\n", Encoding.UTF8);

            await _next(context);

            await context.Response.WriteAsync("第 2 个Use   结束!\r\n", Encoding.UTF8);
        }
    }
  • Program
    image

  • 运行
    image

此处的中间件使用有顺序问题,如果我先app.UseMiddleware<TestMiddleware2>() 因为 TestMiddleware1 修改了标头,根据约定是不允许的,所以程序是有报错。

image

因此中间件组件的顺序定义了针对请求调用这些组件的顺序,以及响应的相反顺序。 此顺序对于安全性、性能和功能至关重要。

七、中间件顺序

image

以上是内置中间件的默认顺序规则,具体如何使用内置中间件,可参阅官方资料。

八、写在最后

以上就是关于中间件的部分知识,结合我自己的理解做了前后衔接的梳理逻辑。

官方网站更多的是讲解每个知识点的细节,前后需要结合起来理解,当然我还是强烈建议跟着官方文档学习,而且是最权威最可信的:ASP.NET Core 中间件

这个系列历时2年,工作生活都比较忙,也有放纵啥事不相干的时候,中间断断续续的,总算是坚持完了。很多东西就是这样,累了就休息一下贵在坚持,即使再慢,积累的成果也有收获。

相关文章:

5分钟教会你如何在生产环境debug代码

前言 有时出现的线上bug在测试环境死活都不能复现,靠review代码猜测bug出现的原因,然后盲改代码直接在线上测试明显不靠谱。这时我们就需要在生产环境中debug代码,快速找到bug的原因,然后将锅丢出去。 生产环境的代码一般都是关闭source map和经过混淆的,那么如何进行debug代码呢

Rust之旅 - Rust概念、Windows安装、环境配置

本章节介绍Rust概念、Windows安装、环境配置以及最初级的语法。至此,我们就成功的构建了一个Rust程序,并成功在Visual Studio Code里运行了这个程序,万事俱备,我们就可以开始Rust之旅了。资料获取,更多粉丝福利,关注下方公众号获取。

轻松管理Linux磁盘空间命令:df

通过使用--output选项,可以自定义df命令的输出格式,选择显示的列以及它们的顺序。这对于筛选特定信息以便进一步处理非常有用。本文我们介绍了Linux系统上的df命令,包括基本用法、进阶用法、实际案例和场景应用,以及一些实用技巧和注意事项。df命令是系统管理中的一个重要工具,能够帮助用户有效管理磁盘空间,预防和解决潜在问题。在实际使用中,请根据具体情况选择合适的df命令选项和参数,并结合其他命令,以获取更全面的系统信息。

C语言中常用的字符串处理函数和内存操作函数

`memmove(void *destination, const void *source, size_t num)`:将`source`指向的内存块的前`num`个字节移动到`destination`所指向的内存块,即使内存块有重叠部分。返回指向`destination`的指针。- `memcpy(void *destination, const void *source, size_t num)`:将`source`指向的内存块的前`num`个字节复制到`destination`所指向的内存块。

小程序中的数据双向绑定和Vue的有什么区别

微信小程序的数据绑定跟vue的有什么区别

使用docker部署RStudio容器并结合内网穿透实现公网访问

RStudio Server 使你能够在 Linux 服务器上运行你所熟悉和喜爱的 RStudio IDE,并通过 Web 浏览器进行访问,从而将 RStudio IDE 的强大功能和工作效率带到基于服务器的集中式环境中。下面介绍在Linux docker中安装RStudio Server并结合cpolar内网穿透工具,实现远程访问,docker方式安装可以避免很多问题,一键安装,如设备没有安装docker,需提前安装docker。

Spring Boot整合日期转换器(Converter)和拦截器(HandlerInterceptor)

配置文件形式针对框架进行个性化定制,例如:拦截器,类型转化器等等。WebMvcConfigurer配置类其实是。内部的一种配置方式,采用。

Redis的key过期策略是怎么实现的

这是一道经典的Redis面试题,一个Redis中可能存在很多很多的key,这些key中可能有很大一部分都有过期时间,此时Redis服务器咋知道哪些key已经过期,哪些还没过期呢?如果直接遍历所有的key,这显然是行不通的,效率非常低!!Redis整体的策略是定期删除和惰性删除相结合。举个栗子:假如我去小卖铺买东西,付款的时候,发现东西过期了。就告知老板,于是老板下架此产品。消费者发现过期了,才去下架,这就叫。小卖铺老板主动定期抽取一部分商品,进行筛查,这就叫定期删除。

怎么用Office的Excel将图片转为excel表格?

在处理大量的表格数据时,我们经常需要将图片中的表格转换成Excel格式,以便进行更高效的数据分析和处理。2. 打开OCR软件,将需要转换的图片导入。点击“提交识别”,仅识别所点击的图片,点击“识别全部”,将识别列表上的全部图片。第四步:等识别完成后,在弹出的提示框点击“确定”,打开识别结果文件,也可点击取消,然后点击“打开文件”。第二步:点击“图片编辑”,查看图片,如有必要,可进行添加表格线和旋转、裁切图片等操作。点击“打开文件夹”可查看全部已识别的文件,点击下方“选择”,可修改识别文件的保存目录。

一键式Excel分词统计工具:如何轻松打包Python脚本为EXE

最近,表姐遇到了一个挑战:需要从Excel文件中统计出经过分词处理的重复字段,但由于数据隐私问题,这些Excel文件不能外传。这种情况下,直接使用Excel内置功能好像是行不通的,需要借助Python脚本来实现。为了解决这个问题,我写了一个简单的数据分析和自动化办公脚本,以方便使用。想象一下,即使电脑上没有安装Python,也能通过一个简单的EXE文件轻松完成工作,这是多么方便!因此,我决定不仅要写出这个脚本,还要学会如何将其打包成一个独立的EXE文件。这样,无需Python环境的电脑也能直接运行它

深入解析JavaScript的原生原型

在JavaScript中,除了自定义对象,还存在很多由JavaScript语言本身提供的原生对象。这些原生对象同样基于原型继承机制,拥有自己的原型。理解原生对象的原型非常重要,可以让我们正确使用这些内置对象,也有助于进一步理解JavaScript的原型继承系统。本文将详细解析原生对象的原型结构,揭开一些常见原生对象原型的神秘面纱。​学习原生对象的原型关系,有助于我们在日常开发中正确理解和使用这些JavaScript内置对象,避免一些常见陷阱。

WebSocket 入门实战

这个简单示例演示了如何使用 Spring Boot 和 Spring WebSocket 创建一个基本的 WebSocket 服务。通过这个例子,可以了解 WebSocket 在实时通信中的应用,如果大家在平时工作当中有遇到需要实时推送的场景,比如大屏实时展示数据变化,就可以用这种发放时。

深入三目运算符:JavaScript、C++ 和 Python 比较

三目运算符是编程中常用的条件表达式,它允许我们根据条件选择不同的值。我们将通过具体的例子分别介绍 JavaScript、C++ 和 Python 中的三目运算符,以便更好地理解它们的用法和特性。JavaScript 示例// 例子: 根据条件选择不同的值var x = 10;var y = 20;"x 大于 y" : "x 不大于 y";在这个例子中,如果x大于y,则result的值为 “x 大于 y”,否则为 “x 不大于 y”。C++ 示例// 例子: 根据条件选择不同的值。

Java中的4种引用类型,你知道几种?

Java作为一门面向对象的编程语言,内存管理一直是程序员需要关注的重要方面。在Java中,垃圾回收机制负责自动管理内存,而引用类型则是垃圾回收的重要参考。本文将深入讨论Java中的四种引用类型:强引用、弱引用、软引用和虚引用,以及它们在内存管理中的应用和区别。

Docker网络配置&网络模式

网络相关概念,子网掩码、网关、规则的介绍及网络模式bridge、host详解,Dockers自定义网络配置

.NET使用QuestPDF高效地生成PDF文档

前言 在.NET平台中操作生成PDF的类库有很多如常见的有iTextSharp、PDFsharp、Aspose.PDF等,今天我们分享一个用于生成PDF文档的现代开源.NET库:QuestPDF,本文将介绍QuestPDF并使用它快速实现发票PDF文档生成功能。 QuestPDF介绍 QuestPD

python实现网络爬虫代码_python如何实现网络爬虫

2、【find()】和【find_all()】方法可以遍历这个html文件,提取指定信息。return soup.find_all(string=re.compile( '百度' )) #结合正则表达式,实现字符串片段匹配。print(res) #打印输出[root@localhost demo]# python3 demo1.py。[root@localhost demo]# vim demo.py#web爬虫学习 -- 分析。r.raise_for_status() #如果状态码不是200,产生异常。

【Java】程序员应该了解的进制知识

进制对我们今后理解更复杂的知识有着重要作用。对于整数来说有四种表示方式:二进制、十进制、八进制、十六进制。而我们的计算机是以二进制的方式进行存储的。接下来我们深入地学习一下进制的知识。正如,前言所说整数的进制有四种:二进制:0、1 满2进1,以0b或0B开头十进制:0-9 满10进1八进制:0-7 满8进1,以数字0开头十六进制:0-9及A(10)-F(15) 满16进1,以0x或0X开头表示。此处的A-F不区分大小写//编写一个main方法//n1 二进制。

Java中访问修饰符public、private、protect、default范围

静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。我们看到匿名内部类Test$1的构造器含有两个参数,一个是指向外部类对象的引用,一个是int型变量,很显然,这里是将变量test方法中的形参a以参数的形式传进来对匿名内部类中的拷贝(变量a的拷贝)进行赋值初始化。

Linux grep命令教程:强大的文本搜索工具(附案例详解和注意事项)

grep(Global Regular Expression Print)命令用来在文件中查找包含或者不包含某个字符串的行,它是强大的文本搜索工具,并可以使用正则表达式进行搜索。当你需要在文件或者多个文件中搜寻特定信息时,grep就显得无比重要啦。

详细讲解Python中的aioschedule定时任务操作

aioschedule 是一个基于 asyncio 的 Python 库,用于在异步应用程序中进行任务调度。它提供了一种方便的方式来安排和执行异步任务,类似于传统的 schedule 库,但适用于异步编程。

Java HttpClient 实战 GET 与 POST 请求一网打尽

使用Java HttpClient 进行HTTP请求 在Java中,HttpClient是进行HTTP通信的一个强大工具。它提供了简单而灵活的API,可以轻松地发送HTTP请求并处理响应。在本篇博文中,我们将深入探讨如何使用HttpClient执行GET、POST等不同类型的HTTP请求。 1. 引

Windows下的Linux子系统(WSL)

什么是WSLWSL:Windows subsystem for Linux,是用于Windows上的Linux的子系统作用很简单,可以在Windows系统中获取Linux系统环境,并完全直连计算机硬件,无需通过虚拟机虚拟硬件,不会影响Windows系统本身 为什么使用WSLWSL作为自Windows

雪花算法生成ID、UUID生成ID和MySql自增ID优缺点分析

综上所述,UUID适用于分布式系统和需要保密的场景,雪花ID适用于分布式系统和高并发环境,MySQL自增ID适用于单机系统和高效查询的场景。根据具体的业务需求和系统架构,选择合适的主键类型。通过本文的介绍和对比,希望读者能够更好地理解在MySQL中不推荐使用UUID或者雪花ID作为主键的原因,并能够根据实际情况做出明智的选择。在MySQL中,使用自增整数作为主键是一种常见的做法,因为它具有较小的存储空间、高效的索引和自动增长的特性。然而,具体选择何种主键类型还是要根据具体的业务需求和数据特点来决定。

解决鸿蒙APP的内存泄漏

解决鸿蒙应用的内存泄漏问题需要开发者对鸿蒙框架和组件的生命周期有深入的了解,以确保资源能够在适当的时候得到释放。LeakCanary同样可以用于鸿蒙应用,它是一款流行的内存泄漏检测库,可以在应用运行时检测内存泄漏并提供详细的报告。使用这些工具来监测应用的内存使用情况,找到潜在的内存泄漏问题。在某些场景下,使用弱引用(WeakReference)可以帮助避免对对象的强引用,从而减少内存泄漏的可能性。通过模拟各种使用场景,找到潜在的内存泄漏问题。释放不再需要的资源,避免在组件销毁后仍然持有对它的引用。

鸿蒙HarmonyOS实战-ArkTS语言(基本语法)

&#128640;一、ArkTS语言基本语法 &#128270;1.简介 HarmonyOS的ArkTS语言是一种基于TypeScript开发的语言,它专为HarmonyOS系统开发而设计。ArkTS语言结合了JavaScript的灵活性和TypeScript的严谨性,使得开发者能够快速、高效地开发

保持Python程序在Linux上持续运行的几种方法

主要是用来定时执行任务的,但你也可以利用它来监控你的Python脚本是否正在运行,并在需要时重新启动它。是一个非常实用的命令,它可以让你的Python脚本在你退出shell后继续运行。总结起来,根据你的具体需求和环境,你可以选择以上任何一种方法来保持Python程序在Linux上的持续运行。使用这些工具,你可以随时断开SSH连接,而不用担心脚本会停止运行。通过这种方式,你可以安全地关闭终端,而脚本会继续在后台执行。这样,你的Python脚本就会作为系统服务运行,并且会在系统启动时自动启动。

深入浅出:Golang内存逃逸机制与性能优化技巧

内存逃逸发生在编译期间,当编译器判断一个变量的生命周期超出了其当前作用域,它会将该变量分配到堆上,而非栈上。这种情况虽然保证了程序的正确性,但会增加垃圾回收的负担,从而影响程序性能。通过本文的深入探讨,我们了解了Golang内存逃逸的基本原理,以及它是如何影响程序性能的。我们探讨了内存逃逸的常见场景,并通过具体的代码示例展示了这些问题的出现和解决方法。最重要的是,我们学习了一系列性能优化技巧,包括数据结构优化、减少不必要的指针使用、利用栈空间、使用内存池,以及定期进行性能分析。

【小白专用】C# 连接 MySQL 数据库

C# 连接 MySQL 数据库

Nas群晖中安装Cpolar实现内网穿透

Nas群晖中安装Cpolar实现内网穿透