【NetCore】09-中间件

news2025/1/19 17:10:42

文章目录

  • 中间件:掌控请求处理过程的关键
    • 1. 中间件
      • 1.1 中间件工作原理
      • 1.2 中间件核心对象
    • 2.异常处理中间件:区分真异常和逻辑异常
      • 2.1 处理异常的方式
        • 2.1.1 日常错误处理--定义错误页的方法
        • 2.1.2 使用代理方法处理异常
        • 2.1.3 异常过滤器 IExceptionFilter
        • 2.1.4 特性过滤 ExceptionFilterAttribute
        • 2.1.5 异常处理技巧总结
    • 3 静态文件中间件: 前后端分离开发合并部署
      • 3.1 静态文件中间件的能力
      • 3.2 注册使用非www.root目录
    • 4 文件提供程序:将文件放在任何地方
      • 4.1 文件提供程序核心类型
      • 4.2 内置文件提供程序

中间件:掌控请求处理过程的关键

1. 中间件

1.1 中间件工作原理

中间件工作原理

1.2 中间件核心对象

  • IApplicationBuilder
  • RequestDelegate

IApplicationBuilder可以通过委托方式注册中间件,委托的入参也是委托,这就可以将这些委托注册成一个链,如上图所示;最终会调用Builder方法返回一个委托,这个委托就是把所有的中间件串起来后合并成的一个委托方法,Builder的委托入参是HttpContext(实际上所有的委托都是对HttpContext进行处理);
RequestDelegate是处理整个请求的委托。

中间件的执行顺序和注册顺序是相关的

//注册委托方式,注册自己逻辑
 // 对所有请求路径
            app.Use(async (context, next) =>
            {
                await next();
                await context.Response.WriteAsync("Hello2");
            });

            // 对特定路径指定中间件,对/abc路径进行中间件注册处理
            app.Map("/abc", abcBuilder =>
            {
            	// Use表示注册一个完整的中间件,将next也注册进去
                abcBuilder.Use(async (context, next) =>
                {
                    await next();
                    await context.Response.WriteAsync("abcHello");
                });
            });

            // Map复杂判断,判断当前请求是否符合某种条件
            app.MapWhen(context =>
            {
                return context.Request.Query.Keys.Contains("abc");
            }
            , builder =>
            {
                // 使用Run表示这里就是中间件的执行末端,不再执行后续中间件
                builder.Run(async context =>
                {
                    await context.Response.WriteAsync("new abc");
                });
            });

应用程序一旦开始向Response进行write时,后续的中间件就不能再操作Head,否则会报错
可以通过context.Resopnse.HasStarted方法判断head是否已经被操作

  • 设计自己的中间件
    中间件的设计时才有的约定的方式,即在方法中包含Invoke或者InvokeAsync,如下:
public class MyMiddleware
    {
        private readonly RequestDelegate next;
        private readonly ILogger<MyMiddleware> logger;

        public MyMiddleware(RequestDelegate next,ILogger<MyMiddleware> logger)
        {
            this.next = next;
            this.logger = logger;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            using (logger.BeginScope("TraceIndentifier:{TraceIdentifier}",context.TraceIdentifier))
            {
                logger.LogDebug("Start");
                await next(context);
                logger.LogDebug("End");
            }
        }
    }

public static class MyBuilderExtensions
{
	public  static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder app)
	{
		return app.UseMiddleware<MyMiddleware>();
	}
}

// 中间件使用
app.UseMyMiddleware();

2.异常处理中间件:区分真异常和逻辑异常

2.1 处理异常的方式

  • 异常处理页
  • 异常处理匿名委托方法
  • IExceptionFilter
  • ExceptionFilterAttribute
// startup中的Configure
 if (env.IsDevelopment())
 {
      app.UseDeveloperExceptionPage();// 开发环境下的异常页面,生产环境下是需要被关闭,页面如下图所示
      app.UseSwagger();
      app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "LoggingSerilog v1"));
}

在这里插入图片描述

2.1.1 日常错误处理–定义错误页的方法

// startup中的Configure
app.UseExceptionHandler("/error");

// 控制器
public class ErrprController : Controller
{
	[Route("/error")]
	public IActionResult Index()
	{
		// 获取上下文中的异常
		var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
		var ex = exceptionHandlerPathFeature?.Error;
		
		var knowException = ex as IKnowException;
		
		if(knowException == null)
		{
			var logger = HttpContext.RequestServices.GetService<ILogger<MyExceptionFilterAttribute>>();
			logger.LogError(ex,ex.Message);
			knowException = KnowException.Unknow;
		}
		else
		{
			knowException = KnowException.FromKnowException(knowException);
		}
		return View(knowException);
	}
}

// 定义接口
public interface IKnowException
{
	public string Message {get;}
	
