【ASP.NET Core】MVC過濾器:常見用法

来源:https://www.cnblogs.com/tcjiaan/archive/2023/12/02/17871967.html
-Advertisement-
Play Games

前面老周給大伙伴們演示了過濾器的運行流程,大伙只需要知道下麵知識點即可: 1、過濾器分為授權過濾、資源訪問過濾、操作方法(Action)過濾、結果過濾、異常過濾、終結點過濾。上一次咱們沒有說異常過濾和終結點過濾,不過老周後面會說的。對這些過濾器,你有印象就行了。 2、所有過濾器介面都有同步版本和非同步 ...


前面老周給大伙伴們演示了過濾器的運行流程,大伙只需要知道下麵知識點即可:

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

好,試試看。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 理解 async/await 的原理和使用方法是理解現代JavaScript非同步編程的關鍵。這裡我會提供一個詳細的實例,涵蓋原理、流程、使用方法以及一些註意事項。代碼註釋會儘量詳盡,確保你理解每個步驟。 實例:使用async/await進行非同步操作 <!DOCTYPE html> <html lan ...
  • 本文檔譯自 www.codeproject.com 的文章 "Calling Conventions Demystified",作者 Nemanja Trifunovic,原文參見此處 引言 - Introduction 在學習 Windows 編程的漫長、艱難而美妙的旅途中,你可能會對函數聲明前出 ...
  • 如何使用mysql實現可重入的分散式鎖 目錄 什麼是分散式鎖? 如何實現分散式鎖? 定義分散式表結構 定義鎖統一介面 使用mysql來實現分散式鎖 ① 生成線程標記ID ② 加鎖 ③ 解鎖 ④ 重置鎖 寫在最後 1. 什麼是分散式鎖? 百度百科:分散式鎖是控制分散式系統之間同步訪問共用資源的一種方式 ...
  • Scikit-learn是一個基於Python的開源機器學習庫,它提供了大量的機器學習演算法和工具,方便用戶進行數據挖掘、分析和預測。 Scikit-learn是基於另外兩個知名的庫 Scipy 和 Numpy的,關於 Scipy 和 Numpy 等庫,之前的系列文章中有介紹: Scipy 基礎系列 ...
  • 第二部分主要涵蓋了 SpringMVC 中作用域處理,介紹了 Request 作用域、Session 作用域和應用作用域的處理方式,以及 @ModelAttribute 註解的使用和 ModelAndView 的使用方法;最後,探討了靜態資源的處理方式,包括使用 DefaultServlet 或者 ... ...
  • acwing week2 基礎演算法3總結 總結點1:雙指針演算法 //常用模版框架 for (int i = 0, j = 0; i < n; i ++ ) { while (j < i && check(i, j)) j ++ ; } 常見問題分類: (1) 對於一個序列,用兩個指針維護一段區間 ( ...
  • 小市值選股策略的核心在於通過綜合分析公司的基本面、行業定位、財務健康狀況以及市場趨勢, 來尋找那些被市場低估但具備顯著成長潛力的股票,同時也要重視風險管理和投資組合的多樣化。 今天來給大家分享下小市值策略代碼如下: # 顯式導入 BigQuant 相關 SDK 模塊 from bigdatasour ...
  • 選項用來提供對相關設置的強類型訪問,讀取配置首選使用選項模式。選項無法脫離容器使用,依賴容器,實現了選項不同的訪問方式。選項模式使用了泛型包裝器,因此具備瞭如下優點: 不需要顯示註冊選項具體類型,只需要將泛型包裝器註入到容器中; 對於選項實例的評估推遲到獲取IOptions.Value時進行,而不是 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...