Reputation: 9013
I have an abstract (it's no matter) class:
public abstract class CarrierAbstractFormAPI
{
public string Name { get; set; }
public string Fein { get; set; }
public string McNumber { get; set; }
public string DotNumber { get; set; }
public AddressCreateAPI Address { get; set; }
}
and AddressCreateAPI class:
public class AddressCreateAPI
{
public string Street { get; set; }
public string City { get; set; }
public string ZipPostalCode { get; set; }
public int StateProvinceId { get; set; }
public string ContactName { get; set; }
public string ContactPhone { get; set; }
public string ContactFaxNumber { get; set; }
public string ContactEmail { get; set; }
}
My Validator:
public abstract class CarrierAbstractFluentValidation<T> : AbstractValidator<T> where T : CarrierAbstractFormAPI
{
public CarrierAbstractFluentValidation()
{
RuleFor(d => d.Name)
.NotEmpty().WithMessage("Name is required");
RuleFor(d => d.Fein)
.NotEmpty().WithMessage("Fein is required");
RuleFor(d => d.McNumber)
.NotEmpty().WithMessage("McNumber is required");
RuleFor(d => d.DotNumber)
.NotEmpty().WithMessage("DotNumber is required");
RuleFor(d => d.Address.Street)
.NotEmpty().WithMessage("Address Street is required");
RuleFor(d => d.Address.City)
.NotEmpty().WithMessage("Address City is required");
RuleFor(d => d.Address.StateProvinceId)
.InclusiveBetween(0, int.MaxValue).WithMessage("Address State is required");
}
}
It works fine. But I have some additional classes, which can have
public AddressCreateAPI Address { get; set; }
property. I want to move a part:
RuleFor(d => d.Address.Street)
.NotEmpty().WithMessage("Address Street is required");
RuleFor(d => d.Address.City)
.NotEmpty().WithMessage("Address City is required");
RuleFor(d => d.Address.StateProvinceId)
.InclusiveBetween(0, int.MaxValue).WithMessage("Address State is required");
to common class and the apply it in every my fluent validator, which has property Address. How to do it?
Upvotes: 1
Views: 3156
Reputation: 2165
Come to think about it all you need to do is to reuse a validator class.
class AddressCreateAPIValidator : AbstractValidator<AddressCreateAPI>
{
public AddressCreateAPIValidator()
{
RuleFor(d => d.Street)
.NotEmpty().WithMessage("Address Street is required");
RuleFor(d => d.City)
.NotEmpty().WithMessage("Address City is required");
RuleFor(d => d.StateProvinceId)
.InclusiveBetween(0, int.MaxValue).WithMessage("Address State is required");
}
}
class SomeClass
{
public AddressCreateAPI Prop { get; set; }
}
class SomeClassValidator : AbstractValidator<SomeClass>
{
public SomeClassValidator()
{
RuleFor(d => d.Prop).SetValidator(new AddressCreateAPIValidator());
}
}
Notice how AddressCreateAPIValidator
extracts common logic for validating AddressCreateAPI
classes and is then reused for properties by using a call to SetValidator
.
You can mix and match this with reflection-based approach from the other answer if you want to create a generic validator.
Upvotes: 5
Reputation: 2165
You can use the following extension method that inspects the passed type via reflection and applies the specified validation rules:
public static bool Apply<T, TProperty>(this AbstractValidator<T> validator, string propertyName, Action<IRuleBuilderInitial<T, TProperty>> rule)
{
var property = typeof(T).GetProperty(propertyName);
if (property == null)
{
Console.WriteLine($"{typeof(T).Name} does not expose property {propertyName}!");
return false;
}
if (!typeof(TProperty).IsAssignableFrom(property.PropertyType))
{
Console.WriteLine($"Property {typeof(T).Name}.{propertyName} is of type {property.PropertyType.Name} which is not (derived from) {typeof(TProperty).Name}!");
return false;
}
rule(validator.RuleFor(t => (TProperty)property.GetValue(t)));
return true;
}
Example usage:
class a
{
public string Prop { get; set; }
}
class b
{
public DateTime Prop { get; set; }
}
class c
{
public string Prop2 { get; set; }
}
class Validator<T> : AbstractValidator<T>
{
public Validator()
{
this.Apply<T, string>("Prop", r => r.NotEmpty().WithMessage("Prop is required"));
}
}
Console.WriteLine(new Validator<a>().Validate(new a { Prop = "AAA" }));
Console.WriteLine(new Validator<a>().Validate(new a()));
Console.WriteLine(new Validator<b>().Validate(new b { Prop = DateTime.Now }));
Console.WriteLine(new Validator<c>().Validate(new c { Prop2 = "AAA" }));
Console.WriteLine(new Validator<c>().Validate(new c { Prop2 = "AAA" }));
Upvotes: 2