	public int ErrorCode {get;}

	public object[] ErrorData {get;}
}

// 定义实现
public class KnowException : IKnowException
{
	public string Message {get;private set;}

	public int ErrorCode {get;private set;}

	public object[] ErrorData {get;private set;}

	public readonly static IKnowException Uknow = new KnowException{Message = "未知错误",ErrorCode = 9999};

	public static IKnowException FromKnowException(IKnowException exception)
	{
		return new KnowException{Message = exception.Message,ErrorCode = exception.ErrorCode,ErrorData = exception.ErrorData};
	}
}

// 需要定义一个错误页面 index.html,输出错误Message和ErrorCode

2.1.2 使用代理方法处理异常

// startup中的Configure
app.UseExceptionHandler(errApp =>
{
	errApp.Run(async context =>
	{
		var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
		var ex = exceptionHandlerPathFeature?.Error;
		var knowException = ex as IKnowException;
		
		if(knowException == null)
		{
			var logger = HttpContext.RequestServices.GetService<ILogger<MyExceptionFilterAttribute>>();
			logger.LogError(exceptionHandlerPathFeature.Error,exceptionHandlerPathFeature.Error.Message);
			knowException = KnowException.Unknow;
			context.Response.StatusCode = StatusCodes.Status500InternalServerError;
		}
		else
		{
			knowException = KnowException.FromKnowException(knowException);
			context.Response.StatusCode = StatusCodes.Status200OK;
		}
		var jsonOptions = context.RequestServices.GetService<Options<JsonOptions>>();
		context.Response.ContextType = "application/json";charset=utf-8";
		await context.Response.WriteAsync(System.Text.Json.JsonSerializer(knowException,jsonOptions.Value));
	});
});

  • 未知异常输出Http500响应,已知异常输出Http200
    因为监控系统会对Http响应码进行识别,如果返回的500比率比较高的时候,会认为系统的可用性有问题,告警系统会发出警告。对已知异常进行200响应能够让告警系统正常运行,能够正确识别系统一些未知的错误,使告警系统更加灵敏,避免了业务逻辑的异常干扰告警系统

2.1.3 异常过滤器 IExceptionFilter

异常过滤器是作用在整个MVC框架体系之下,在MVC整个声明周期中发生作用,也就是说它只能工作早MVC Web Api的请求周期里面

// 自定义异常过滤器
public class MyException : IExceptionFilter
{
	public void OnException(ExceptionContext context)
	{
		IKnowException knowException = context.Exception as IKnowException;
		if(knowException == null)
		{
			var loger = context.HttpContext.RequestServices.GetService<ILogger<MyExceptionFilterAttribute>>();
			logger.LogError(context.Exception,context.Exception.Message);
			knowException = KnowException.UnKnow;
			context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
		}
		else
		{
			knowException = KnowException.FromKnowException(knowException);
			context.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
		}
		context.Result = new JsonResult(knowException)
		{
			ContextType = "application/json:charset=utf-8"
		}
	}
}

// startup注册
public void ConfigureServices(IServiceCollection services)
{
	services.AddMvc(mvcoption => 
	{
		mvcOptions.Filters.Add<MyExceptionFilter>();
	}).AddJsonOptions(jsonOptions => {
		jsonOptions.JsonSerializerOptions.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscapt
	});
}



2.1.4 特性过滤 ExceptionFilterAttribute

public class MyExceptionFilterAttriburte  : ExceptionFilterAttribute
{
	public override void OnException(ExceptionContext context)
	{
		IKnowException knowException = context.Exception as IKnowException;
		if(knowException == null)
		{
			var logger = context.HttpContext.RequestServices.GetServices<ILogger<MyExceptionFilterAttribute>>();
			logger.LogError(context.Exception,context.Exception.Message);
			knowException = KnowException.UnKnow;
			context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
		}
		else
		{
			knowException = KnowException.FromKnowException(knowException);
			context.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
		}
		context.Result = new JsonResult(knowException)
		{
			ContextType = "application/json:charset=utf-8"
		}
	}
}

// 使用方式
在Controller控制器上方标注[MyExceptionFilter]
或者在 startup中ConfigureServices注册
services.AddMvc(mvcoption => 
	{
		mvcOptions.Filters.Add<MyExceptionFilterAttribute>();
	});

2.1.5 异常处理技巧总结

  • 用特定的异常类或接口表示业务逻辑异常
  • 为业务逻辑异常定义全局错误码
  • 为未知异常定义定义特定的输出信息和错误码
  • 对于已知业务逻辑异常响应HTTP 200(监控系统友好)
  • 对于未预见的异常响应HTTP 500
  • 为所有的异常记录详细的日志

3 静态文件中间件: 前后端分离开发合并部署

