简体   繁体   中英

Custom validator to reuse in fluent validation

I would like to implement fluent validation without repetition of validating same properties. I am looking for a way so I can reuse validation.

I have three classes as below, both Customer and NewClass are same except that NewClass inherits PageRequest.

public sealed class Customer {

public int Id{get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
public string MiddleName {get; set;}
public string Address {get; set;}

}

public class PageRequest
{
     public int CurrentPage {get; set;}
     public int PerPage {get; set;}
     public string SortBy {get; set;}
}

public class NewClass : PageRequest
    {
        public int Id{get; set;}
    public string FirstName {get; set;}
    public string LastName {get; set;}
    public string MiddleName {get; set;}
    public string Address {get; set;}
    }

Fluent validations are as below:

public abstract class GetPaginatedDataRequestValidator<TRequest, TModel> : AbstractValidator<TRequest>
        where TRequest : PageRequest
    {
        protected GetPaginatedDataRequestValidator()
        {
            var properties = typeof(TModel).GetProperties().Select(x => x.Name).ToList();
            RuleFor(x => x.CurrentPage).Required().GreaterThan(0);
            RuleFor(x => x.PerPage).Required().GreaterThan(0);
            RuleFor(x => x.SortBy)
               .Must(x => properties.Contains(x, StringComparer.OrdinalIgnoreCase))
               .When(x => !string.IsNullOrEmpty(x.SortBy))
               .WithMessage("{PropertyName} must be a known property name of a " + typeof(TModel).Name.Humanize());
        }
    }


public class NewClassValidator : GetPaginatedDataRequestValidator<
            NewClass, SomeDto>
{
    public NewClassValidator()
        {
            const string message = "At least one of either first name, last name, address and postcode are required.";
           
            RuleFor(x => x.FirstName)
                .Required()
                .When(x => string.IsNullOrEmpty(x.LastName) 
                           && string.IsNullOrEmpty(x.Address) && string.IsNullOrEmpty(x.PostCode))
                .WithMessage(message);

            RuleFor(x => x.LastName)
                .Required()
                .When(x => string.IsNullOrEmpty(x.FirstName) 
                           && string.IsNullOrEmpty(x.Address) && string.IsNullOrEmpty(x.PostCode))
                .WithMessage(message);

            RuleFor(x => x.Address)
                .Required()
                .When(x => string.IsNullOrEmpty(x.LastName) 
                           && string.IsNullOrEmpty(x.FirstName) && string.IsNullOrEmpty(x.PostCode))
                .WithMessage(message);

            RuleFor(x => x.PostCode)
                .Required()
                .When(x => string.IsNullOrEmpty(x.LastName) 
                           && string.IsNullOrEmpty(x.Address) && string.IsNullOrEmpty(x.FirstName))
                .WithMessage(message);
        }
}

public class CustomerValidator : AbstractValidator<Customer>
    {
        public CustomerValidator()
        {
            const string message = "At least one of either first name, last name, address and postcode are required.";
           
            RuleFor(x => x.FirstName)
                .Required()
                .When(x => string.IsNullOrEmpty(x.LastName) 
                           && string.IsNullOrEmpty(x.Address) && string.IsNullOrEmpty(x.PostCode))
                .WithMessage(message);

            RuleFor(x => x.LastName)
                .Required()
                .When(x => string.IsNullOrEmpty(x.FirstName) 
                           && string.IsNullOrEmpty(x.Address) && string.IsNullOrEmpty(x.PostCode))
                .WithMessage(message);

            RuleFor(x => x.Address)
                .Required()
                .When(x => string.IsNullOrEmpty(x.LastName) 
                           && string.IsNullOrEmpty(x.FirstName) && string.IsNullOrEmpty(x.PostCode))
                .WithMessage(message);

            RuleFor(x => x.PostCode)
                .Required()
                .When(x => string.IsNullOrEmpty(x.LastName) 
                           && string.IsNullOrEmpty(x.Address) && string.IsNullOrEmpty(x.FirstName))
                .WithMessage(message);
        }
    }

You could see the same property validations in both customer and newclass validators. Is there anyway I could create a custom validator and reuse in both both Customer and NewClass validators? Is any modification to class structure is required as properties are repeated?

You may be able to unify the Customer and NewClass classes by way of an interface (ICustomer or similar), write a validator for the interface, then include that validator in Customer/NewClass validators.

Edit: MVP LINQPad sample

void Main()
{
    var customerValidator = new CustomerValidator();
    var customer1 = new Customer();
    var customerResult1 = customerValidator.Validate(customer1);
    Console.WriteLine(customerResult1.Errors.Select(x => x.ErrorMessage));

    var newClassValidator = new NewClassValidator();
    var newClass1 = new NewClass { CurrentPage = -1, PerPage = -1, SortBy = "Foo" };
    var newClassResult1 = newClassValidator.Validate(newClass1);
    Console.WriteLine(newClassResult1.Errors.Select(x => x.ErrorMessage));
}

public interface ICustomer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string MiddleName { get; set; }
    public string Address { get; set; }
    public string PostCode { get; set; }
}

