【ASP.NET Core】MVC过滤器:常见用法
前面老周给大伙伴们演示了过滤器的运行流程,大伙只需要知道下面知识点即可:
1、过滤器分为授权过滤、资源访问过滤、操作方法(Action)过滤、结果过滤、异常过滤、终结点过滤。上一次咱们没有说异常过滤和终结点过滤,不过老周后面会说的。对这些过滤器,你有印象就行了。
2、所有过滤器接口都有同步版本和异步版本。为了让伙伴不要学得太累,咱们暂时只说同步版本的。
3、过滤器的应用可以分为全局和局部。全局先运行,局部后运行。全局在应用程序初始化时配置,局部用特性类来配置。
4、实际应用中,我们不需要实现所有过滤器接口,需要啥就实现啥即可。比如,你想在 Action 调用后修改一些东西,那实现 IActionFilter 接口就好了,其他不用管。
本篇咱们的重点在于“用”,光知道是啥是不行的,得拿来用才是硬道理。
我们先做第一个练习:阻止控制器的参数从查询字符串获取数据。
什么意思呢?咱们知道,MVC 在模型绑定时,会从 N 个 ValueProvider 中提取数据值,包括 QueryString、Forms、RouteData 等。其中,QueryString 就是URL的查询字符串。比如,咱们写一个这样的控制器:
public class GameController : ControllerBase { [HttpGet("game/play")] public string Play(Game g) { if(ModelState.IsValid == false) { return "你玩个寂寞"; } return $"你正在玩{g.Year}年出的《{g.GameName}》游戏"; } } public class Game { /// <summary> /// 游戏序列号 /// </summary> public string? GameSerial { get; set; } /// <summary> /// 游戏名称 /// </summary> public string? GameName { get; set; } /// <summary> /// 谁发行的 /// </summary> public string? Publisher { get; set; } /// <summary> /// 哪一年发行的 /// </summary> public int Year { get; set; } }
这个通过 /game/play?gameserial=DDSBYCL-5K2FF&gamename=伏地魔三世&publisher=无德无能科技有限公司&year=2017 这样的URL就能传递数据给 g 参数。
这里我不做 HTML 页了,直接通过 MapGet 返回 HTML 内容。
app.MapGet("/", async (HttpContext context) => { string html = """ <!DOCTYPE html> <html> <head> <title>试试看</title> <style> label { min-width: 100px; display: inline-block; } </style> </head> <body> <div> <label for="gserial">游戏序列号:</label> <input id="gserial" type="text" /> </div> <div> <label for="gname">游戏名称:</label> <input id="gname" type="text" /> </div> <div> <label for="pub">发行者:</label> <input type="text" id="pub" /> </div> <div> <label for="year">发行年份:</label> <input id="year" type="text"/> </div> <div> <button onclick="reqTest()">确定</button> </div> <p id="res"></p> <script> function reqTest() { let serial= document.getElementById("gserial").value; let name = document.getElementById("gname").value; let pub = document.getElementById("pub").value; let year = parseInt(document.getElementById("year").value, 10); let result = document.getElementById("res"); const url = `/game/play?gameSerial=${serial}&gamename=${name}&publisher=${pub}&year=${year}`; fetch(url, { method: "GET" }) .then(response => { response.text().then(txt => { result.innerHTML = txt; }); }); } </script> </body> </html> """; var response = context.Response; response.Headers.ContentType = "text/html; charset=UTF-8"; await response.WriteAsync(html); });
设置响应的 Content-Type 头时一定要指定字符集是 UTF-8 编码,这样可以免去 99.999% 的乱码问题。向服务器发送请求是通过 fetch 函数实现的。
比如,咱们在页面上填写:
然后点一下“确定”按钮,提交成功后服务将响应:
你正在玩2022年出的《法外狂徒大冒险》游戏
模型绑定的数据是从查询字符串提取出来的。现在,咱们写一个过滤器,阻止 QueryStringValueProvider 提供查询字符串数据。而 QueryStringValueProvider 实例是由 QueryStringValueProviderFactory 工厂类负责创建的。因此,需要写一个过滤器,在模型绑定之前删除 QueryStringValueProviderFactory 对象,这样模型绑定时就不会读取 URL 中的查询字符串了。
于是,重点就落在选用哪种过滤器。关键点是:必须在模型绑定前做这项工作。所以,Action过滤器、结果过滤器就别指望了,而且肯定不是授权过滤器,那就剩下资源过滤器了。
咱们写一个自定义的资源过滤器—— RemoveQueryStringProviderFilter,实现的接口当然是 IResourceFilter 了。
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; public class RemoveQueryStringProviderFilter : IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { // 空空如也 } public void OnResourceExecuting(ResourceExecutingContext context) { var qsValueProviders = context.ValueProviderFactories.OfType<QueryStringValueProviderFactory>(); if (qsValueProviders != null && qsValueProviders.Any()) { context.ValueProviderFactories.RemoveType<QueryStringValueProviderFactory>(); } } }
我们要做的事情是在模型绑定之前才有效,所以 OnResourceExecuted 方法不用管,留白即可。在 OnResourceExecuting 方法中,首先用 ValueProviderFactories.OfType<T> 方法找出现有的 QueryStringValueProviderFactory 对象,若找到,用 RemoveType 方法删除。
把这个过滤器应用于全局。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(options => { options.Filters.Add<RemoveQueryStringProviderFilter>(); }); var app = builder.Build();
现在,咱们再运行应用程序,输入游戏信息。
点击“确定”按钮后,发现服务未响应正确的内容。
你正在玩0年出的《》游戏
由于无法从查询字符串中提取到数据,所以返回默认属性值。为什么不是返回“你玩个寂寞”呢?因为模型绑定并没有出错,也未出现验证失败的值,只是未提供值而已,即 ModelState.IsValid 的值依然是 true 的。
下面咱们看看异常过滤器怎么用。
异常过滤器最好定义为局部的,而非全局。使用局部过滤器的好处是针对性好,全局的话你用一个万能异常处理好像过于简单。当然了,如果你的项目可以这样做,就随便用。
这里我定义一个局部的异常过滤器,通过特性方式应用到操作方法上。
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class CustExceptionFilterAttribute : Attribute, IExceptionFilter { public void OnException(ExceptionContext context) { // 看看有没有异常 if (context.Exception is null || context.ExceptionHandled) return; // 有异常哦,修改一下返回结果 ContentResult result = new(); result.Content = "出错啦,伙计。错误消息:" + context.Exception.Message; // 设置返回结果 context.Result = result; // 标记异常已处理 context.ExceptionHandled = true; } }
首先,context 参数是一种上下文对象,它在多上异常过滤器间共享实例(你可能实现了很多个异常过滤器)。context.Exception 属性引用的是异常类对象;注意一个有趣的属性 ExceptionHandled,它是一个 bool 类型的值,表示“我这个异常过滤器是否已处理过了”。这个有啥用呢?由于上下文是共享的,当你的某个异常过滤器设置了 ExceptionHandled 为 true,那么,其他异常过滤器也可以读这个属性。这样就可以实现:啊,原来有人处理过这个异常了,那我就不处理了。即 A 过滤器处理异常后设置为已处理,B 过滤器可以检查这个属性的值,如果没必要处理就跳过。
下面写一个控制器,并在方法成员上应用前面定义的异常过滤器。
public class AbcController : ControllerBase { [HttpGet("calc/add"), CustExceptionFilter] public int Add(int x, int y) { int r = x + y; if(r >= 1000) { throw new Exception("计算结果必须在1000以内"); } return r; } }
此处我设定抛出异常的条件是:x、y 相加结果大于或等于 1000。
咱们以 GET 方式调用,URL 为 /calc/add?x=599&y=699,这样会发生异常。服务器的响应为:
最后一个例子是结果过滤器。咱们要实现在响应消息中添加自定义 Cookie。
public class SetCookieResultFilter : IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { // 不能在这里写 Cookie } public void OnResultExecuting(ResultExecutingContext context) { HttpResponse response = context.HttpContext.Response; // 设置Cookie response.Cookies.Append("X-VER", "3.0"); } }
这里要注意,咱们要写 Cookie 必须在 OnResultExecuting 方法中处理,不能在 OnResultExecuted 方法中写。因为当 OnResultExecuted 方法执行时,响应消息头已经被锁定了,Cookie 是通过 set-cookie 标头实现的,此时无法修改 HTTP 头了,写 Cookie 会抛异常。所以只能在 OnResultExecuting 方法中写。
定义一个控制器。
public class DemoController : ControllerBase { [HttpGet("abc/test")] public string Work() => "山重水复疑无路"; }
将自定义的结果过滤器添加为全局。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(options => { options.Filters.Add<SetCookieResultFilter>(); }); var app = builder.Build();
好,试试看。
相关文章:

【Windows】内网穿透实现hMailServer远程发送邮件
hMailServer 是一个邮件服务器,通过它我们可以搭建自己的邮件服务,通过cpolar内网映射工具即可实现远程发送邮件,不需要使用公网服务器,不需要域名,而且邮件账号名称可以自定义.

C++嵌入式开发:开发嵌入式系统的驱动程序和应用
这是一个简化的示例,实际上,LED驱动程序还需要与硬件进行交互,通过控制寄存器或引脚等方式实现LED的开关。请注意,实际开发中涉及硬件操作,需要根据具体的嵌入式系统和硬件进行适当的配置和调试,确保代码正确地与硬件进行交互。以上示例代码仅展示嵌入式开发中的简单场景,实际的嵌入式开发涉及更多复杂的任务和组件,如中断处理、通信协议、传感器接口等

【C++ Primer Plus】C++11 深入理解右值、右值引用和完美转发
1. 右值引用和移动语义 1.1 左值和右值 左值 local value:存储在内存中、有明确存储地址(可寻址)的数据(x、y、z) 右值 read value:不一定可以寻址,例如存储于寄存器中的数据;通常字面量都是右值,除了字符串常量(1、3) int x = 1; int y =

【HTTP协议】简述HTTP协议的概念和特点
HTTP(Hypertext Transfer Protocol)是一种用于在Web上进行数据通信的协议。它是基于客户端-服务器模型的,其中客户端发送请求,服务器返回响应。

零基础搭建本地Nextcloud私有云结合内网穿透实现远程访问
文章浏览阅读753次,点赞53次,收藏47次。本文主要讲解如何搭建本地Nextcloud私有云结合内网穿透实现远程访问

centos7下执行yum命令报错
文章浏览阅读323次,点赞11次,收藏7次。在Linux系统中,安装nginx时候,需要先安装环境。Nginx是使用C语言开发,安装nginx需要先从官网上将源码下载,然后编译,编译需要gcc环境,但是在安装gcc环境的时候,执行命令报错。

鸿蒙开发软件用什么编程语言?
鸿蒙经过几年的迭代,抛弃了Java,基于TS出了一个官方推荐的ArkTS语言,甩开了JVM,提升效率,同时支持自己研发的一些现代化特性,没有版权的问题,现在唯一的问题就是各大公司愿不愿意为它去适配生态了,还好的是,目前各大互联网公司已经开始适配了。

spring cloud Eureka注册中心和Nacos注册中心
文章浏览阅读103次。代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:@Bean配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则注意。

通过.NET Core+Vue3 实现SignalR即时通讯功能
.NET Core 和 Vue3 结合使用 SignalR 可以实现强大的实时通讯功能,允许实时双向通信。在这个示例中,我们将详细说明如何创建一个简单的聊天应用程序,演示如何使用 .NET Core SignalR 后端和 Vue3 前端来实现实时通讯功能。 步骤1:准备工作 确保你已经安装了以下工

Python爬虫遇到重定向URL问题时如何解决?
文章浏览阅读652次,点赞14次,收藏6次。重定向是指当用户请求一个URL时,服务器返回一个中断请求的URL的响应。这种情况通常发生在网站对URL进行了修改或者重定向到其他页面的情况下。其中,如果处理不当开发,可能会导致爬虫无法获取所需的数据,从而影响爬虫的效果。在Python爬虫开发中,处理重定向URL问题是非常的。我们使用可以请求库来处理重定向,通过查看重定向后的重要URL和重定向历史来了解重定向的情况,从而确保爬虫能够正确获取所需的数据。

C# 实现微信退款及对帐
文章浏览阅读1.6k次,点赞77次,收藏84次。本次我们以微信支付进行举例,在考生注册账号、编写简历、报名职位、被初审核通过等一系列基础的条件的具备下,可以进入支付考务费的环节(笔试费用),我们会为其生成一个支付二维码,考生支付后(无论成功与否),都会记录其支付结果状态。以上提供的代码仅供参考,在实际的应用中,我们还可以根据业务需要编写其它功能,如下载微信官方对帐单,导入到应用系统中,与业务数据进行对帐,以排查争议数据;退款申请成功后,仅为申请状态,需要通过查询退款情况以确定是否完成,该功能可以在考生方进行实现,考生可随时查询自己的对帐情况。

uniapp开发App从开发到上架全过程
当我们的APP开发完毕,最终交付的时候,必然要经历的一个环节,就是APP上架,国内APP上架一般为IOS端appstore上架,安卓端应用商店比较多,最常见的应用商店有华为应用商店、小米应用商店、OPPO应用商店、VIVO应用商店、应用宝应用商店等。 在开始上架 前,需要准备好相应的材料,安卓端

vue实现动态路由菜单!!!
文章浏览阅读244次,点赞2次,收藏2次。递归处理后端响应的菜单树,后依次通过addRoute方法往静态父路由,添加动态子路由,添加完使用el-menu渲染并添加router属性实现路由菜单模式。

GaussDB数据库SQL系列-触发器
文章浏览阅读680次,点赞37次,收藏33次。GaussDB数据库中的触发器是一种强大的工具,可用于自动化数据处理、数据验证、日志记录等任务。通过使用触发器,您可以提高数据一致性、减少数据冗余、实施业务规则并增强数据安全性。本文介绍了GaussDB数据库中触发器的基本概念、创建步骤和示例。希望能够帮助您更好地了解和使用GaussDB中的触发器功能。

本地Nginx服务搭建结合内网穿透实现多个Windows Web站点公网访问
文章浏览阅读1.1k次,点赞96次,收藏91次。访问http://127.0.0.1:9200/登录cpolar web UI管理界面,点击左侧仪表盘的隧道管理——隧道列表,找到所要配置的隧道,点击右侧的编辑。接下来,我们通过强大的且稳定的内网穿透工具cpolar,将本地nginx服务暴露至公网环境,以实现穿透多个站点端口需求,无需公网IP,也不用设置路由器。提示更新隧道成功,点击左侧仪表盘的状态——在线隧道列表,可以看到公网地址已经更新为保留成功的二级子域名,将其复制下来。修改隧道信息,将保留成功的二级子域名配置到隧道中。

swagger注解属性设置导致的报错:For input string: ““
文章浏览阅读61次。使用swagger的时候,用 @ApiModelProperty 修饰的类的属性如果没设置的话默认赋值为“”,当Integer类型的属性没设置example的话就会报错。

go的HTTP网络编程
文章浏览阅读258次,点赞8次,收藏2次。本文教大家用go实现http网络编程

Flask 实现Token认证机制
在Flask框架中,实现Token认证机制并不是一件复杂的事情。除了使用官方提供的`flask_httpauth`模块或者第三方模块`flask-jwt`,我们还可以考虑自己实现一个简易版的Token认证工具。自定义Token认证机制的本质是生成一个令牌(Token),并在用户每次请求时验证这个令牌

elasticsearc DSL查询文档
文章浏览阅读68次。精确查询常见的有哪些?term查询:根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段range查询:根据数值范围查询,可以是数值、日期的范围。

什么是分布式锁?Redis实现分布式锁详解
文章浏览阅读151次,点赞4次,收藏3次。在分布式系统中,涉及多个主机访问同一块资源,此时就需要锁来做互斥控制,避免出现类似线程安全问题。而Java中的synchronized只是对当前进程中的线程有效,多个主机实际上是多个进程,那么它就无能为力了,此时就需要分布式锁。

MySQL运行在docker容器中会损失多少性能
前言 自从使用docker以来,就经常听说MySQL数据库最好别运行在容器中,性能会损失很多。一些之前没使用过容器的同事,对数据库运行在容器中也是忌讳莫深,甚至只要数据库跑在容器中出现性能问题时,首先就把问题推到容器上。 那么到底会损失多少,性能损失会很多吗? 为此我装了两个MySQL,版本都是8.

【Docker】Docker与Kubernetes:区别与优势对比
一种革新性的容器技术一、Docker与Kubernetes简介二、架构和部署模型1. Docker 部署模型2. 构建 Docker 镜像3. 运行容器4. 编排工具三、可移植性和可扩展性1. 可移植性(Portability):2. 可扩展性(Scalability):四、管理和编排能力五、生态系统和社区支持

微信小程序完整实现微信支付功能(SpringBoot和小程序)
然后到提供前端调用支付路由的类,WechatController类,注意我这里路由拼接的有/wechat/pay/notify,这个要和之前配置yml文件的支付回调函数一样,要不然不行。不久前给公司实现支付功能,折腾了一阵子,终于实现了,微信支付对于小白来说真的很困难,特别是没有接触过企业级别开发的大学生更不用说,因此尝试写一篇我如何从小白实现微信小程序支付功能的吧,使用的后端是SpringBoot。效果如下,这里因为我的手机不能截图支付页面,所以用的开发者工具支付的效果,都是一样的。4.前端(小程序端)

IT行业哪个方向比较好就业?
文章浏览阅读12次。在IT行业中,就业前景好的方向有很多,以下是一些比较热门的:

通过内网穿透本地MariaDB数据库,实现在公网环境下使用navicat图形化工具
文章浏览阅读113次,点赞50次,收藏40次。cpolar安装成功后,双击打开cpolar【或者在浏览器上访问本地9200端口 127.0.0.1:9200】,使用cpolar邮箱账号登录 web UI管理界面,如果还没有注册cpolar账号的话,点击免费注册,会跳到cpolar官网注册一个账号就可以了.在浏览器上访问9200端口,http://127.0.0.1:9200/,登录cpolar web ui管理界面,点击左侧仪表盘的隧道管理——隧道列表,找到mariaDB隧道,点击右侧的编辑。修改隧道信息,将保留成功的固定tcp地址配置到隧道中。

MySQL数据库索引以及使用唯一索引实现幂等性
一次和多次请求某一个资源对于资源本身应该具有同样的结果任意多次执行对资源本身所产生的影响均与一次执行的影响相同。

PVE 下虚拟机 Ubuntu 无法进入恢复模式的解决方案——提取原有系统文件
问题说明 某天重启虚拟机 Ubuntu,发现虚拟机只有容器IP,桥接的接口在虚拟机显示状态为 DOWN: 想重启进入恢复模式,却发现恢复模式一直花屏,无法使用: 没有办法了,只能想办法提取原有系统内原有文件。 解决方案 定位虚拟机编号: 找到虚拟机主硬盘: SSH 登录宿主机,执行以下命令 ls -

Nginx 核心配置文 nginx.conf介绍
文章浏览阅读38次。我们都知道浏览器中可以显示的内容有HTML、XML、GIF等种类繁多的文件、媒体等资源,浏览器为了区分这些资源,就需要使用MIME Type。所以说MIME Type是网络资源的媒体类型。Nginx作为web服务器,也需要能够识别前端请求的资源类型。在Nginx的配置文件中,默认有两行配置:用来配置Nginx响应前端请求默认的MIME类型。语法默认值位置在default_type之前还有一句。

如何快速本地搭建悟空CRM结合内网穿透工具高效远程办公
如何快速本地搭建悟空CRM结合内网穿透工具高效远程办公。

Kafka 集群如何实现数据同步?
哈喽大家好,我是咸鱼 最近这段时间比较忙,将近一周没更新文章,再不更新我那为数不多的粉丝量就要库库往下掉了 T﹏T 刚好最近在学 Kafka,于是决定写篇跟 Kafka 相关的文章(文中有不对的地方欢迎大家指出) 考虑到有些小伙伴可能是第一次接触 Kafka ,所以先简单介绍一下什么是 Kafka