首页 > 编程语言 >【ASP.NET Core】MVC过滤器:常见用法

【ASP.NET Core】MVC过滤器:常见用法

时间:2023-12-02 22:45:08浏览次数:47  
标签:Core ASP class response MVC context 过滤器 异常 public

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

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();

好,试试看。

 

标签:Core,ASP,class,response,MVC,context,过滤器,异常,public
From: https://www.cnblogs.com/tcjiaan/p/17871967.html

相关文章

  • .net core Razor Page TempData不工作,RedirectToPage后无法获取值怎么办?
    问题:.netcore旧项目更新到.netcore8.0后,发现之前的错误反馈信息显示不出来了,经过反复搜索,询问人工智能无果。之前怀疑/测试过:1.新版浏览器chrome访问https://localhost是否限制了Cookie2.浏览器是否受欧盟Cookie法规的要求进行了限制。3.写法错误RazorpageTempData......
  • .NET Core|--调用C++库|--docker环境下让web api应用程序调用C++类库
    前言#前提安装docker环境~启动docker~#多说一句,为什么我要搞这个一个镜像,既包含gcc开发环境,又包含.NET开发环境我的api应用程序是基于.NET写的,但是我的这个api程序,又要调用c++的一些东西,特别是涉及一些画图之类的,所以就需要gcc的开发环境,最终搞了这么一......
  • ASP.NET MVC Cookie的一个问题
    代码项目启动,第一次访问该方法publicActionResultIndex(){Response.Cookies["Test"].Value="TestCookieValue";varresult=Request.Cookies["Test"].Value;//此处result会是什么?returnView();}答案:TestCookieValue这Response还没返回......
  • .net core 使用Task多线程执行任务,限制线程数量,并等待所有任务结束
    usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;namespaceDataService.ETL_ApiData{publicclassMultiTask{///<summary>///最大线程数量///</summa......
  • QT-对于MVC中典型QTreeView简单使用参考记录
    //创建以ui文件中对应View为载体的model<-此处使用QStandardItemModel(比较常用)QStandardItemModel*model=newQStandardItemModel(ui->treeView);model->setHorizontalHeaderLabels(QStringList()<<QStringLiteral("国家")<<QStringLiteral("省份"......
  • .net7(.net core) 依赖注入:从 AddSingleton 注册的类里面访问 AddScoped 的问题
    记录一下以免忘记。今天从NopCommerce开源项目里面把它的任务调度类拆出来到我的项目用的时候,发现报错,报错信息如下Someservicesarenotabletobeconstructed(Errorwhilevalidatingtheservicedescriptor'ServiceType:OUC.Services.ScheduleTasks.ITaskSchedulerLi......
  • .net core(web api) 后台 +uniapp移动端自动更新
    移动端采用uniapp开发后台采用.netcore先到插件市场找到对应的插件  然后将插件安装到 然后在移动端项目pages.json"path":"uni_modules/rt-uni-update/components/rt-uni-update/rt-uni-update","style":{"app-plus":{"......
  • 从零开始的 dbt 入门教程 (dbt-core 基础篇)
    最近一直在处理数据分析和数据建模的事情,所以接触了dbt等数据分析的工具,国内目前对于dbt比较详细的资料不多,所以打算写四道五篇dbt相关的文章,本文属于dbt系列的第一篇,本篇主要阐述dbt一些基本概念,教会你如何配置dbt连接远端数据库,并运行你的第一个数据模型,那么本文开......
  • 通过.NET Core+Vue3 实现SignalR即时通讯功能
    .NETCore和Vue3结合使用SignalR可以实现强大的实时通讯功能,允许实时双向通信。在这个示例中,我们将详细说明如何创建一个简单的聊天应用程序,演示如何使用.NETCoreSignalR后端和Vue3前端来实现实时通讯功能。步骤1:准备工作确保你已经安装了以下工具和环境:.NETCore......
  • 黑客玩具入门——5、继续Metasploit
    1、利用FTP漏洞并植入后门实验靶机:Metasploitable2。实践:使用nmap扫描目标靶机nmap-sVxxx.xxx.xxx.xxx(目标ip)生成linux系统后门msfvenom-plinux/x86/meterpreter/reverse_tcpLHOST=xxx.xxx.xxx.xxx(主控端ip)LPORT=5555-felf-o/home/xxxx(用户名)/backdoor.elf......