Custom ASP.NET Validation

Trong bài viết này, mình sẽ hướng dẫn các bạn tùy biến Validator trong ASP.NET MVC và cách viết 1 validator ở phía Client side.

Custom ASP.NET Validator

Trong ví dụ này, chúng ta sẽ viết 1 custom validator để kiếm tra giá trị của 1 property này sẽ không bằng giá trị của 1 property khác.
Trong form này, bạn sẽ kiểm tra 2 field UserName và Password. Nếu giá trị Password bằng với UserName, sẽ xuất thông báo lỗi: Password cannot be the same as Username


Bạn tạo 1 class mới tên NotEqualToAttribute

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class NotEqualToAttribute : ValidationAttribute
{
    private const string DefaultErrorMessage = "{0} cannot be the same as {1}.";

    public string OtherProperty { get; private set; }

    public NotEqualToAttribute(string otherProperty)
      : base(DefaultErrorMessage)
    {
        if (string.IsNullOrEmpty(otherProperty))
        {
            throw new ArgumentNullException("otherProperty");
        }

        OtherProperty = otherProperty;
    }

    public override string FormatErrorMessage(string name)
    {
        return string.Format(ErrorMessageString, name, OtherProperty);
    }

    protected override ValidationResult IsValid(object value,
                          ValidationContext validationContext)
    {
        if (value != null)
        {
            var otherProperty = validationContext.ObjectInstance.GetType()
                               .GetProperty(OtherProperty);

            var otherPropertyValue = otherProperty
                          .GetValue(validationContext.ObjectInstance, null);

            if (value.Equals(otherPropertyValue))
            {
                return new ValidationResult(
                  FormatErrorMessage(validationContext.DisplayName));
            }
        }

        return ValidationResult.Success;
    }
}
ValidationAttribute: là 1 abstract class, chứa phương thức virtual là IsValid.
IsValid: chứa 2 tham số đầu vào: value và validationContext. ValidationContext là đối tượng context mà validation cần thực hiện kiểm tra. Kết quả trả về là ValidationResult.Success hoặc đối tượng ValidationResult(errorMessage).
Đoạn code validationContext.ObjectInstance sẽ trả về đối tượng cần validate. Thông qua đối tượng trả về, bạn có thể lấy được value của các property khác. Để lấy được value của property khác, bạn thực hiện đoạn code reflection:
otherProperty.GetValue(validationContext.ObjectInstance, null);
Đoạn code: [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] báo cho .NET biết attribute này chỉ sử dụng cho property, và chỉ tạo được 1 instance trên mỗi property.
Constructor: NotEqualToAttribute(string otherProperty) cho phép bạn khởi tạo giá trị cho other property
Bạn mở website lên, nhập user name và mật khẩu cùng 1 giá trị sẽ thấy thông báo lỗi:
 

Client side Validation

Ví dụ 1:

Để kiểm tra model ở phía browser, bạn cần thêm jquery và jquery validatioin vào file html và đảm bảo 2 key ClientValidationEnabled và UnobtrusiveJavaScriptEnabled luôn được set bằng true.

<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
Trong ví dụ này, chúng ta sẽ làm 1 khung tìm kiếm, với điều kiện là không cho phép nhập dấu sao (*) ở đầu keyword
Dựa vào ví dụ trước đó, bạn sẽ tạo class NoAsteriskAtBegining

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class NoAsteriskAtBegining : ValidationAttribute
{
    private const string DefaultErrorMessage = "Asterisk starting is not allow";

    public NoAsteriskAtBegining() : base(DefaultErrorMessage)
    {

    }

    protected override ValidationResult IsValid(object value,
                          ValidationContext validationContext)
    {
        if (value != null)
        {
            var propValue = value.ToString();
            if(propValue.Length > 0)
            {
                if(propValue.StartsWith("*"))
                    return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
            }
            return ValidationResult.Success;                
        }

        return ValidationResult.Success;
    }
}
ở Controller, bạn thêm đoạn code sau:

public class SearchController : Controller
{
    // GET: Search
    public ActionResult Index()
    {
        return View(new SearchModel());
    }

    [HttpPost]
    public ActionResult Index(SearchModel searchModel, FormCollection form)
    {
        if (ModelState.IsValid)
        {
            //Do something
        }
        return View("Index");
    }
}
Ở View:

