[MVC]Model驗證的介紹,還有如何自訂Model驗證
前言
最近在做一些關於Model定義的驗證,然後跟vue整個要整合在一起,不過每次都要去google到底有哪些可以用,所以不如自己來記錄一篇,最後因應目前的需求,需要自訂一個比較特殊的驗證。
導覽
如何有寫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; }
結果同上
官方提供了不少的驗證,但是每次筆者想要用到都要去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; }
結果
最近筆者在驗證的部份遇到了一個情境,也就是我要驗證一個數字必須大於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的部份,再來補充囉,如果對此篇有任何覺得錯誤的地方,再請多多指導哦。