設計自訂驗證 – MVC5

by vivid 28. 一月 2015 11:37

.NET Magazine國際中文電子雜誌
者:許薰尹
稿:張智凱
文章編號:N150115602
出刊日期:2015/01/28
開發工具:Visual Studio 2013 Ultimate Update 3
版本:.NET Framework 4.5.xASP.NET MVC5

在設計MVC網站應用程式中我們常常需要搜集使用者輸入的資料,也需要時時檢查使用者輸入的資料是否正確。預設MVC可以使用Data Annotations來驗證模型資料是否如預期,若預設的功能不符合需求,您也可以自行設計驗證機制。本篇文章將介紹如何在MVC類型的網站應用程式之中設計自訂驗證的機制,利用伺服端與用戶端的驗證來確保資料的正確性。

預設MVC採用Data Annotation Attribute來啟用資料驗證的功能,例如以下的模型類別,Id屬性上方套用Required Attribute代表此屬性值是必要的,一定要有值,不可以不輸入:

 

public class Employee {
  [Required]
  [DisplayName( "員工編號" )]
  public int Id { get; set; }
  [DisplayName( "員工名稱" )]
  public string Name { get; set; }
  public string MobilePhone { get; set; }
}

MVC在進行模型繫結的過程中,會自動套用attribute來檢查資料的有效性。同時MVC的網站應用程式中預設便啟用用戶端驗證功能,預設web.config檔案中會有如下的appSettings,啟用用戶端驗證,同時採用Unobtrusive JavaScript設計慣例來進行驗證動作:

 

<appSettings>
  <add key="webpages:Version" value="3.0.0.0" />
  <add key="webpages:Enabled" value="false" />
  <add key="ClientValidationEnabled" value="true" />
  <add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>

因此若要設計自定驗證,你需要同時撰寫伺服端驗證程式碼,與用戶端驗證程式碼,同時為了要讓用戶端驗證能正確執行,你需要使用Nuget下載jQuery、jQuery.Validation與Microsoft.jQuery.Unobtrusive.Validation等套件。

 

設計伺服端驗證程式碼

若要自訂伺服端的驗證功能,在MVC之中,你可以撰寫一個自定驗證類別,繼承System.ComponentModel.DataAnnotations命名空間下的ValidationAttribute類別,並改寫其中一個IsValid方法,自行設計驗證程式碼就行了,例如底下的程式碼定義MobilePhoneAttribute 類別,以及兩個IsValid方法的函式簽名:

public class MobilePhoneAttribute : ValidationAttribute  {

  public override bool IsValid( object value ) {
    //自訂驗證邏輯
  }

  protected override ValidationResult IsValid( object value , ValidationContext validationContext ) {
    //自訂驗證邏輯
  }
}


 

接下來的範例中,我們將改寫帶兩個參數的IsValid方法,來自定驗證邏輯。

套用ValidationAttribute

若設計完自定驗證的ValidationAttribute  類別,就可以在模型之中,直接套用MobilePhoneAttribute,我們希望檢查模型中的行動電話號碼必需以「09」字串開始:

public class Employee {
  [Required]
  [DisplayName( "員工編號" )]
  public int Id { get; set; }
  [DisplayName( "員工名稱" )]
  public string Name { get; set; }
  [MobilePhone( "09" )]
  [DisplayName( "行動電話" )]
  public string MobilePhone { get; set; }
}

 

設計ValidationAttribute類別

由於我們希望檢查行動電話號碼是以「09」開始,參考以下程式碼,定義MobilePhoneAttribute 類別來客製化檢查的動作。ValidationAttribute 類別是一個抽象類別(abstract class):

public class MobilePhoneAttribute : ValidationAttribute {
  public MobilePhoneAttribute( string prefix )
    : base( "{0}格式錯誤" ) {
    Prefix = prefix;

  }
  public string Prefix { get; set; }

  protected override ValidationResult IsValid( object value , ValidationContext validationContext ) {

    if ( value != null ) {
      if ( value.ToString( ).Substring( 0 , 2 ) != Prefix ) {
        return new ValidationResult( FormatErrorMessage( validationContext.DisplayName ) );
      }

    }
    return ValidationResult.Success;
  }
}

 

Prefix 屬性用來存放預期的行動電話號碼-字串「09」,為了擴充性,我們不把程式寫死,而是在模型屬性套用MobilePhoneAttribute 時利用建構函式傳入。

範例中改寫含兩個引數的IsValid方法,第一個引數value變數將存放使用者輸入將用來驗證的資料;第二個ValidationContext 型別的引數將提供更多關於驗證相關的資訊,例如模型的實體、要檢查的屬性之DisplayName…等等。

若使用者輸入的資料是有效的,則回傳ValidationResult.Success列舉常值;若使用者輸入的資料是無效的,則回傳ValidationResult物件,並利用FormatErrorMessage方法,設定預設的錯誤訊息,從validationContext.DisplayName取得名稱之後,代入「{0}格式錯誤」字串中。validationContext.DisplayName的值將會代入{0}所在的位置。

未來使用者輸入資料時,若資料有誤,便會顯示錯誤訊息:

clip_image002

圖 1:驗證行動電話號碼以「09」開始。

控制器用來修改資料的Action Method參考如下:

public class EmployeesController : Controller {
  private MyDBContext db = new MyDBContext( );

  public ActionResult Edit( int? id ) {
    if ( id == null ) {
      return new HttpStatusCodeResult( HttpStatusCode.BadRequest );
    }
    Employee employee = db.Employees.Find( id );
    if ( employee == null ) {
      return HttpNotFound( );
    }
    return View( employee );
  }
  //程式碼省略
}

 

Edit 檢視程式參考如下:

@model CustValidate.Models.Employee
@{
  Layout = null;
}
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Edit</title>
</head>
<body>
  @using ( Html.BeginForm( ) ) {
    @Html.AntiForgeryToken( )
 
    <div class="form-horizontal">
      <h4>Employee</h4>
      <hr />
      @Html.ValidationSummary( true , "" , new { @class = "text-danger" } )
      @Html.HiddenFor( model => model.Id )
 
      <div class="form-group">
        @Html.LabelFor( model => model.Name , htmlAttributes: new { @class = "control-label col-md-2" } )
        <div class="col-md-10">
          @Html.EditorFor( model => model.Name , new { htmlAttributes = new { @class = "form-control" } } )
          @Html.ValidationMessageFor( model => model.Name , "" , new { @class = "text-danger" } )
        </div>
      </div>
 
      <div class="form-group">
        @Html.LabelFor( model => model.MobilePhone , htmlAttributes: new { @class = "control-label col-md-2" } )
        <div class="col-md-10">
          @Html.EditorFor( model => model.MobilePhone , new { htmlAttributes = new { @class = "form-control" } } )
          @Html.ValidationMessageFor( model => model.MobilePhone , "" , new { @class = "text-danger" } )
        </div>
      </div>
 
      <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
          <input type="submit" value="Save" class="btn btn-default" />
        </div>
      </div>
    </div>
  }
 
  <div>
    @Html.ActionLink( "Back to List" , "Index" )
  </div>
</body>
</html>

 

設計用戶端驗證

若要支援用戶端驗證,你需要讓你的ValidationAttribute 類別額外實作IClientValidatable 介面。IClientValidatable 介面包含一個GetClientValidationRules方法以取得用戶端的中繼資料(Metadata),MobilePhoneAttribute 類別實作IClientValidatable 介面的程式如下:

public class MobilePhoneAttribute : ValidationAttribute , IClientValidatable {
  public MobilePhoneAttribute( string prefix )
    : base( "{0}格式錯誤" ) {
      Prefix = prefix;
  }
  public string Prefix { get; set; }

  protected override ValidationResult IsValid( object value , ValidationContext validationContext ) {

    if ( value != null ) {
      if ( value.ToString( ).Substring(0,2) != Prefix ) {
        return new ValidationResult( FormatErrorMessage( validationContext.DisplayName ) );
      }
    }
    return ValidationResult.Success;
  }
  public IEnumerable<ModelClientValidationRule> GetClientValidationRules( ModelMetadata metadata , ControllerContext context ) {
    var rule = new ModelClientValidationRule( );
    rule.ErrorMessage = FormatErrorMessage( metadata.GetDisplayName( ) );
    rule.ValidationParameters.Add( "prefix" , Prefix ); //要英文的小寫,不可以含大寫字元
    rule.ValidationType = "phoneprefix";//要英文的小寫,不可以含大寫字元
    yield return rule;

  }
}


在GetClientValidationRules 方法中,我們回傳一個ModelClientValidationRule物件,設定ErrorMessage 屬性來顯示錯誤訊息。ValidationParameters屬性包含用戶端所需的參數資訊,在此範例中即為電話號碼前置字串(在模型中設定為09)。ValidationType 指明用戶端用來驗證的JavaScript程式碼。注意,在定義ValidationParameters與ValidationType 時,名稱都必需是英文的小寫字元,不可以使用大寫字元。

clip_image004

圖 2:使用用戶端驗證。

MVC是根據GetClientValidationRules方法中的程式碼,將ModelClientValidationRule的資訊序列化成以下的data-attribute:

<input class="form-control text-box single-line input-validation-error" 
  data-val="true" 
  data-val-phoneprefix="行動電話格式錯誤" 
  data-val-phoneprefix-prefix="09" 
  id="MobilePhone" 
  name="MobilePhone" 
  type="text" 
  value="0912345678">

