ASP.NET MVC 常用擴展點:過濾器、模型綁定等

来源:http://www.cnblogs.com/oppoic/archive/2017/04/29/6407896.html
-Advertisement-
Play Games

一、過濾器(Filter) ASP.NET MVC中的每一個請求,都會分配給對應Controller(以下簡稱“控制器”)下的特定Action(以下簡稱“方法”)處理,正常情況下直接在方法里寫代碼就可以了,但是如果想在方法執行之前或者之後處理一些邏輯,這裡就需要用到過濾器。 常用的過濾器有三個:Au ...


一、過濾器(Filter)

ASP.NET MVC中的每一個請求,都會分配給對應Controller(以下簡稱“控制器”)下的特定Action(以下簡稱“方法”)處理,正常情況下直接在方法里寫代碼就可以了,但是如果想在方法執行之前或者之後處理一些邏輯,這裡就需要用到過濾器。

常用的過濾器有三個:Authorize(授權過濾器),HandleError(異常過濾器),ActionFilter(自定義過濾器),對應的類分別是:AuthorizeAttribute、HandleErrorAttribute和ActionFilterAttribute,繼承這些類並重寫其中方法即可實現不同的功能。

1.Authorize授權過濾器

授權過濾器顧名思義就是授權用的,授權過濾器在方法執行之前執行,用於限制請求能不能進入這個方法,新建一個方法:

public JsonResult AuthorizeFilterTest()
{
    return Json(new ReturnModel_Common { msg = "hello world!" });
}

直接訪問得到結果:

現在假設這個AuthorizeFilterTest方法是一個後臺方法,用戶必須得有一個有效的令牌(token)才能訪問,常規做法是在AuthorizeFilterTest方法里接收並驗證token,但是這樣一旦方法多了,每個方法里都寫驗證的代碼顯然不切實際,這個時候就要用到授權過濾器:

public class TokenValidateAttribute : AuthorizeAttribute
    {
        /// <summary>
        /// 授權驗證的邏輯處理。返回true則通過授權,false則相反
        /// </summary>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            string token = httpContext.Request["token"];
            if (string.IsNullOrEmpty(token))
            {
                return false;
            }
            else
            {
                return true;
            }
        }
    }

新建了一個繼承AuthorizeAttribute的類,並重寫了其中的AuthorizeCore方法,這段偽代碼實現的就是token有值即返回true,沒有則返回false,標註到需要授權才可以訪問的方法上面:

[TokenValidate]
public JsonResult AuthorizeFilterTest()
{
    return Json(new ReturnModel_Common { msg = "hello world!" })
}

標註TokenValidate後,AuthorizeCore方法就在AuthorizeFilterTest之前執行,如果AuthorizeCore返回true,那麼授權成功執行AuthorizeFilterTest裡面的代碼,否則授權失敗。不傳token:

傳token:

不傳token授權失敗時進入了MVC預設的未授權頁面。這裡做下改進:不管授權是成功還是失敗都保證返回值格式一致,方便前端處理,這個時候重寫AuthorizeAttribute類里的HandleUnauthorizedRequest方法即可:

/// <summary>
/// 授權失敗處理
/// </summary>
/// <param name="filterContext"></param>
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    base.HandleUnauthorizedRequest(filterContext);

    var json = new JsonResult();
    json.Data = new ReturnModel_Common
    {
        success = false,
        code = ReturnCode_Interface.Token過期或錯誤,
        msg = "token expired or error"
    };
    json.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
    filterContext.Result = json;
}

效果:

實戰:授權過濾器最廣泛的應用還是做許可權管理系統,用戶登錄成功後服務端輸出一個加密的token,後續的請求都會帶上這個token,服務端在AuthorizeCore方法里解開token拿到用戶ID,根據用戶ID去資料庫里查是否有請求當前介面的許可權,有就返回true,反之返回false。這種方式做授權,相比登錄成功給Cookie和Session的好處就是一個介面PC端、App端共同使用。

2.HandleError異常過濾器