public sealed class Customer : ICustomer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string MiddleName { get; set; }
    public string Address { get; set; }
    public string PostCode { get; set; }
}

public class PageRequest
{
    public int CurrentPage { get; set; }
    public int PerPage { get; set; }
    public string SortBy { get; set; }
}

public class NewClass : PageRequest, ICustomer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string MiddleName { get; set; }
    public string Address { get; set; }
    public string PostCode { get; set; }
}

public class SomeDto
{

}

public class ICustomerValidator : AbstractValidator<ICustomer>
{
    public ICustomerValidator()
    {
        const string message = "At least one of either first name, last name, address and postcode are required.";

        RuleFor(x => x.FirstName)
            .NotEmpty()
            .When(x => string.IsNullOrEmpty(x.LastName) && string.IsNullOrEmpty(x.Address) && string.IsNullOrEmpty(x.PostCode))
            .WithMessage(message);

        RuleFor(x => x.LastName)
            .NotEmpty()
            .When(x => string.IsNullOrEmpty(x.FirstName) && string.IsNullOrEmpty(x.Address) && string.IsNullOrEmpty(x.PostCode))
            .WithMessage(message);

        RuleFor(x => x.Address)
            .NotEmpty()
            .When(x => string.IsNullOrEmpty(x.LastName) && string.IsNullOrEmpty(x.FirstName) && string.IsNullOrEmpty(x.PostCode))
            .WithMessage(message);

        RuleFor(x => x.PostCode)
            .NotEmpty()
            .When(x => string.IsNullOrEmpty(x.LastName) && string.IsNullOrEmpty(x.Address) && string.IsNullOrEmpty(x.FirstName))
            .WithMessage(message);
    }
}

public class CustomerValidator : AbstractValidator<Customer>
{
    public CustomerValidator()
    {
        Include(new ICustomerValidator());
    }
}

public abstract class GetPaginatedDataRequestValidator<TRequest, TModel> : AbstractValidator<TRequest> where TRequest : PageRequest
{
    protected GetPaginatedDataRequestValidator()
    {
        var properties = typeof(TModel).GetProperties().Select(x => x.Name).ToList();
        RuleFor(x => x.CurrentPage).GreaterThan(0);
        RuleFor(x => x.PerPage).GreaterThan(0);
        RuleFor(x => x.SortBy)
           .Must(x => properties.Contains(x, StringComparer.OrdinalIgnoreCase))
           .When(x => !string.IsNullOrEmpty(x.SortBy))
           //.WithMessage("{PropertyName} must be a known property name of a " + typeof(TModel).Name.Humanize());
           .WithMessage("{PropertyName} must be a known property name of a " + typeof(TModel).Name);
    }
}

public class NewClassValidator : GetPaginatedDataRequestValidator<NewClass, SomeDto>
{
    public NewClassValidator()
    {
        Include(new ICustomerValidator());
    }
}

Result:

在此处输入图像描述

This is using your definitions; I've added PostCode as the validators referenced it and changed the Required validators (removed if the properties cannot be null (eg, int) or, for strings, replaced with NotEmpty as Required isn't a built-in validator).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM