ASP.NET Core MVC过滤器

news2025/1/23 12:04:52

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 οnclick="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();

  

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

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

相关文章

可视化开源编辑器Swagger Editor本地部署并实现远程访问管理编辑文档

最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 文章目录 Swagger Editor本地接口文档公网远程访问1. 部署Swagge…

【Python百练——第3练】矩形类及操作

&#x1f490;作者&#xff1a;insist-- &#x1f490;个人主页&#xff1a;insist-- 的个人主页 理想主义的花&#xff0c;最终会盛开在浪漫主义的土壤里&#xff0c;我们的热情永远不会熄灭&#xff0c;在现实平凡中&#xff0c;我们终将上岸&#xff0c;阳光万里 ❤️欢迎点…

一缕青丝寄相思

10年8月16日七夕节男孩向女孩表白,女孩不知道那天是七夕,也没有读懂男孩的爱,女孩在9月22日中秋,向男孩打开了心门,男孩却没有懂女孩的心思.13年后的一封问候邮件,一束女孩的长发和回不去的青春 洒满阳光的午后 转眼间看到你的笑脸 微笑着你对我说 遇上你认识我真好 你说得好莫…

论文解读--Robust lane detection and tracking with Ransac and Kalman filter

使用随机采样一致性和卡尔曼滤波的鲁棒的车道线跟踪 摘要 在之前的一篇论文中&#xff0c;我们描述了一种使用霍夫变换和迭代匹配滤波器的简单的车道检测方法[1]。本文扩展了这项工作&#xff0c;通过结合逆透视映射来创建道路的鸟瞰视图&#xff0c;应用随机样本共识来帮助消…

力扣日记12.3-【二叉树篇】二叉树的所有路径

力扣日记&#xff1a;【二叉树篇】二叉树的所有路径 日期&#xff1a;2023.12.3 参考&#xff1a;代码随想录、力扣 257. 二叉树的所有路径 题目描述 难度&#xff1a;简单 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径…

JAVA代码优化:CommandLineRunner(项目启动之前,预先加载数据)

CommandLineRunner接口是Spring Boot框架中的一个接口&#xff0c;用于在应用程序启动后执行一些特定的代码逻辑。它是一个函数式接口&#xff0c;只包含一个run方法&#xff0c;该方法在应用程序启动后被自动调用。可以帮助我们在应用程序启动后自动执行一些代码逻辑&#xff…

sharding-jdbc实现分库分表

shigen日更文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 &#x1f605;&#x1f605;最近几天的状态有点不对&#xff0c;所以有几天没有更新了。 当我们的数据量比较大…

css实现简单的抽奖动画效果和旋转效果,还有春联效果

使用css的animation和transform和transition可以实现简单的图片放大缩小&#xff0c;旋转&#xff0c;位移的效果&#xff0c;由此可以延伸的动画效果还是挺多的&#xff0c;比如图片慢慢放大&#xff0c;图片慢慢旋转并放大&#xff0c;图片慢慢变化位置等等&#xff0c; 抽奖…

使用pytorch从零开始实现迷你GPT

生成式建模知识回顾: [1] 生成式建模概述 [2] Transformer I&#xff0c;Transformer II [3] 变分自编码器 [4] 生成对抗网络&#xff0c;高级生成对抗网络 I&#xff0c;高级生成对抗网络 II [5] 自回归模型 [6] 归一化流模型 [7] 基于能量的模型 [8] 扩散模型 I, 扩散模型 II…

MathType 7.5.2中文版软件使用期到了怎么办?

MathType 7.5.2中文版作为一款专业的公式编辑器&#xff0c;MathType受到很多人的青睐&#xff0c;它可以将编辑好的公式保存成多种图片格式或透明图片模式&#xff0c;可以很方便的添加或移除符号、表达式等模板&#xff08;只需要简单地用鼠标拖进拖出即可)&#xff0c;也可以…

Verilog 入门(八)(验证)

