[英]How to organize FluentValidation rules so that they may be reused in multiple validators?
我有一個域模型/實體,根據它的填充方式需要以不同的方式進行驗證。 假設我想出了 3 個驗證器,如下所示:
public class Product1Validator : AbstractValidator<Ticket>
{
public Product1Validator()
{
RuleFor(ticket => ticket.Policy.PolicyNumber)
.NotEmpty()
.WithMessage("Policy Number is missing.");
RuleFor(ticket => ticket.Policy.ApplSignedInState)
.NotEmpty()
.WithMessage("Application Signed In State is missing or invalid.");
}
}
public class Product2Validator : AbstractValidator<Ticket>
{
public Product2Validator()
{
RuleFor(ticket => ticket.Policy.PolicyNumber)
.NotEmpty()
.WithMessage("Policy Number is missing.");
RuleFor(ticket => ticket.Policy.ApplSignedInState)
.NotEmpty()
.WithMessage("Application Signed In State is missing or invalid.");
}
}
public class Product3Validator : AbstractValidator<Ticket>
{
public Product3Validator()
{
RuleFor(ticket => ticket.Policy.PolicyNumber)
.NotEmpty()
.WithMessage("Policy Number is missing.");
RuleFor(ticket => ticket.Policy.ApplSignedInState)
.NotEmpty()
.WithMessage("Application Signed In State is missing or invalid.");
RuleFor(ticket => ticket.Policy.DistributionChannel)
.NotEmpty()
.WithMessage("Distribution Channel is missing.");
}
}
如何重構重復的 RuleFor(s) 以便只有其中一個並由不同的驗證器共享?
謝謝你,斯蒂芬
更新
我運行了 Ouarzy 的想法,但是當我編寫代碼來驗證它時,它不會編譯。
[TestMethod]
public void CanChainRules()
{
var ticket = new Ticket();
ticket.Policy = new Policy();
ticket.Policy.ApplSignedInState = "CA";
ticket.Policy.PolicyNumber = "";
ticket.Policy.DistributionChannel = null;
var val = new Product1Validator();
var result = val.Validate(ticket); //There is no Method 'Validate'
Assert.IsTrue(!result.IsValid);
Console.WriteLine(result.Errors.GetValidationText());
}
更新 2
問題是新的復合驗證器沒有從 AbstractValidator 繼承,一旦我糾正了它,它就會編譯,但它們似乎不起作用。
public class Product1Validator : AbstractValidator<Ticket>
{
public Product1Validator()
{
TicketValidator.Validate().Policy().ApplSignedState();
}
}
更新 3
在對最初的答案深惡痛絕並直接在 GitHub 上聯系 Jeremy 之后,我想出了以下內容:
class Program{
static void Main(string[] args){
var p = new Person();
var pv = new PersonValidator();
var vr = pv.Validate(p);
//Console.ReadKey();
}
}
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
}
class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
CascadeMode = CascadeMode.Continue;
this.FirstName();
this.LastName();
}
}
static class Extensions
{
public static void FirstName(this AbstractValidator<Person> a)
{
a.RuleFor(b => b.FirstName).NotEmpty();
}
public static void LastName(this AbstractValidator<Person> a)
{
a.RuleFor(b => b.LastName).NotEmpty();
}
}
在您的情況下,我可能會嘗試使用所有規則為票證構建流暢的驗證,然后為每個產品調用所需的驗證。 就像是:
public class TicketValidator : AbstractValidator<Ticket>
{
public TicketValidator Policy()
{
RuleFor(ticket => ticket.Policy.PolicyNumber)
.NotEmpty()
.WithMessage("Policy Number is missing.");
return this;
}
public TicketValidator ApplSignedState()
{
RuleFor(ticket => ticket.Policy.ApplSignedInState)
.NotEmpty()
.WithMessage("Application Signed In State is missing or invalid.");
return this;
}
public TicketValidator DistributionChannel()
{
RuleFor(ticket => ticket.Policy.DistributionChannel)
.NotEmpty()
.WithMessage("Distribution Channel is missing.");
return this;
}
public static TicketValidator Validate()
{
return new TicketValidator();
}
}
然后每個產品使用流暢的語法一個驗證器:
public class Product1Validator
{
public Product1Validator()
{
TicketValidator.Validate().Policy().ApplSignedState();
}
}
public class Product2Validator
{
public Product2Validator()
{
TicketValidator.Validate().Policy().ApplSignedState();
}
}
public class Product3Validator
{
public Product3Validator()
{
TicketValidator.Validate().Policy().ApplSignedState().DistributionChannel();
}
}
我沒有嘗試編譯這段代碼,但我希望你能看到這個想法。
希望能幫助到你。
集中擴展方法方法
我想在多種不同類型的對象中使用它們。
我通過創建集中式擴展方法來做到這一點。
一個簡單的例子:
擴展方法
namespace FluentValidation
{
public static class LengthValidator
{
public static IRuleBuilderOptions<T, string>
CustomerIdLength<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.Length<T>(1, 0);
}
}
}
用法
public class CreateCustomerValidator : AbstractValidator<CreateCustomerCommand>
{
public CreateCustomerValidator()
{
RuleFor(x => x.CustomerId).CustomerIdLength();
}
}
當類型對象通過泛型傳遞時,它可以跨多個對象使用,而不僅僅是一個對象,即
public class UpdateCustomerValidator : AbstractValidator<UpdateCustomerCommand>
就我而言,必須驗證不同對象的簡單(內置類型)屬性,因此我需要另一種解決方案來消除重復。
我有一個CreateTodoItemCommand
和一個UpdateTodoItemCommand
,它們都有一個string Title
屬性,應該以相同的方式進行驗證。
我的選擇:
PropertyValidator
因為我沒有(並且我不想創建)僅針對該字段的通用合同(“IHaveTitle”接口)Must
或Custom
驗證器代碼需要我自己編寫我不想要的錯誤消息,因為它是不必要的樣板。我想出的是以下擴展方法:
internal static IRuleBuilderOptions<T, string> ValidateTodoItemTitle<T>(
this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder
.NotEmpty()
.MaximumLength(5000);
}
並像RuleFor(x => x.Title).ValidateTodoItemTitle();
一樣使用它 .
我看到的唯一缺點是我在公共類型上創建了一個擴展方法可能會污染該類型,但這應該不是問題,因為擴展類位於其自己的命名空間中,僅在驗證時使用。
對於使用例如 ReSharper 的人來說,這可能會有所不同,這可能會顯示來自未導入名稱空間的機會,從而造成真正的污染。 出於這個原因,在 C# 中擁有命名空間范圍的可見性會很好。
為使用擴展方法煩惱的人,使用常規方法,用法變成這樣: CommonTodoItemValidators.ValidateTodoItemTitle(RuleFor(x => x.Title));
. 它的可讀性也可以通過例如using static
來提高。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.