異常過濾器是處理代碼異常的,在系統的代碼拋錯的時候執行,MVC預設已經實現了異常過濾器,並且註冊到了App_Start目錄下的FilterConfig.cs:

filters.Add(new HandleErrorAttribute());

這個生效於整個系統,任何介面或者頁面報錯都會執行MVC預設的異常處理,並返回一個預設的報錯頁面:Views/Shared/Error(程式發到伺服器上報錯時才可以看到本頁面,本地調試許可權高,還是可以看到具體報錯信息的)

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>錯誤</title>
</head>
<body>
    <hgroup>
        <h1>錯誤。</h1>
        <h2>處理你的請求時出錯。</h2>
    </hgroup>
</body>
</html>

預設的異常過濾器顯然無法滿足使用需求,重寫下異常過濾器,應付項目實戰中的需求:

1)報錯可以記錄錯誤代碼所在的控制器和方法,以及報錯時的請求參數和時間;

2)返回特定格式的JSON方便前端處理。因為現在系統大部分是ajax請求,報錯了返回MVC預設的報錯頁面,前端不好處理

新建一個類LogExceptionAttribute繼承HandleErrorAttribute,並重寫內部的OnException方法:

 public override void OnException(ExceptionContext filterContext)
 {
     if (!filterContext.ExceptionHandled)
     {
         string controllerName = (string)filterContext.RouteData.Values["controller"];
         string actionName = (string)filterContext.RouteData.Values["action"];
         string param = Common.GetPostParas();
         string ip = HttpContext.Current.Request.UserHostAddress;
         LogManager.GetLogger("LogExceptionAttribute").Error("Location:{0}/{1} Param:{2}UserIP:{3} Exception:{4}", controllerName, actionName, param, ip, filterContext.Exception.Message);

         filterContext.Result = new JsonResult
         {
             Data = new ReturnModel_Common { success = false, code = ReturnCode_Interface.服務端拋錯, msg = filterContext.Exception.Message },
             JsonRequestBehavior = JsonRequestBehavior.AllowGet
         };
     }
     if (filterContext.Result is JsonResult)
         filterContext.ExceptionHandled = true;//返回結果是JsonResult,則設置異常已處理
     else
         base.OnException(filterContext);//執行基類HandleErrorAttribute的邏輯,轉向錯誤頁面
 }

異常過濾器就不像授權過濾器一樣標註在方法上面了,直接到App_Start目錄下的FilterConfig.cs註冊下,這樣所有的介面都可以生效了:

filters.Add(new LogExceptionAttribute());

異常過濾器里使用了NLog作為日誌記錄工具,Nuget安裝命令:

Install-Package NLog
Install-Package NLog.Config

相比Log4net,NLog配置簡單,僅幾行代碼即可,NLog.config:

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <targets>
    <target xsi:type="File" name="f" fileName="${basedir}/log/${shortdate}.log" layout="${uppercase:${level}} ${longdate} ${message}" />
    <target xsi:type="File" name="f2" fileName="D:\log\MVCExtension\${shortdate}.log" layout="${uppercase:${level}} ${longdate} ${message}" />
  </targets>
  <rules>
    <logger name="*" minlevel="Debug" writeTo="f2" />
  </rules>
</nlog>

如果報錯,日誌就記錄在D盤的log目錄下的MVCExtension目錄下,一個項目一個日誌目錄,方便管理。全部配置完成,看下代碼:

public JsonResult HandleErrorFilterTest()
{
    int i = int.Parse("abc");
    return Json(new ReturnModel_Data { data = i });
}

字元串強轉成int類型,必然報錯,頁面響應:

同時日誌也記錄下來了:

3.ActionFilter自定義過濾器

自定義過濾器就更加靈活了,可以精確的註入到請求前、請求中和請求後。繼承抽象類ActionFilterAttribute並重寫裡面的方法即可:

public class SystemLogAttribute : ActionFilterAttribute
{
    public string Operate { get; set; }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnActionExecuted");
        base.OnActionExecuted(filterContext);
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnActionExecuting");
        base.OnActionExecuting(filterContext);
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnResultExecuted");
        base.OnResultExecuted(filterContext);
    }

    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnResultExecuting");
        base.OnResultExecuting(filterContext);
    }
}