文章目录 编写测试验证程序波形产生值序列重复模式 测试验证程序实例从文本文件中读取向量实例&#xff1a;时序检测器 测试验证程序用于测试和验证设计方法的正确性。Verilog 提供强有力的结构来说明测试验证程序。 编写测试验证程序 测试验证程序有三个主要目的&#xff1a;…

Java 表达式引擎

企业的需求往往是多样化且复杂的&#xff0c;对接不同企业时会有不同的定制化的业务模型和流程。我们在业务系统中使用表达式引擎&#xff0c;集中配置管理业务规则&#xff0c;并实现实时决策和计算&#xff0c;可以提高系统的灵活性和响应能力。 引入规则引擎似乎就能解决这个…

UE学习C++(1)创建actor

创建新C类 在 虚幻编辑器 中&#xff0c;点击 文件&#xff08;File&#xff09; 下拉菜单&#xff0c;然后选择 新建C类...&#xff08;New C Class...&#xff09; 命令&#xff1a; 此时将显示 选择父类&#xff08;Choose Parent Class&#xff09; 菜单。可以选择要扩展的…

你知道MySQL 中的 order by 是怎么工作的吗?

欢迎大家到我的博客浏览。order by是怎么工作的&#xff1f; | YinKais Blog今天我们来看一下 MySQL 中 “ order by ” 是怎么工作的。 我们以一个实际的例子&#xff0c;来探讨这个问题&#xff1a; 假设我们的表是这样定义的&#xff1a; CREATE TABLE t (id int(11) NOT…

数据结构和算法-树与二叉树的存储结构以及树和二叉树和森林的遍历

文章目录 二叉树的存储结构二叉树的顺序存储二叉树的链式存储小结 二叉树的先中后序遍历例题小结 二叉树的层次遍历小结 由遍历序列构造二叉树一个遍历序列即使给定了前中后序&#xff0c;也不能确定该二叉树的形态可以确定的序列组合前序中序后序中序层序中序 小结若前序&…

LeetCode的几道题

一、捡石头 292 思路就是&#xff1a; 谁面对4块石头的时候&#xff0c;谁就输&#xff08;因为每次就是1-3块石头&#xff0c;如果剩下4块石头&#xff0c;你怎么拿&#xff0c;我都能把剩下的拿走&#xff0c;所以你就要想尽办法让对面面对4块石头的倍数&#xff0c; 比如有…

如何使用手机制作证件照

1、打开vx搜索小&#x1f34a;x名称&#xff1a;标准证件照免冠照 2、选择你需要的证件照尺寸类型 3&#xff0e;选择手机照片生活照或者点击开始拍摄&#xff08;建议纯色的墙面好换底色&#xff09; 4&#xff0e;选择背景颜色&#xff0c;红底&#xff0c;蓝底奉背景颜色随你…

LeetCode力扣每日一题(Java):1、两数之和

一、题目 二、解题思路 方法一&#xff1a;暴力枚举 这是最容易想到的一种方法&#xff0c;本质就是二重循环遍历数组&#xff0c;话不多说直接上代码 public int[] twoSum(int[] nums, int target) {for (int i 0; i < nums.length - 1; i) {for (int j i 1; j < …

纯js实现录屏并保存视频到本地的尝试

前言&#xff1a;先了解下&#xff1a;navigator.mediaDevices&#xff0c;mediaDevices 是 Navigator 只读属性&#xff0c;返回一个 MediaDevices 对象&#xff0c;该对象可提供对相机和麦克风等媒体输入设备的连接访问&#xff0c;也包括屏幕共享。 const media navigator…

【动态规划】LeetCode-931.下降路径最小和

&#x1f388;算法那些事专栏说明&#xff1a;这是一个记录刷题日常的专栏&#xff0c;每个文章标题前都会写明这道题使用的算法。专栏每日计划至少更新1道题目&#xff0c;在这立下Flag&#x1f6a9; &#x1f3e0;个人主页&#xff1a;Jammingpro &#x1f4d5;专栏链接&…