@using ModelValidationSamples.Models;
@model SearchModel
<h2>@ViewBag.Title.</h2>
@using (Html.BeginForm("Index", "Search", FormMethod.Post, new { @id = "searchFormId", @class = "form-horizontal", role = "form" }))
{
    @Html.ValidationSummary(true)
<fieldset>
    <div class="form-group">
        @Html.LabelFor(m => m.Keyword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Keyword, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.Keyword, "", new { @class = "text-danger" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input class="btn btn-primary" type="submit" value="Ok" />
            <input class="btn btn-default" type="button" value="Cancel" />
        </div>
    </div>
</fieldset>
}
Chạy chương trình, nhập *, rồi Enter, bạn sẽ thấy kết quả như sau:

Muốn chạy được ở phía client, thì class NoAsteriskAtBegining phải kế thừa interface IClientValidatable và implement method GetClientValidationRules:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class NoAsteriskAtBegining : ValidationAttribute, IClientValidatable
{
 private const string DefaultErrorMessage = "Asterisk starting is not allow";

 public NoAsteriskAtBegining() : base(DefaultErrorMessage)
 {

 }

 public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
 {
  var clientValidationRule = new ModelClientValidationRule()
  {
   ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
   ValidationType = "custom"
  };
  //var OtherProperty = "";
  //clientValidationRule.ValidationParameters.Add("otherproperty", OtherProperty);
  yield return clientValidationRule;
 }

 protected override ValidationResult IsValid(object value, ValidationContext validationContext)
 {
  if (value != null)
  {
   var propValue = value.ToString();
   if(propValue.Length > 0)
   {
    if(propValue.StartsWith("*"))
     return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
   }
   return ValidationResult.Success;                
  }

  return ValidationResult.Success;
 }
}
ValidationType: bạn chỉ ra loại dữ liệu cần kiểm tra. Lưu ý đây phải là tên duy nhất. clientValidationRule.ValidationParameters.Add: Khai bao input parameter để sử dụng trong jquery Validation Để kiểm tra dữ liệu trên trang Html, bạn cần tạo thêm hàm validation và liên kết với metadata bằng jquery adapter. Trong file cshtml:

(function ($) {                        
  $.validator.unobtrusive.adapters.add("custom", function (options) {
   options.rules['custom'] = {};
   options.messages['custom'] = options.message;
  });
  $.validator.addMethod("custom", function (value, element, params) {                
   if (value != undefined && value.length > 0) {
    if (value[0] === '*') {
     return false;
    }
   }
   return true;                
  });
  

 }(jQuery));
Hàm add trong jquery unobtrsive:

adapters.add = function (adapterName, params, fn) {
 /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation.
 /// The name of the adapter to be added. This matches the name used
 /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).
 /// [Optional] An array of parameter names (strings) that will
 /// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and
 /// mmmm is the parameter name).
 /// The function to call, which adapts the values from the HTML
 /// attributes into jQuery Validate rules and/or messages.
 /// 
 if (!fn) {  // Called with no params, just a function
  fn = params;
  params = [];
 }
 this.push({ name: adapterName, params: params, adapt: fn });
 return this;
};
Do hàm kiểm tra * không có parameter, nên bạn cần thêm 1 function Javascript. Để tìm hiểu thêm về function đó, bạn đi tới ví dụ tiếp theo.

Ví dụ 2: Truyền input parameter

Trong ví dụ này, bạn cần truyền parameter từ html vào function trong Javascript
Quay trở lại form Register, bạn sửa lại đoạn code trong file class NotEqualTo.cs

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class NotEqualToAttribute : ValidationAttribute, IClientValidatable
{
 private const string DefaultErrorMessage = "{0} cannot be the same as {1}.";

 public string OtherProperty { get; private set; }

 public NotEqualToAttribute(string otherProperty)
   : base(DefaultErrorMessage)
 {
  if (string.IsNullOrEmpty(otherProperty))
  {
   throw new ArgumentNullException("otherProperty");
  }

  OtherProperty = otherProperty;
 }

 public override string FormatErrorMessage(string name)
 {
  return string.Format(ErrorMessageString, name, OtherProperty);
 }

 protected override ValidationResult IsValid(object value, ValidationContext validationContext)
 {
  if (value != null)
  {
   var otherProperty = validationContext.ObjectInstance.GetType()
          .GetProperty(OtherProperty);

   var otherPropertyValue = otherProperty
        .GetValue(validationContext.ObjectInstance, null);

   if (value.Equals(otherPropertyValue))
   {
    return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));                    
   }
  }

  return ValidationResult.Success;
 }

 public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
 {
  var clientValidationRule = new ModelClientValidationRule()
  {
   ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
   ValidationType = "notequalto"
  };

  clientValidationRule.ValidationParameters.Add("otherproperty", OtherProperty);

  return new[] { clientValidationRule };
 }
}
Bạn mở file Index.cshtml, cập nhật lại đoạn Javascript:

(function ($) {
 $.validator.addMethod("notequalto", function (value, element, params) {
  if (!this.optional(element)) {
   //var otherProp = $('#' + params)
   //return (otherProp.val() != value);
   return $(params).val() != value;
  }
  return true;
 });
 //$.validator.unobtrusive.adapters.addSingleVal("notequalto", "otherproperty");
 $.validator.unobtrusive.adapters.add("notequalto", ["otherproperty"], function (options) {
  options.rules["notequalto"] = "#" + options.params.otherproperty;
  options.messages["notequalto"] = options.message;
 });
}(jQuery));
Giải thích options.rules["notequalto"] = "#" + options.params.otherproperty: Thêm dấu '#' vào trước tên property. Khi bạn gọi lấy giá trị của otherproperty, bạn viết $(params).val() thay vì viết $('#' + params).val().
Download source code: MediaFire

Tham khảo

https://thewayofcode.wordpress.com/2012/01/18/custom-unobtrusive-jquery-validation-with-data-annotations-in-mvc-3/
http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-2
https://www.itorian.com/2013/08/enabling-client-side-validation-on.html
https://thewayofcode.wordpress.com/2012/01/18/custom-unobtrusive-jquery-validation-with-data-annotations-in-mvc-3/

No comments:

Post a Comment