關於ASP.NET中過濾器、模型繫結的執行個體詳解

來源:互聯網
上載者:User
本篇文章主要介紹了詳解ASP.NET MVC 常用擴充點:過濾器、模型繫結,非常具有實用價值,需要的朋友可以參考下

一、過濾器(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 NLogInstall-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方法裡說明請求結束了,這個時候自然知道使用者的這個操作是否成功了,根據主鍵更新下這條動作記錄的是否成功欄位。

二、模型繫結(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。

【相關推薦】

1. ASP.NET免費視頻教程

2. ASP.NET教程

3. 極客學院ASP,NET視頻教程

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.