Contents

DataAnnotations 簡介

DataAnnotations 簡介

DataAnnotations 是 NET 內建的驗證器,是相當老牌的驗證器,時至今日,依然有新功能不斷添加近來,使用上也相當簡單。只是自從使用 FluentValidation 後,就漸漸少用,趁著整理 FluentVaildation,順便水一篇文章同時做個紀錄並與 FluentValidation 做對照。

1
2
3
4
5
6
7
8
9
public class CreateWeatherForecastRequest
{
    public string Nation { get; set; }
    public string City { get; set; }
    public int TemperatureC { get; set; }
    public string Summary { get; set; }
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }
}

撰寫驗證

內建驗證語法

使用內建的驗證器,好處即是不用安裝,並且若是撰寫 MVC 專案,則 Razor Page頁面可搭配jquery.validate 顯示錯誤。

撰寫風格是在要驗證的屬性上面,加上 ValidationAttribute,而 NET 也有提供許多內建的 ValidationAttribute,常見如

  • 必填:[Required]
  • 自訂名稱:[Display(Name = “國家”)]
  • 字串長度:[StringLength(50)]
  • 最小字串長度:[MinLength(1)]
  • 最大字串長度:[MaxLength(100)]
  • 數值範圍:[Range (1, 100)]
  • Email 格式:[EmailAddress]

更多內建驗證方法,可以直接參考官方文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class CreateWeatherForecastRequest
{
    [Display(Name = "國家")]
    [Required(ErrorMessage = "{0} 為必填欄位")]
    [StringLength(50, ErrorMessage = "{0} 最多可輸入 {1} 字")]
    public string Nation { get; set; }

    [Display(Name = "城市")]
    [Required(ErrorMessage = "{0} 為必填欄位")]
    [StringLength(50, ErrorMessage = "{0} 最多可輸入 {1} 字")]
    public string City { get; set; }

    public DateTime Date { get; set; }

    [Display(Name = "溫度")]
    [Range(-50, 50, ErrorMessage = "{0} 必須介於 {1} 至 {2}")]
    public int TemperatureC { get; set; }

    public string Summary { get; set; }
}

自訂驗證 IValidatableObject

如果內建的驗證語法無法達到需求,也可以自訂驗證。自訂驗證大致可分為兩種,實作 IValidatableObject和 自訂ValidationAttribute

在要驗證的類別上,實作IValidatableObject,在 Validate 加上驗證邏輯,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class CreateWeatherForecastRequest : IValidatableObject
{
    public string Nation { get; set; }
    public string City { get; set; }
    public int TemperatureC { get; set; }
    public string Summary { get; set; }
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (StartTime > EndTime)
        {
            yield return new ValidationResult("開始時間必須在結束時間之前", new[] { nameof(StartTime) });
        }
    }
}

自訂驗證 ValidationAttribute

另一種自訂驗證的方法,就是實作 ValidationAttribute,然後在驗證物件上面加上自訂的Attribute,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class CreateWeatherForecastRequest
{
    public string Nation { get; set; }
    public string City { get; set; }
    public int TemperatureC { get; set; }
    public string Summary { get; set; }

    [BeforeEndTime]
    public DateTime StartTime { get; set; }

    public DateTime EndTime { get; set; }
}

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class BeforeEndTimeAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var endTime = (DateTime)validationContext.ObjectType.GetProperty("EndTime").GetValue(validationContext.ObjectInstance, null);

        if ((DateTime)value > endTime)
        {
            return new ValidationResult("開始時間必須在結束時間之前");
        }

        return ValidationResult.Success;
    }
}

陣列驗證,同樣使用ValidationAttribute來驗證

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class CreateWeatherForecastRequest
{
    public string Nation { get; set; }
    public string City { get; set; }
    public string Summary { get; set; }

    [TemperatureValidate]
    public List<Temperature> Temperatures { get; set; }
}

public class Temperature
{
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }
    public int TemperatureC { get; set; }
}

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class TemperatureValidateAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var temperatures = value as List<Temperature>;

        foreach (var temperature in temperatures)
        {
            if (temperature.StartTime > temperature.EndTime)
            {
                return new ValidationResult("開始時間必須在結束時間之前");
            }
        }

        return ValidationResult.Success;
    }
}

驗證結果

/static/DataAnnotations簡介_720b30dd61fa4eadb056a059bf2195f8/2023-10-06_14-16-27.png

其他

與資料庫互動

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class EmailIsUniqueValidateAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var email = value as string;

        var context = validationContext.GetService<ApplicationDbContext>();

        var isExists = context.Users.Any(x => x.Email == email);
        // Or
        // var userRepository = validationContext.GetService<IUserRepository>();
        // var isExists = userRepository.IsEmailUnique(email);

        return isExists ? new ValidationResult("Email已被註冊") : ValidationResult.Success;
    }
}

或者使用 [Remote],參考官方範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[AcceptVerbs("GET", "POST")]
public IActionResult VerifyEmail(string email)
{
    if (!_userService.VerifyEmail(email))
    {
        return Json($"Email {email} is already in use.");
    }

    return Json(true);
}
1
2
[Remote(action: "VerifyEmail", controller: "Users")]
public string Email { get; set; }

參考

System.ComponentModel.DataAnnotations 命名空間

https://learn.microsoft.com/zh-tw/dotnet/api/system.componentmodel.dataannotations?view=net-7.0