3.1 静态文件中间件的能力

  • 支持指定相对路径
  • 支持目录浏览
  • 支持设置默认文档
  • 支持多目录映射
// startup的Configure方法中
app.UseDefaultFiles();// 设置默认访问根目录文件index.html
app.UseStaticFiles();// 将www.root目录映射出去

如果需要浏览文件目录,需要如下配置

// startup中的ConfigureServices中配置
services.AddDirectoryBrowser();

// startup的Configure方法中
app.UseDirectoryBrowser();
app.UseStaticFiles();

3.2 注册使用非www.root目录

// startup的Configure方法中
app.UseStaticFiles();

app.UseStaticFiles(new StaticFileOptions
{
	// 将程序中名为file文件目录注入
	RequestPath = "/files",// 设置文件指定访问路径,将文件目录映射为指定的Url地址
	FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(),"file"))
});


实际生产中会遇到将非api请求重定向到指定目录,采用如下配置

// startup的Configure方法中
app.MapWhen(context => 
{
	return !context.Request.Path.Value.StartWith("/api");
},appBuilder => 
{
	var option = new RewriteOptions();
	option.AddRewrite(".*","/index.html",true);
	appBuilder.UseRewriter(option);
	appBuilder.UseStaticFiles();
});

4 文件提供程序:将文件放在任何地方

4.1 文件提供程序核心类型

  • IFileProvider
  • IFileInfo
  • IDirectoryContexts

4.2 内置文件提供程序

  • PhysicalFileProvider⇒ 物理文件提供程序
  • EmbeddedFileProvider ⇒ 嵌入式文件提供程序
  • CompositeFileProvoder ⇒ 组合文件提供程序,将各种文件程序组合成一个目录
// 映射指定目录文件- 物理文件
IFileProvider provider1 = new PhysicalFileProvider(AppDomain.CurrentDomain.BaseDirectory);
// 获取文件目录下内容
var contents = provider1.GetDirectoryContents("/");
// 输出文件信息
foreach (var item in contents)
{
    var stream = item.CreateReadStream();// 获取文件流
    Console.WriteLine(item.Name);
}

// 嵌入式文件
IFileProvider fileProvider2 = new EmbeddedFileProvider(typeof(Program).Assembly);
var html = fileProvider2.GetFileInfo("file.html");// file.html文件属性设置为嵌入的资源

// 组合文件提供程序
IFileProvider fileProvider3 = new CompositeFileProvider(provider1,fileProvider2);
var contexts3 = fileProvider3.GetDirectoryContents("/");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/884306.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NTN(六) switchover

NTN中的switchover包括feeder link switchover和 serving link switch。所谓feeder link switchover就是将feeder link从source NTN 网关更改为特定 NTN payload的target NTN 网关的过程。 feeder link switchover是网络层过程。 而service link switch则是指serving NTN paylo…

OpenCV分析tfboys十周年演唱会灯牌大战结果

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。本文是音视频系…

【代码质量】认知复杂度(COGNITIVE COMPLEXITY)一种衡量可理解性的新方法

白皮书地址 摘要&#xff1a;圈复杂度最初是作为“可测试性和模块控制流的“可维护性”。虽然它擅长于衡量前者&#xff0c;但它的数学模型不能产生一个令人满意的值来衡量后者。本文描述一种打破数学度量模型的新度量模型来评估代码&#xff0c;以弥补圈复杂度的缺点&#xf…

【科研论文配图绘制】task1 掌握科研绘图的基本知识

【科研论文配图绘制】task1 掌握科研绘图的基本知识 写在最前 8月份Datawhale组队学习&#xff0c;写下该博客记录学习内容 1.科研论文配图的分类与构成 2.科研论文配图的格式和尺寸 3.科研论文配图中的字体和字号设置 4.科研论文配图的版式设计、结构布局和颜色搭配 占个…

【校招VIP】CSS校招考点之选择器优先级

考点介绍&#xff1a; 选择器是CSS的基础&#xff0c;也是校招中的高频考点&#xff0c;特别是复合选择器的执行优先级&#xff0c;同时也是实战中样式不生效的跟踪依据。 因为选择器的种类较多&#xff0c;很难直接记忆&#xff0c;可以考虑选择一个相对值&#xff0c;比如id类…

day4 IO模型

IO多路复用 1.select函数 服务器&#xff1a; 客户端 poll函数 客户端&#xff1a;

《Java-SE-第三十八章》之注解

前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页&#xff1a;KC老衲爱尼姑的博客主页 博主的github&#xff0c;平常所写代码皆在于此 共勉&#xff1a;talk is cheap, show me the code 作者是爪哇岛的新手&#xff0c;水平很有限&…

