【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();
好,试试看。

【ASP.NET Core】MVC过滤器:常见用法的更多相关文章
- 解说asp.net core MVC 过滤器的执行顺序
asp.net core MVC 过滤器会在请求管道的各个阶段触发.同一阶段又可以注册多个范围的过滤器,例如Global范围,controller范围等.以ActionFilter为例,我们来看看过滤 ...
- ASP.NET Core MVC 模型绑定用法及原理
前言 查询了一下关于 MVC 中的模型绑定,大部分都是关于如何使用的,以及模型绑定过程中的一些用法和概念,很少有关于模型绑定的内部机制实现的文章,本文就来讲解一下在 ASP.NET Core MVC ...
- ASP.NET Core MVC 过滤器介绍
过滤器的作用是在 Action 方法执行前或执行后做一些加工处理.使用过滤器可以避免Action方法的重复代码,例如,您可以使用异常过滤器合并异常处理的代码. 过滤器如何工作? 过滤器在 MVC Ac ...
- ASP.NET Core MVC 过滤器
参考网址:https://www.cnblogs.com/dotNETCoreSG/p/aspnetcore-4_4_3-filters.html ASP.NET Core有五种类型的过滤器,每个过滤 ...
- asp.net core MVC 全局过滤器之ExceptionFilter异常过滤器(一)
本系类将会讲解asp.net core MVC中的内置全局过滤器的使用,将分为以下章节 asp.net core MVC 过滤器之ExceptionFilter异常过滤器(一) asp.net cor ...
- asp.net core MVC 过滤器之ExceptionFilter过滤器(一)
简介 异常过滤器,顾名思义,就是当程序发生异常时所使用的过滤器.用于在系统出现未捕获异常时的处理. 实现一个自定义异常过滤器 自定义一个异常过滤器需要实现IExceptionFilter接口 publ ...
- asp.net core MVC 过滤器之ActionFilter过滤器(二)
本系类将会讲解asp.net core MVC中的内置全局过滤器的使用,将分为以下章节 asp.net core MVC 过滤器之ExceptionFilter过滤器(一) asp.net core ...
- Asp.Net Core MVC框架内置过滤器
第一部分.MVC框架内置过滤器 下图展示了Asp.Net Core MVC框架默认实现的过滤器的执行顺序: Authorization Filters:身份验证过滤器,处在整个过滤器通道的最顶层.对应 ...
- ASP.NET Core MVC中的 [Required]与[BindRequired]
在开发ASP.NET Core MVC应用程序时,需要对控制器中的模型校验数据有效性,元数据注释(Data Annotations)是一个完美的解决方案. 元数据注释最典型例子是确保API的调用者提供 ...
- Pro ASP.Net Core MVC 6th 第四章
第四章 C# 关键特征 在本章中,我描述了Web应用程序开发中使用的C#特征,这些特征尚未被广泛理解或经常引起混淆. 这不是关于C#的书,但是,我仅为每个特征提供一个简单的例子,以便您可以按照本书其余 ...
随机推荐
- 解读 --- Span<T>
引言 Span<T> 是C# 中的一种结构体,它是一种内存安全的类型,可以用来表示连续的内存区域.Span<T> 可以被用于访问和操作数组.堆上分配的内存和栈上分配的内存.使用 ...
- 【升职加薪秘籍】我在服务监控方面的实践(8)-elasticsearch 性能监控与分析手段
大家好,我是蓝胖子,之前讲了mysql,redis中间件的监控,今天我们再来看看另一个基础组件elasticsearch,如何对它进行监控,当你思考如何对一个组件进行监控时,四大黄金指标会告诉你答案, ...
- 记一次weak_up函数绕过
2023 蓝帽杯CTF LovePHP 因为比赛已经结束,所以复现环境是从本地进行复现,这次比赛本来排名挺靠前的,原本总排名是60多名,赛区排名30多名,本来是以为有希望进入半决赛的,但是没想到比赛结 ...
- C# object类型与dynamic类型的使用
获取动态变化的类型属性 例: var类型的参数a中包含属性Name或Age 获取这个不固定的数据 首先验证a中存在的是什么属性 /// <summary> /// 验证object类型是否 ...
- 用 Rust 的 declarative macro 做了个小东西
最近几天在弄 ddnspod 的时候,写了个宏: custom_meta_struct 解决什么问题 #[derive(Debug, Clone, serde::Serialize, serde::D ...
- 简述Spring Cache缓存策略
一.简介 Spring框架提供了一种名为Spring Cache的缓存策略.Spring Cache是一种抽象层,它提供了一种方便的方式来管理缓存,并与Spring应用程序中的各种缓存实现(如EhCa ...
- 用Rust手把手编写一个Proxy(代理), TLS加密通讯
用Rust手把手编写一个Proxy(代理), TLS加密通讯 项目 ++wmproxy++ gite: https://gitee.com/tickbh/wmproxy github: https:/ ...
- 2023-09-27:用go语言,在一个 n x n 的国际象棋棋盘上,一个骑士从单元格 (row, column) 开始, 并尝试进行 k 次移动。行和列是 从 0 开始 的,所以左上单元格是 (0
2023-09-27:用go语言,在一个 n x n 的国际象棋棋盘上,一个骑士从单元格 (row, column) 开始, 并尝试进行 k 次移动.行和列是 从 0 开始 的,所以左上单元格是 (0 ...
- 【短道速滑十】非局部均值滤波的指令集优化和加速(针对5*5的搜索特例,可达到单核1080P灰度图 28ms/帧的速度)。
非局部均值滤波(Non Local Means)作为三大最常提起来的去燥和滤波算法之一(双边滤波.非局部均值.BM3D),也是有着很多的论文作为研究和比较的对象,但是也是有着致命的缺点,速度慢,严重的 ...
- PostgreSQL学习笔记-2.基础知识:INSERT、SELECT、运算符、表达式、约束
PostgreSQL INSERT INTO 语句用于向表中插入新记录,兼容SQL通用语法. 语法 INSERT INTO 语句语法格式如下: INSERT INTO TABLE_NAME (colu ...