0

I'm using Fluent validation library to validate incoming requests from webApi with the following model structure to validate:

public class BaseClass
{
    public MyEnum MyProperty { get; set; }
}

public class DerivedClass : BaseClass
{
    public new string MyProperty { get; set; }
}

Please note the new keyword for DerivedClass.MyProperty property.
Here is my fluent validation implementation:

public class BaseClassValidator<T> : AbstractValidator<T> where T : BaseClass
{
    public BaseClassValidator()
    {
        RuleFor(prop => prop.MyProperty).IsInEnum();
    }
}

public class DerivedClassValidator<T> : BaseClassValidator<DerivedClass>
{
    public DerivedClassValidator()
    {
        RuleFor(prop => prop.MyProperty).NotEmpty();
    }
}

Incomming model to be validate is of type DerivedClass.
I have several other properties though, i cut them off for more clarification.
The problem is, the FluentValidator calls the validations for both BaseClass and DerivedClass, while desired behavior is to validate only DerivedClass's properties when a property is overridden with new keyword and leave the BaseClass's property with the same name unchecked.
The question is: Is there any built-in functionality support this scenario "to suppress validation of BaseClass's properties which are overridden in the DerivedClass, instead use corresponding DerivedClass's validation for those properties"?

Rzassar
  • 2,117
  • 1
  • 33
  • 55
  • Are you sure you need inheritance here? whenever you feel you need to use the `new` keyword, it's a good indication you're misusing inheritance - either by inheriting from the wrong base class or by using inheritance instead of composition. Of course, that's a rule of thumb - there are exceptions where using the `new` keyword to hide inherited members is the most sensible thing to do. – Zohar Peled Sep 07 '20 at 14:23
  • I am not sure if your second code snippet makes sense. Did you just put code that belongs into a method or property directly into a class? – Battle Sep 07 '20 at 14:37
  • @Battle Sure, i fixed it. – Rzassar Sep 08 '20 at 04:57

2 Answers2

1

The bottom code snippet is still a little weird. The "RuleFor" comes out of nowhere and does not show what input it is based on.

Normally you'd use inheritance to override a property's getter and setter, so that no matter if you grab its base class or derived class, both have that property set by the derived class. The new key serves to override the access to a given member on that particular level, meaning you can't directly access the hidden member any more if you are dealing with a derived class. But you could simply cast it to its base class and do so anyway.

What you also did is to put the validation into constructors. The derived classes must call an existing constructor of their base class (and if none are defined, it's a public, parameterless, empty constructor). Meaning whenever you create an instance of a derived class, you first trigger the base class's constructor and THEN the derived class' constructor.

This is how it usually looks like:

public class MyClass : BaseClass
{
    public MyClass (string p1, int p2, bool p3) : base (p1, p2)
    {
        P3 = p3;
    }
}

I do not think you can avoid triggering a base class' constructor if you have no access to the code (if it's within a foreign library). Otherwise you can add a protected, parameterless constructor, in which case you can either write ) : base () or leave it out.

If you have access to the code, you should let the constructor trigger a virtual or abstract method in the base class only, and then simply override the method.

Battle
  • 786
  • 10
  • 17
0

Finally, i ended up with the following solution:

    public class BaseValidator<T> : AbstractValidator<T>
    {
        #region Constructor

        public BaseValidator(params string[] suppressPropertyValidations)
        {
            var type = typeof(T);

            if (!hiddenPropertiesDictionary.ContainsKey(type))
            {
                lock (type)
                {
                    if (!hiddenPropertiesDictionary.ContainsKey(type))
                    {

                        var notMappedProperties = new List<string>();

                        var hiddenProperties = from property in type.GetProperties()
                                               where property.IsHidingMember()
                                               select property.Name;
                        notMappedProperties.AddRange(hiddenProperties);

                        hiddenPropertiesDictionary.Add(type, notMappedProperties);
                    }
                }
            }

            this.suppressPropertyValidations = new List<string>(suppressPropertyValidations);
        }

        #endregion

        #region Fields & Properties

        private static Dictionary<Type, List<string>> hiddenPropertiesDictionary = new Dictionary<Type, List<string>>();

        private readonly List<string> suppressPropertyValidations;

        #endregion

        #region Fields & Properties

        private IEnumerable<string> GetSuppressedPropertyValidation()
        {
            var type = typeof(T);
            var hiddenProperties =
                hiddenPropertiesDictionary.ContainsKey(type) ?
                hiddenPropertiesDictionary[type] :
                null;

            return suppressPropertyValidations.Union(hiddenProperties);
        }

        public override ValidationResult Validate(ValidationContext<T> context)
        {
            var result = base.Validate(context);
            var errors =
                from suppressProperty in GetSuppressedPropertyValidation()
                join error in result.Errors on suppressProperty equals error.PropertyName
                select error;

            foreach (var error in errors)
                result.Errors.Remove(error);

            return result;
        }

        #endregion
    }

I used the the extension method IsHidingMember() to check hidden properties (from this link).
Now change the BaseClass as follows:

public class BaseClassValidator<T> : BaseValidator<T> where T : BaseClass
{
    public BaseClassValidator(params string[] suppressPropertyValidations)
        : base(suppressPropertyValidations)
    {
        //Some codes and rules here
    }
}

And finally we have the DerivedClass:

public class DerivedClassValidator : BaseClassValidator<DerivedClass>
{
    public DerivedClassValidator() 
        : base("foo","bar") 
        //some arbitrary property names (other than the hidden ones e.g. MyProperty) that you want to be suppressed in the baseclass goes here.
    {
        //Some codes and Rules here.
    }
}
Rzassar
  • 2,117
  • 1
  • 33
  • 55