[MVC]Model驗證的介紹,還有如何自訂Model驗證

[MVC]Model驗證的介紹,還有如何自訂Model驗證

前言

最近在做一些關於Model定義的驗證,然後跟vue整個要整合在一起,不過每次都要去google到底有哪些可以用,所以不如自己來記錄一篇,最後因應目前的需求,需要自訂一個比較特殊的驗證。

導覽

  1. 如何在controller做驗證
  2. 官方提供的驗證attribute
  3. 自行訂做一個驗證attribute
  4. 結論

如何在controller做驗證

如何有寫mvc或web api的人應該都會知道,這邊的驗證方式是會寫在要傳輸的物件裡面,假設我現在定義了一個Company的物件,然後再屬性的上方加一個Required的attribute,那就代表了是必填的

public class Company                          
{                                             
    public int Id { get; set; } = 1;          
                                              
    [Required]                                
    public string CompanyName { get; set; }   
}                                             

然後Action的controller寫成如下(如果你想要更省事的話,可以參考筆者之前的文章(https://dotblogs.com.tw/kinanson/2017/05/03/082547))

public class ValuesController : ApiController         
{                                                     
    public IHttpActionResult Post(Company company)    
    {                                                 
        if (ModelState.IsValid)                       
        {                                             
            return Ok();                              
        }                                             
        return BadRequest(ModelState);                
    }                                                 
}                                                     

最後結果如下

你也可以自訂錯誤訊息

[Required(ErrorMessage ="公司名稱必填")]
​

結果

你也可以用類似string.format的方式

[Required(ErrorMessage ="{0}必填")]        
[DisplayName("公司名稱")]                   
public string CompanyName { get; set; }    

結果同上

官方提供的驗證attribute

官方提供了不少的驗證,但是每次筆者想要用到都要去google查一下,所以乾脆自己寫下來,官方預設提供的所有驗證有哪些,比較特別的會說明一下

Attribute的部份

  • Required
  • Range(為數字定義一個範圍)
  • RegularExpression 
  • Compare(可比較另一個屬性值,常用在密碼跟確認密碼的值是否相同)
  • StringLength
  • Data type(裡面提供了很多種格式驗證,例如Email或信用卡)
  • MinLength
  • MaxLength

接下來DataType的部份,在移至Enum的時候就能看到有多少個定義了

接下來則是一些示例,先看一下DataType的部份

    public class Company
    {
        public int Id { get; set; } = 1;

        [Required(ErrorMessage = "{0}必填")]
        [DisplayName("公司名稱")]
        public string CompanyName { get; set; }

        [EmailAddress] //新增了Email的屬性
        public string Email { get; set; }
    }

如果輸入的不是合法的Email的話,結果如下

如果空白的話算合法或不合法呢?答案是要視情況哦,以Email來說如果空白的話是不合法,但以CreditCard的話,卻是合法的哦,所以保險起見如果我確定是要必填的話,最好是自行多加上Required的attribute

[EmailAddress]                        
[Required]                            
public string Email { get; set; }     

如果我們要自訂錯誤訊息的話,就改用如下的方式來寫

[Required]                                            
[EmailAddress(ErrorMessage = "{0}只允許輸入Email格式")]
[DisplayName("Eamil欄位")]                            
public string Email { get; set; }                     

結果

自行訂做一個驗證attribute

最近筆者在驗證的部份遇到了一個情境,也就是我要驗證一個數字必須大於0,但是要是可以編輯的狀況才驗證數字,而且我要帶的名稱並不是這個屬性本身的DisplayName,而是希望帶到對象上面的DisplayName名稱,那接下來就看一下筆者是如何實做這個驗證吧,首先我先定義兩個Model,一個叫ParentModel一個則是ChildModel

    public class ParentModel
    {
        public ChildModel HomePrice { get; set; }
        public ChildModel CarPrice { get; set; }

        public ParentModel()
        {
            HomePrice=new ChildModel { ParentName = "Home Price" };//因為我想要把此名稱顯示在回傳的error message裡面,所以在調用端義ParentName是什麼
            CarPrice = new ChildModel { ParentName = "Car Price" };
        }
    }
public class ChildModel                        
{                                              
    public string ParentName { get; set; } 這個是ParentModel設定的名稱    
                                               
    [Required]                                 
    [MaxPrice(0,"IsDisable", "ParentName")]//自行定義的Attribute    
    public int SetValue { get; set; }          
                                               
    public bool IsDisable { get; set; }        
}                                              

接著則是看一下筆者自行定義的MaxPriceAttribute.cs的部份

    public class MaxPriceAttribute: ValidationAttribute
    {
        private int _price;
        private string _isDisable;
        private string _parentName;
        private string _displayName;
       

        public MaxPriceAttribute(int price,string isDisable,string parentName)//建構整定義了attribute可以丟進來的參數
        {
            _price = price;
            _isDisable = isDisable;
            _parentName = parentName;
        }

        /// <summary>
        /// 使用propertyinfo的方式,來把字串解析出真實的值
        /// </summary>
        /// <param name="validationContext"></param>
        /// <param name="field"></param>
        /// <returns></returns>
        private object GetRealValue(ValidationContext validationContext, string field)
        {
            PropertyInfo isDisableInfo = validationContext.ObjectType.GetProperty(field);
            return isDisableInfo.GetValue(validationContext.ObjectInstance, null);
        }

        private string GetErrorMessage
        {
            get
            {
                return $"{_displayName} Must big then {_price}";
            }
        }


        /// <summary>
        /// 主要邏輯驗證區
        /// </summary>
        /// <param name="value">屬性傳進來的值</param>
        /// <param name="validationContext">用此物件可以反解析字串的值,而取得真實的value</param>
        /// <returns></returns>
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            bool isDisable =(bool) GetRealValue(validationContext,_isDisable);
            _displayName = GetRealValue(validationContext, _parentName).ToString();

            if (!isDisable)
            {
                int inputValue = (int)value;
                if (inputValue < _price)
                {
                    return new ValidationResult(GetErrorMessage);
                }
            }
            return ValidationResult.Success;
        }

        /// <summary>
        /// 覆寫官方的預設方法,只是要告知回傳的預設errormessage
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override string FormatErrorMessage(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                return GetErrorMessage;
            }
            return name;
        }
    }

結果

呃,回傳結果為string,那是因為modelbinding的原因,因為我們把這個屬性名稱public了,就代表了可以寫入,當然聰明一點我們知道把這個屬性拿掉就好了,但是這無形也是違反了里氏替換原則,即然我們原本就不想讓外面調用,那我們就把屬性設為internal的方式,只有同一個命名空間的才能去寫入

public class ChildModel                                
{                                                      
    public string ParentName { get; internal set; }    
                                                       
    [Required]                                         
    [MaxPrice(0,"IsDisable", "ParentName")]            
    public int SetValue { get; set; }                  
                                                       
    public bool IsDisable { get; set; }                
}                                                      

最後結果

欄位是沒有disible狀態,並且值必須要大於0,這邊我故意做成非法的

回傳結果確實是我想要的方式。

結論

這篇算是筆記一下,但其實光Model validation還有關於多國語系還有MVC的部份都沒有介紹,因為筆者開發已經有將近兩年沒有在寫mvc的view了,多數都用一些前端框架,所以以後如果有使用到mvc的部份,再來補充囉,如果對此篇有任何覺得錯誤的地方,再請多多指導哦。