這個過濾器適合做系統操作日誌記錄功能:

[SystemLog(Operate = "添加用戶")]
public string CustomerFilterTest()
{
    Response.Write("<br/>Action 執行中...");
    return "<br/>Action 執行結束";
}

看下結果:

四個方法執行順序:OnActionExecuting—>OnActionExecuted—>OnResultExecuting—>OnResultExecuted,非常精確的控制了整個請求過程。

實戰中記錄日誌過程是這樣的:在OnActionExecuting方法里寫一條操作日誌到資料庫里,全局變數存下這條記錄的主鍵,到OnResultExecuted方法里說明請求結束了,這個時候自然知道用戶的這個操作是否成功了,根據主鍵更新下這條操作日誌的是否成功欄位。

擴展閱讀:

ASP.NET MVC的過濾器

MVC過濾器使用案例:統一處理異常順道精簡代碼

二、模型綁定(ModelBinder)

先看一個普通的方法:

public ActionResult Index(Student student)
{
    return View();
}

這個方法接受的參數是一個Student對象,前端傳遞過來的參數跟Student對象里的屬性保持一直,那麼就自動被綁定到這個對象里了,不需要在方法里new Student這個對象並挨個綁定屬性了,綁定的過程由MVC中的DefaultModelBinder完成的,DefaultModelBinder同時繼承了IModelBinder介面,現在就利用IModelBinder介面和DefaultModelBinder來實現更加靈活的模型綁定。

場景一、前端傳過來了一個加密的字元串token,方法里需要用token里的某些欄位,那就得在方法里接收這個字元串、解密字元串、轉換成對象,這樣一個方法還好說,多了的話重覆代碼非常多,就算提取通用方法,還是要在方法里調用這個通用方法,有沒有辦法直接在參數里就封裝好這個對象?

模型綁定的對象:

public class TokenModel
{
    /// <summary>
    /// 主鍵
    /// </summary>
    public int Id { get; set; }

    /// <summary>
    /// 姓名
    /// </summary>
    public string Name { set; get; }

    /// <summary>
    /// 簡介
    /// </summary>
    public string Description { get; set; }

}

新建一個TokenBinder繼承IModelBinder介面並實現其中的BindModel方法:

public class TokenBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var token = controllerContext.HttpContext.Request["token"];
        if (!string.IsNullOrEmpty(token))
        {
            string[] array = token.Split(':');
            if (array.Length == 3)
            {
                return new TokenModel() { Id = int.Parse(array[0]), Name = array[1], Description = array[2] };
            }
            else
            {
                return new TokenModel() { Id = 0 };
            }
        }
        else
        {
            return new TokenModel() { Id = 0 };
        }
    }
}

這個方法里接收了一個token參數,並對token參數進行瞭解析和封裝。代碼部分完成了需要到Application_Start方法里進行下註冊:

ModelBinders.Binders.Add(typeof(TokenModel), new TokenBinder());

現在模擬下這個介面:

public JsonResult TokenBinderTest(TokenModel tokenModel)
{
    var output = "Id:" + tokenModel.Id + ",Name:" + tokenModel.Name + ",Description:" + tokenModel.Description;
    return Json(new ReturnModel_Common { msg = output });
}

調用下:

可以看出,“1:汪傑:oppoic.cnblogs.com”已經被綁定到tokenModel這個對象裡面了。但是如果稍複雜的模型綁定IModelBinder就無能為力了。

場景二、去除對象某個屬性的首位空格

public class Student
{
    public int Id { get; set; }

    public string Name { get; set; }

    public string Class { get; set; }
}

如果前端傳來的Name屬性有空格,如何去除呢?利用DefaultModelBinder即可實現更靈活的控制

public class TrimModelBinder : DefaultModelBinder
{
    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
    {
        var obj = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
        if (obj is string && propertyDescriptor.Attributes[typeof(TrimAttribute)] != null)//判斷是string類型且有[Trim]標記
        {
            return (obj as string).Trim();
        }
        return obj;
    }
}

