Search code examples
c#visual-studio-2022.net-7.0fluentvalidation

Missing Validate extension method in FluentValidation


I am working off the documentation for fluent validation found here.

https://docs.fluentvalidation.net/en/latest/specific-properties.html#

In that documentation, there is an example for a method as such.

_validator.Validate(this, v => v.IncludeProperties(propertyName));

Easy - so far so good, except when I use this in visual studio 2022 I get the message that there is no recognized method for the overload of validate that takes an options action as the second argument. It IS an extension method, so I would expect there to be a namespace someplace in FluentValidation where I can put the right using statement at the top of my code so the compiler can find the extension method, I dug and dug and dug... nothing in the documentation says anything besides just using the FluentValidation namespace.

Still not convinced - I spent some more time poking at the FluentValidation DLL in DotPeek to see if I can find it there, and nothing.

If I cannot use this method, I am going to have to dump fluent validation, I need to be able to have control to say which properties are validated when Validate() is called on my validator but I would really like to keep this around (but this really is the dealbreaker - if I am forced to have to run all the property validation on a validate call, that is not going to work for us.

Here is a screen shot of what I see in VS when I try to use this validate overload on IValidate enter image description here

I tried to find any mention of an extension method in the documentation besides where it is demonstrated. I disassembled the DLL for fluent validation in DotPeek so I could see if it was anywhere in their code. I tried to use ALL available namespaces for Fluent validation at the top of my code file.


Solution

  • I attempted an upgrade, but that didn't resolve the issue. I had some doubts since Intellisense could locate it, indicating it must be present. My challenge lay in pinpointing its location. I needed to identify where this extension was defined. Subsequently, I attempted to call it explicitly from the static, which helped me recognize my mistake.

    The issue stemmed from my attempt to call validate with an instance of this, where this was a generic (base class implementation). The method was designed to be callable from property setters in a child class implementation.

    Here's how my class is defined:

    public abstract class ValidatableClientModelBase<TDto, TValidator, TModel> :
        ClientModelBase<TDto, TModel>,
        IDataErrorInfo
        where TValidator : IValidator<TModel>
        where TModel : ValidatableClientModelBase<TDto, TValidator, TModel>
    

    Consequently, I attempted to pass "this" to the call in fluent fashion:

    public bool ValidateProperty(string propertyName)
    {
        ValidationResult result = _validator.Validate(this, o => 
                                         o.IncludeProperties(propertyName));
        return result.IsValid;
    }
    

    The issue arose because the extension method expected whatever the child class instance (denoted by TModel generic) was. Below is an example of a child class inheriting from the above class:

    public sealed class TicketBed :
        AuditableClientModelBase<Domain.Dto.TicketBed, ITicketBedValidator, TicketBed>,
        ITicketBed
    { ...
    

    In this class, we utilize the previously defined method in a property like so:

    private string _name;
    public string Name
    {
        get => _name;
        set 
        {
            if (_name != value)
            {
                _name = value;
                MarkChanged();
                ValidateProperty(nameof(Name));
                OnPropertyChange(nameof(Name));
            }
        }
    }
    

    The crux was to cast the reference to this to the TModel generic type argument. This is because the generic type argument for IValidator is covariant to our type, as our validator is tailored for that specific class (TicketBed). Therefore, AbstractValidator needs ValidateProperty to have an explicit cast to TModel (in this case, TicketBed) for it to recognize the type being validated by the AbstractValidator. Implicit casting wouldn't suffice in this scenario.

    Returning to the method in my base class where I instruct the validator to validate the property, I needed to alter it as follows, using an explicit cast:

    public bool ValidateProperty(string propertyName)
    {
        ValidationResult result = _validator.Validate((TModel)this, o => 
                                         o.IncludeProperties(propertyName));
        return result.IsValid;
    }
    

    I just had to eat some of my own dogfood the right way and were back in business! Hoisted by my own petard!