FluentValidation 簡介
FluentValidation 是一套驗證套件,可以將驗證以口語化的方式呈現,除了靈活性高,易於客製性化,也可以將驗證邏輯與業務邏輯分離,讓各自程式碼更加簡潔。
拿微軟內建的天氣預報,添加一些變化來當範例,輸入某時間段某地的溫度,如:台灣台北下午1點至3點,27度。
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; }
}
|
假設在新增天氣時,為了資料能正確填寫,需要針對輸入的欄位進行檢查,如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
[HttpPost]
public async Task<IActionResult> CreateWeatherForecast(
[FromBody] CreateWeatherForecastRequest request)
{
if (string.IsNullOrWhiteSpace(request.Nation)) return BadRequest("國家為必填項目");
if (request.Nation.Length > 50) return BadRequest("國家字串不能大於50");
if (request.StartTime > request.EndTime) return BadRequest("開始時間必須在結束時間之前");
// 其他驗證...
await Task.CompletedTask;
return Ok();
}
|
但這樣的驗證,會讓程式碼快速增長,當欄位屬性越來越多時,程式碼很快就變成一長串,妨礙業務邏輯可讀性,所以大部分都會把驗證切開來,我們使用 FluentValidation 改寫看看
安裝 FluentValidation
新增 Validator
依照官方範例,要使用 FluentValidation,首先必須建立一個驗證目標的驗證器 Validator,並繼承 AbstractValidator<T>
,其中 <T>
就是要驗證的類別。
我們要檢查的對象是 CreateWeatherForecastRequest
,所以就建立一個繼承自 AbstractValidator<CreateWeatherForecastRequest>
的驗證器。
1
2
3
4
5
6
7
|
public class CreateWeatherForecastValidator : AbstractValidator<CreateWeatherForecastRequest>
{
public CreateWeatherForecastValidator()
{
// 驗證邏輯...
}
}
|
撰寫驗證
內建驗證語法
FluentValidation 本身提供許多驗證方式,如
- 不可為Null:NotNull
- 不可為Empty:NotEmpty (同時驗證不可為值類型的預設值,例如 int 為 0,陣列等不能為空陣列)。
- 最小字串長度:MinLength
- 最大字串長度:MaxLength
- Email 格式:EmailAddress
- 數值範圍:InclusiveBetween(必須介於兩者之間)、ExclusiveBetween(不能介於兩者之間)
更多內建驗證方法,可以參考官方文件 Built-in Validators 內容。
1
2
3
4
5
6
7
8
9
10
11
|
public class CreateWeatherForecastValidator : AbstractValidator<CreateWeatherForecastRequest>
{
public CreateWeatherForecastValidator()
{
// 內建驗證
RuleFor(x => x.Nation).NotEmpty().Length(1, 50);
RuleFor(x => x.City).NotEmpty().Length(1, 50);
RuleFor(x => x.TemperatureC).InclusiveBetween(-50, 50);
RuleFor(x => x.Summary).MaximumLength(300);
}
}
|
自訂驗證
如果內建的驗證語法無法達到需求,也可以自訂驗證,官方文件 Custom Validators
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class CreateWeatherForecastValidator : AbstractValidator<CreateWeatherForecastRequest>
{
public CreateWeatherForecastValidator()
{
// 內建驗證
RuleFor(x => x.Nation).NotEmpty().Length(1, 50);
RuleFor(x => x.City).NotEmpty().Length(1, 50);
RuleFor(x => x.TemperatureC).InclusiveBetween(-50, 50);
RuleFor(x => x.Summary).MaximumLength(300);
// 自訂驗證
RuleFor(x => x).Must(x => x.StartTime < x.EndTime).WithMessage("開始時間必須在結束時間之前");
//或
//RuleFor(x => x).Custom((value, context) =>
//{
// if (value.StartTime >= value.EndTime)
// {
// context.AddFailure("開始時間必須在結束時間之前");
// }
//});
}
}
|
驗證陣列
RuleFor
,是針對單一屬性,如: int
、string
…,如果是屬性是 List
,則可以使用 RuleForEach
我們將範例稍加改變測試一下,改成輸入各時段的溫度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class CreateWeatherForecastRequest
{
public string Nation { get; set; }
public string City { get; set; }
public string Summary { get; set; }
public List<Temperature> Temperatures { get; set; }
}
public class Temperature
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public int TemperatureC { get; set; }
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class CreateWeatherForecastValidator : AbstractValidator<CreateWeatherForecastRequest>
{
public CreateWeatherForecastValidator()
{
// 內建驗證
RuleFor(x => x.Nation).NotEmpty().Length(1, 50);
RuleFor(x => x.City).NotEmpty().Length(1, 50);
RuleFor(x => x.Summary).MaximumLength(300);
// 自訂驗證
//RuleFor(x => x).Must(x => x.StartTime < x.EndTime).WithMessage("開始時間必須在結束時間之前");
// List 項目驗證
RuleForEach(x => x.Temperatures).ChildRules(t =>
{
t.RuleFor(x => x).Must(t => t.StartTime < t.EndTime).WithMessage("開始時間必須在結束時間之前");
t.RuleFor(x => x.TemperatureC).InclusiveBetween(-50, 50).WithName("溫度");
});
}
}
|
其他使用驗證方式,請查看官方最新文件。
如何使用 Validator
直接使用
建立完驗證器後,下一步便是使用驗證器做驗證,而最簡單的方式就是直接使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
[HttpPost]
public async Task<IActionResult> CreateWeatherForecast([FromBody] CreateWeatherForecastRequest request)
{
// 使用驗證器
var validator = new CreateWeatherForecastValidator();
// 驗證
var validate = await validator.ValidateAsync(request);
// 驗證結果
if (validate.IsValid is false) return BadRequest(validate.ToDictionary());
await Task.CompletedTask;
return Ok();
}
|
使用依賴注入(Dependency Injection,DI)
自 NET Core 版本以來,使用 Dependency Injection 來反轉依賴已經很常見了,而官方也有提供Dependency Injection 說明。
- 自動註冊 Validator
1
2
3
|
using FluentValidation;
builder.Services.AddValidatorsFromAssemblyContaining<CreateWeatherForecastValidator>();
|
- 使用方法注入服務(也可以選擇建構式注入)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
[HttpPost]
public async Task<IActionResult> CreateWeatherForecast(
[FromBody] CreateWeatherForecastRequest request,
[FromServices] IValidator<CreateWeatherForecastRequest> validator)
{
// 驗證
var validate = await validator.ValidateAsync(request);
// 驗證結果
if (validate.IsValid is false) return BadRequest(validate.ToDictionary());
await Task.CompletedTask;
return Ok();
}
|
驗證結果
其他
與資料庫互動
有時候我們驗證是需要與資料庫互動,例如驗證 User 的 Email 是否已存在,這時候我們可以在建構式注入要使用的服務。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class UserValidator : AbstractValidator<User>
{
public UserValidator(IUserRepository userRepository)
{
RuleFor(x => x.Email).Must(email =>
{
return userRepository.IsEmailUnique(email);
});
// 非同步方法
//RuleFor(x => x.Email).MustAsync(async (email, _) =>
//{
// return await userRepository.IsEmailUniqueAsync(email);
//});
}
}
|