標註下需要格式化首位屬性的實體:

[ModelBinder(typeof(TrimModelBinder))]
public class Student
{
    public int Id { get; set; }

    [Trim]
    public string Name { get; set; }

    public string Class { get; set; }
}

好了,測試下:

public JsonResult TrimBinderTest(Student student)
{
    if (string.IsNullOrEmpty(student.Name) || string.IsNullOrEmpty(student.Class))
    {
        return Json(new ReturnModel_Common { msg = "未找到參數" });
    }
    else
    {
        return Json(new ReturnModel_Common { msg = "Name:" + student.Name + ",長度:" + student.Name.Length + " Class:" + student.Class + ",長度:" + student.Class.Length });
    }
}

可見,標註了Trim屬性的Name長度是去除空格的長度:7,而沒有標註的Class屬性的長度則是6。

擴展閱讀:

ASP.NET MVC Model綁定的簡單應用

MVC擴展(ModelBinder)

ASP.NET MVC: 使用自定義 ModelBinder

ASP.NET MVC: 使用自定義 ModelBinder 過濾敏感信息

 

爬蟲可恥,本文原始鏈接:http://www.cnblogs.com/oppoic/p/6407896.html

環境:Visual Studio 2013、ASP.NET MVC 5.0 源碼點我


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

-Advertisement-
Play Games
更多相關文章
  • C盤在使用過程中,內容會越來越多,剩餘空間越來越小。如何清理出更多空間呢?以windows7為例 cleanmgr windows自帶的磁碟清理工具。在運行視窗中執行cleanmgr 如下圖所示,工具會提示可以清空的空間大小,從幾十M到幾十G,都有可能。 減小虛擬記憶體,或將虛擬記憶體移動到其它分區 w ...
  • 如用戶表和電話表,要求搜索時可以模糊查詢姓名和號碼。都可以找到包含該字元的所有用戶。 電話表不能用where去查詢。只能用Any去查詢電話表。 原理不懂。初學。我也是看了 http://www.cnblogs.com/zhaopei/p/5721789.html 這文章才明白怎麼用的~~~ ...
  • 修改app_start/webapiconfig.cs using System.Web.Http; using System.Web.Routing; using Ninject; using TxMobile.Filters; using TxMobile.Models; using WebAp... ...
  • 前言 我們知道目前 .NET Core 還不支持 SMTP 協議,當我麽在使用到發送郵件功能的時候,需要藉助於一些第三方組件來達到目的,今天給大家介紹兩款開源的郵件發送組件,它們分別是 " MailKit " 和 " FluentEmail " , 下麵我對它們分別進行介紹。 MailKit 在 A ...
  • BusterWood.Channels是一個在C#上實現的通道的開源庫。通過使用這個類庫,我們可以在C#語言中實現類似golang和goroutine的通道編程方式。在這裡我們介紹3個簡單的通道的例子。 通過通道發送消息(https://gobyexample.com/channels): stat ...
  • (一)概述 所有需要等待的操作,例如,因為文件、資料庫或網路訪問都需要一定的時間,此時就可以啟動一個新的線程,同時完成其他任務。 線程是程式中獨立的指令流。 (二)Paraller類 Paraller類是對線程的一個很好的抽象,該類位於System.Threading.Tasks名稱空間中,提供了數 ...
  • LZ最近離職,閑著也是閑著,打算梳理下 公司做的是電商,CTO打算把2.0系統用java 語言開發,LZ目前不打算做java,所以 選擇離職。離職前,在公司負責的最後一個項目 供應鏈系統。 系統分為 3套子系統: 1 供應鏈工作平臺(即用戶操作平臺):採用CS架構,Sqlite做緩存。 2 消息中心 ...
  • AngularJs自帶有很多過濾器,現在Insus.NET演示一個自定義的過濾器,如實現一個數據的平方。本演示是在ASP.NET MVC環境中進行。創建一個app: 創建一個控制器: 接下來是重點,創建一個過濾器,例子中的過濾器是實現一個數值的平方。 以上的所指的App,控制器和過濾器均是依Angu ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...