1

I am trying to implement a custom validation using "FluentValidation" into a WebApi project.

So, in my action method(POST) from Controller, I use the base class (Person) as parameter:

[Route("persons/compute")] public HttpResponseMessage Compute(Person person)  { ... }

From nuget I have installed "FluentValidation and FluentValidation.WebApi" packages.

I have the follwing code:

[Serializable]   
public class Person
{        
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string CNP { get; set; } //unique identifier
}

 [Serializable]
 [Validator(typeof(StudentValidator))]
 public class Student : Person
 {
    public string CollegeName { get; set; }      
 }

Validator classes are:

 public abstract class PersonValidator<T>: AbstractValidator<T> where T : Person
        {
            protected abstract PersonClassType PersonClassType { get; }
            public PersonValidator()
            {
                Custom(p => {
                    if (string.IsNullOrEmpty(p.CNP))
                    {
                        return new ValidationFailure("Insured.UniqueIdentifier", "CNP/CUI obligatoriu!");
                    }
                    else
                    {
                        decimal cnp;
                        bool isCNP = (p.CNP.Length == 13 && decimal.TryParse(p.CNP, out cnp));
                        if (!isCNP)
                        {
                            return new ValidationFailure("Insured.UniqueIdentifier", "CNP invalid!");
                        }
                    }
                    return null;
                });
            }
        }

The Validator for derived class is:

public class StudentValidator : PersonValidator<Student>
    {
        public StudentValidator ()
        {
            Custom(p => {
                if (string.IsNullOrEmpty(p.CollegeName))
                {
                    return new ValidationFailure("ekfjekfj", "College Name mandatory!");
                }            

                return null;
            });
        }

        protected override PersonClassType PersonClassType
        {
            get
            {
                return PersonClassType.Student;
            }
        }
    }

 [DataContract]
    public enum PersonClassType
    {
        None = int.MinValue,
        Student = 1,
        Employee = 2
    }

In Global.asax.cs, in Application_Start(), I added:

 FluentValidationModelValidatorProvider.Configure(GlobalConfiguration.Configuration);

What I need?: In action method, in controller, using the parameter of type base class(Person) to make the validation for children/derived(ex:Student, Employee, etc) classess which are received from body (POST). So, it need to know to switch to the right validator. I suppose that this issue could be resolved using "Factory" or "DepencyInjection", but I do not know how. I hope make myself clear. My appollogize, but my English it's not perfect.

I would appreciate an explicit solution because I have tried to implement with Factory but I did not managed. I could send the zip file with my code, if it is needed. Thanks a lot in advance!

0

2 Answers 2

3

To handle this, I created the following class:

public class ValidatorBase<TBase> : AbstractValidator<TBase>
{
    public void MapDerivedValidator<TType, TValidatorType>()
        where TValidatorType : IEnumerable<IValidationRule>, IValidator<TType>, new()
        where TType: TBase
    {
        When(t => t.GetType() == typeof(TType), () => AddDerivedRules<TValidatorType>());
    }

    private void AddDerivedRules<T>()
        where T : IEnumerable<IValidationRule>, new()
    {
        IEnumerable<IValidationRule> validator = new T();
        foreach (var rule in validator)
        {
            this.AddRule(rule);
        }
    }
}

To use it, change your Person validator to inherit from ValidatorBase (replace AbstractValidator<Person> with ValidatorBase<Person>).

Now, when you're setting up the rules for Person validation, add the following line:

MapDerivedValidator<Student, StudentValidator>

StudentValidator can simply inherit from AbstractValidator<Student> as it normally would.


What we're doing here is basically conditionally including in the base class's validator (PersonValidator) the rules from the derived class' validator (StudentValidator) when the type being validated matches.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your answer!
Now when I try to implement in real code, I do not understand why the validator does not work. What am I missing !? The sample application works fine and it is the same structure ..
1

To be useful for others, the update code, based on Jon solution, would be:

 [Route("persons/compute")]
        public HttpResponseMessage Compute([FromBody]Person person)
        {...}

In Global.asax.cs :

protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);

            JsonSerializerSettings serializerSettings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
            serializerSettings.TypeNameHandling = TypeNameHandling.All;
            serializerSettings.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
            serializerSettings.FloatParseHandling = FloatParseHandling.Decimal;

            FluentValidationModelValidatorProvider.Configure(GlobalConfiguration.Configuration);

        }

Adding class: ValidatorBase (see above in Jon answer)

Update PersonValidator.cs as follow:

   public PersonValidator()
            {
                MapDerivedValidator<Student, StudentValidator>();
                MapDerivedValidator<Employee, EmployeeValidator>();
                //.... other derived classes
    ...
    }



public class StudentValidator : ValidatorBase<Student>
    { ...}

Thanks Jon!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.