前面老周给大伙伴们演示了过滤器的运行流程,大伙只需要知道下面知识点即可:

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过滤器:常见用法的更多相关文章

  1. 解说asp.net core MVC 过滤器的执行顺序

    asp.net core MVC 过滤器会在请求管道的各个阶段触发.同一阶段又可以注册多个范围的过滤器,例如Global范围,controller范围等.以ActionFilter为例,我们来看看过滤 ...

  2. ASP.NET Core MVC 模型绑定用法及原理

    前言 查询了一下关于 MVC 中的模型绑定,大部分都是关于如何使用的,以及模型绑定过程中的一些用法和概念,很少有关于模型绑定的内部机制实现的文章,本文就来讲解一下在 ASP.NET Core MVC ...

  3. ASP.NET Core MVC 过滤器介绍

    过滤器的作用是在 Action 方法执行前或执行后做一些加工处理.使用过滤器可以避免Action方法的重复代码,例如,您可以使用异常过滤器合并异常处理的代码. 过滤器如何工作? 过滤器在 MVC Ac ...

  4. ASP.NET Core MVC 过滤器

    参考网址:https://www.cnblogs.com/dotNETCoreSG/p/aspnetcore-4_4_3-filters.html ASP.NET Core有五种类型的过滤器,每个过滤 ...

  5. asp.net core MVC 全局过滤器之ExceptionFilter异常过滤器(一)

    本系类将会讲解asp.net core MVC中的内置全局过滤器的使用,将分为以下章节 asp.net core MVC 过滤器之ExceptionFilter异常过滤器(一) asp.net cor ...

  6. asp.net core MVC 过滤器之ExceptionFilter过滤器(一)

    简介 异常过滤器,顾名思义,就是当程序发生异常时所使用的过滤器.用于在系统出现未捕获异常时的处理. 实现一个自定义异常过滤器 自定义一个异常过滤器需要实现IExceptionFilter接口 publ ...

  7. asp.net core MVC 过滤器之ActionFilter过滤器(二)

    本系类将会讲解asp.net core MVC中的内置全局过滤器的使用,将分为以下章节 asp.net core MVC 过滤器之ExceptionFilter过滤器(一) asp.net core ...

  8. Asp.Net Core MVC框架内置过滤器

    第一部分.MVC框架内置过滤器 下图展示了Asp.Net Core MVC框架默认实现的过滤器的执行顺序: Authorization Filters:身份验证过滤器,处在整个过滤器通道的最顶层.对应 ...

  9. ASP.NET Core MVC中的 [Required]与[BindRequired]

    在开发ASP.NET Core MVC应用程序时,需要对控制器中的模型校验数据有效性,元数据注释(Data Annotations)是一个完美的解决方案. 元数据注释最典型例子是确保API的调用者提供 ...

  10. Pro ASP.Net Core MVC 6th 第四章

    第四章 C# 关键特征 在本章中,我描述了Web应用程序开发中使用的C#特征,这些特征尚未被广泛理解或经常引起混淆. 这不是关于C#的书,但是,我仅为每个特征提供一个简单的例子,以便您可以按照本书其余 ...

随机推荐

  1. [selenium]取值元素文本属性样式

    前言 版本: python:3.9 selenium:4.1.5 获取元素文本 text = driver.find_element(by=By.XPATH, value=""). ...

  2. 基于CUBEMX的STM32F4 Hal库,配置LVGL(无操作系统版)

    本篇文章移植思路适用于所有嵌入式MCU,包括Arm,STM32,NXP,乐鑫,Nuvoton,Arduino,RT-Thread,Zephyr,NuttX,Adafruit等等. 为什么要写这一篇移植 ...

  3. 《Pro Git》起步笔记

    @ 目录 什么是版本控制 本地版本控制系统 集中化的版本控制 分布式的版本控制系统 Git简史 Git是什么 安装Git 在Linux上安装 在Windows上安装 初次运行Git前的配置 用户信息 ...

  4. 【技术积累】Vue.js中的CSS过渡【一】

    CSS过渡是什么 在Vue中,可以使用<transition>组件来实现CSS过渡效果.CSS过渡是指在元素的状态发生改变时,通过添加或移除CSS类来实现平滑的过渡效果. <tran ...

  5. 深入了解API接口技术及其应用

    尊敬的读者们,大家好!在互联网时代,API(Application Programming Interface)接口已经成为开发者们连接各种应用.获取数据的重要工具.今天,我们将深入探讨API接口技术 ...

  6. HiAI Foundation助力端侧音视频AI能力,高性能低功耗释放云侧成本

    过去三年是端侧AI高速发展的几年,华为在2020年预言了端侧AI的发展潮流,2021年通过提供端云协同的方式使我们的HiAI Foundation应用性更进一个台阶,2022年提供视频超分端到端的解决 ...

  7. Mysql优化篇-索引优化与查询优化

    1.索引失败案列 如果查询时没有使用索引,查询语句就会扫描表中所有记录,在数据量大的情况下,查询会很慢. (1)全值匹配 (2)最佳左前缀法则 mysql可以为多个字段创建索引,一个索引可以包括16个 ...

  8. 🖖少年,该升级 Vue3 了!

    你好,我是 Kagol. 前言 根据 Vue 官网文档的说明,Vue2 的终止支持时间是 2023 年 12 月 31 日,这意味着从明年开始: Vue2 将不再更新和升级新版本,不再增加新特性,不再 ...

  9. Solution -「洛谷 P1852」跳跳棋

    Description Link. 在一个数轴上给你三个点,移动方法是彼此为中点进行跳跃,不能同时越过两颗棋子. 给出初始状态和目标状态,问能否从初始状态跳到目标状态.若能,输出最少步数. 棋子之间互 ...

  10. 43道Python经典案例题(有答案)

    1.有四个数字:1.2.3.4,能组成多少个互不相同且无重复数字的三位数?各是多少? for x in range(0,5): for y in range(0,5): for z in range( ...