其中data-val-phoneprefix後的phoneprefix是根據ValidationType而來;data-val-phoneprefix-prefix後的prefix是根據ValidationParameters的設定而來。

 

用戶端JavaScript

你不需要從無到有將用戶端HTML標籤中的data attribute逐一讀出,你可以利用微軟設計的Unobtrusive validation套件中定義的adapters來負責這項工作,不過你還是需要寫一點程式碼來進行用戶端的驗證作業。在用戶端的程式中,你需要先引用jquery、jquery.validate與jquery.validate.unobtrusive套件,再撰寫自訂的用戶端驗證程式碼,參可以下程式:

<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script>
  $.validator.unobtrusive.adapters.addSingleVal( "phoneprefix", "prefix" );
  $.validator.addMethod( "phoneprefix", function ( value, element, validPrefix ) {
    console.log( value.substr( 0, 2 ) ); //使用者輸入資料
    console.log( validPrefix ); //預期的資料
    if ( value.substr( 0, 2 ) != validPrefix ) {
      return false;
    }
    return true;
  } );
</script>

 

addSingleVal方法會根據驗證規則(Validation Rule)來建立一個adapter,並從中繼資料中取出一個參數值。addSingleVal方法第一個參數是adapter的名稱「phoneprefix」,它必需和GetClientValidationRules方法中ModelClientValidationRule 物件的ValidationType 屬性值一致。addSingleVal方法第二個參數是data attribute的名字「prefix」,它必需和ValidationParameters的參數名稱相符,以便從中繼資料中讀取驗證用的參數資訊「09」。

接著,你可以撰寫validator實作用戶端驗證邏輯,叫用validator.addMethod方法,傳入參數。validator.addMethod方法第一個參數是validator的名稱,根據慣例名稱要與adapter的名稱一致(與伺服端的ValidationType屬性值一致);validator.addMethod方法第二個參數是一個回呼函式(Callback),在用戶端驗證事件發生時自動叫用,以進行驗證動作。

validator.addMethod方法回呼函式(Callback)包含三個參數,第一個參數是value,包含使用者輸入來要進行驗證的資料。第二個參數element表示包含驗證資料的HTML項目;第三個參數則是驗證參數,包含預期的資料。

範例中檢查使用者輸入的資料前兩個字是否為預期的「09」,若否則回傳false代表驗證失敗,若是,則回傳true代表驗證成功。

用戶端Edit 檢視完整程式碼如下:

@model CustValidate.Models.Employee
@{
  Layout = null;
}
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Edit</title>
</head>
<body>
  @using ( Html.BeginForm( ) ) {
    @Html.AntiForgeryToken( )
 
    <div class="form-horizontal">
      <h4>Employee</h4>
      <hr />
      @Html.ValidationSummary( true , "" , new { @class = "text-danger" } )
      @Html.HiddenFor( model => model.Id )
 
      <div class="form-group">
        @Html.LabelFor( model => model.Name , htmlAttributes: new { @class = "control-label col-md-2" } )
        <div class="col-md-10">
          @Html.EditorFor( model => model.Name , new { htmlAttributes = new { @class = "form-control" } } )
          @Html.ValidationMessageFor( model => model.Name , "" , new { @class = "text-danger" } )
        </div>
      </div>
 
      <div class="form-group">
        @Html.LabelFor( model => model.MobilePhone , htmlAttributes: new { @class = "control-label col-md-2" } )
        <div class="col-md-10">
          @Html.EditorFor( model => model.MobilePhone , new { htmlAttributes = new { @class = "form-control" } } )
          @Html.ValidationMessageFor( model => model.MobilePhone , "" , new { @class = "text-danger" } )
        </div>
      </div>
 
      <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
          <input type="submit" value="Save" class="btn btn-default" />
        </div>
      </div>
    </div>
  }
 
  <div>
    @Html.ActionLink( "Back to List" , "Index" )
  </div>
 
  <script src="~/Scripts/jquery-1.10.2.js"></script>
  <script src="~/Scripts/jquery.validate.js"></script>
  <script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
  <script>
 
    console.log( $.validator.unobtrusive.adapters );
    $.validator.unobtrusive.adapters.addSingleVal( "phoneprefix", "prefix" );
    $.validator.addMethod( "phoneprefix", function ( value, element, validPrefix ) {
       console.log( value.substr( 0, 2 ) ); //使用者輸入資料
      console.log( validPrefix ); //預期的資料
      if ( value.substr( 0, 2 ) != validPrefix ) {
        return false;
      }
      return true;
    } );
  </script> 
</body>
</html>

Tags:

.NET Magazine國際中文電子雜誌 | ASP.NET MVC | JavaScript | jQuery | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List