每日记--前端解决方案--el-select下拉样式-el-option内容过长-鼠标悬停到文字不修改光标样式-设置透明

文章目录 el-select下拉样式el-select中el-option内容过长解决办法鼠标悬停到文字不修改光标样式设置透明 el-select下拉样式 element-ui自带样式设置popper-class el-select中el-option内容过长解决办法 问题&#xff1a;像这样选项太长了&#xff0c;不好看 解决&#xf…

关于Linux文件系统只读问题的修改笔记

1.问题 2. 原因 系统异常关机或者代码修改错误导致硬盘挂载出现问题开启只读模式&#xff0c;但是重启有时候可以解决。 3.解决方法 1. mount查看那个挂载的硬盘出现问题(ro标识只读) mount | grep ro2.找到硬盘&#xff0c;重新挂载即可 sudo mount -o remount,rw /sys/f…

半导体市场震荡,硅晶圆价格下修成焦点 | 百能云芯

半导体市场状况不容乐观&#xff0c;原本被半导体晶圆制造厂视为稳定业绩的长期合同开始面临松动。行业内传出&#xff0c;国内重要的晶圆代工大厂已向日本硅晶圆供应商提出要求降低明年合同价格的请求&#xff0c;以共同应对困境&#xff0c;双方目前正处于激烈的博弈中。鉴于…

测试相关Liunx基础知识

Linux的历史和安装 基本常识 Liunx目录结果 常见

1€滤波器(1 Euro Filter)使用介绍

怎么调整欧拉角x、y、z的抖动问题&#xff1f;

python+django+mysql项目实践四(信息修改+用户登陆)

python项目实践 环境说明: Pycharm 开发环境 Django 前端 MySQL 数据库 Navicat 数据库管理 用户信息修改 修改用户信息需要显示原内容,进行修改 通过url传递编号 urls views 修改内容需要用数据库的更新,用update进行更新,用filter进行选择 输入参数多nid,传递要修…

数据结构--有向⽆环图 描述表达式

数据结构–有向⽆环图 描述表达式 有向⽆环图 \color{red}有向⽆环图 有向⽆环图&#xff1a;若⼀个有向图中 不存在环 \color{red}不存在环 不存在环&#xff0c;则称为有向⽆环图&#xff0c;简称 D A G 图 \color{red}DAG图 DAG图&#xff08;Directed Acyclic Graph&#x…

2021年09月 C/C++(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;字符统计 给定一个由a-z这26个字符组成的字符串&#xff0c;统计其中哪个字符出现的次数最多。 输入 输入包含一行&#xff0c;一个字符串&#xff0c;长度不超过1000。 输出 输出一行&#xff0c;包括出现次数最多的字符和该字符出现的次数&#xff0c;中间以…

autodock后的pdbqt文件怎么通过网站分析?

首先需要在pymol中打开这个docking后的分子 然后再打开受体&#xff0c; 注意&#xff1a;顺序不要反&#xff0c;顺序反了会导致网址分析错误 最后导出为pdb就可以了放在网站上用了 网址&#xff1a;https://plip-tool.biotec.tu-dresden.de/plip-web/plip/index

RK3568KK操作手册

一&#xff0e;烧录MCU 板子不用上电&#xff0c;接上烧录器 打开 HOPE3000 For e-Link 烧录软件。选择文件&#xff0c;选择要烧录的固件&#xff1a;HT66F2030.MTP 选择3V 点击下载&#xff0c; 点击所有 烧录成功如图所示&#xff1a; 二&#xff0e;接上电源&am…

什么是自动化测试?如何做自动化测试?

前面介绍了功能测试和接口测试&#xff0c;在介绍接口测试时提到了实现API自动化。那具体什么是自动化&#xff0c;为什么要做自动化&#xff0c;这里我们集中总结。 一. 什么是自动化&#xff1f; 顾名思义&#xff0c;自动化测试是相对人工测试而言的&#xff0c;它是指把人…

易云维®医院后勤一站式服务平台实现对医院人、物、设备进行信息化管理

传统后勤移动系统的缺陷 使用的门槛和成本高。在国内只有一些大医院开展及应用&#xff0c;由于传统移动运维系统需要定制软件、结合专用平板使用&#xff0c;导致整体项目价格昂贵&#xff0c;故一般采购医院配置的平板少&#xff0c;从而影响记录实时互动追踪的效果&#xf…

如何克服预测性维护中IT和OT的融合挑战?

预测性维护&#xff08;Predictive Maintenance&#xff0c;简称PdM&#xff09;在现代制造业中扮演着关键角色&#xff0c;通过实时数据分析和资产监控&#xff0c;帮助企业预测设备故障&#xff0c;优化维护计划&#xff0c;并提高生产效率。然而&#xff0c;PdM的成功实施面…