November 22, 2024

FluentValidation with inheritance in ASP.NET Core explained

Hello guys! I’m now switching jobs and I hope I will have more time for writing interesting blog posts, especially for personal development. Usually the personal development ones take more time to write, so I’ll focus on some technical articles for now. In this article, I’m writing about how to use the powerful FluentValidation library in the context of DTO inheritance. I’ve used FluentValidation for several years now, I really like it but I found that you’ll need to perform some workarounds if you want it working with inheritance.

Project setup

For demonstration purposes, I’ll use an ASP.NET Core API template with a controller that accepts 2 POST actions to create (and validate) the Derived1Dto and the Derived2Dto instances inherited from BaseDto class. These two endpoints look like this:

        [HttpPost("derived1")]
        public IActionResult CreateDerived1([FromBody] Derived1Dto derived1)
        {
            if (ModelState.IsValid)
            {
                derived1.Id = Guid.NewGuid();

                return Ok(derived1);
            }

            return BadRequest();
        }

        [HttpPost("derived2")]
        public IActionResult CreateDerived2([FromBody] Derived2Dto derived2)
        {
            if (ModelState.IsValid)
            {
                derived2.Id = Guid.NewGuid();

                return Ok(derived2);
            }

            return BadRequest();
        }

These are nearly identical, we just want to make sure that the validation passed, if so, we generate an id and send back the derived object to the client. The interesting part starts here: since these 2 classes inherit from the same base class, we want to reuse the validation rules from there right? Although the base class has a validator defined, it is not checked in this situation. Why? Because FluentValidation doesn’t check the base type implicitly, we need a way to specify this. So far the DTOs with their validators look like this:

    public class BaseDto
    {
        public int Number { get; set; }

        public Guid Id { get; set; }
    }

    public class BaseDtoValidator : AbstractValidator<BaseDto>
    {
        public BaseDtoValidator()
        {
            RuleFor(_ => _.Number)
                .GreaterThan(18)
                ;

            RuleFor(_ => _.Id)
                .Empty()
                ;
        }
    }

    public sealed class Derived1Dto : BaseDto
    {
        public bool IsNegative { get; set; }
    }

    public class Derived1DtoValidator : AbstractValidator<Derived1Dto>
    {
        public Derived1DtoValidator()
        {
            RuleFor(_ => _.Number)
                .LessThan(0)
                .When(_ => _.IsNegative)
                ;
        }
    }

    public sealed class Derived2Dto : BaseDto
    {
        public float Temperature { get; set; }
    }

    public class Derived2DtoValidator : AbstractValidator<Derived2Dto>
    {
        public Derived2DtoValidator()
        {
            RuleFor(_ => _.Temperature)
                .ExclusiveBetween(10F, 30F)
                ;
        }
    }

Making the base DTO validator work

When I stumbled upon this challenge, the first thing I did was to derive both validators for DTOs from the base validator. And technically this is the most recommended way of doing it. Let’s see how it looks:

    public class BaseDtoAbstractValidator<TDto> : AbstractValidator<TDto>
        where TDto : BaseDto
    {
        public BaseDtoAbstractValidator()
        {
            RuleFor(_ => _.Number)
                .GreaterThan(18)
                ;

            RuleFor(_ => _.Id)
                .Empty()
                ;
        }
    }

    public class Derived1DtoValidator : BaseDtoAbstractValidator<Derived1Dto>
    {
        public Derived1DtoValidator()
        {
            RuleFor(_ => _.Number)
                .LessThan(0)
                .When(_ => _.IsNegative)
                ;
        }
    }

    public class Derived2DtoValidator : BaseDtoAbstractValidator<Derived2Dto>
    {
        public Derived2DtoValidator()
        {
            RuleFor(_ => _.Temperature)
                .ExclusiveBetween(10F, 30F)
                ;
        }
    }

If you try to validate a DTO now, you’ll notice that the validations from the base class also occur. I said previously that this is the recommended way to do it. This is because there is also another way, a bit cunning and also riskier because you may forget to add the validator for each derived class 😁 Basically, you can inject the base validator into your derived validator so that it would look like this:

    public class Derived2DtoValidator : AbstractValidator<Derived2Dto>
    {
        public Derived2DtoValidator(BaseDtoValidator baseValidator)
        {
            RuleFor(o => o)
                .SetValidator(baseValidator)
                ;

            RuleFor(_ => _.Temperature)
                .ExclusiveBetween(10F, 30F)
                ;
        }
    }

That’s a pretty cool trick to do if you need more tuning of when you want to validate the base class as well. I cannot think of anything practical where you would need that, but I find it interesting. Just be a little cautious with that.

A little secret

Behind the scenes, I wanted to make you aware of some limitation of FluentValidation in case of inheritance and usage of a shared validator for some properties. Basically, in one of my projects, I needed to validate a KNX address and I created a property validator for it which was added to DI as singleton. The problem was that FluentValidation didn’t like that the same instance of this validator to be used on multiple DTO types and it crashed pretty ugly. Fortunately, I couldn’t reproduce this issue by using the latest version of FluentValidation 10.1.0 (at the time of this writing). To be able to sort the issue out, I’ve used the second approach and switched the validator as transient. This is pretty cool, I think I’ll upgrade it in my project to test it out.

A little conclusion

Yes I love ❤️ FluentValidation and I recommend to use it in all aspects of validation. You can use it with DI as well, please contact me if you have questions about that. I use it to perform complex validations against data in database with EntityFramework so it’s very powerful. I’ve found a small issue in the context of DTO inheritance, but with the new version it seems sorted out!

Ah and I also share with you some source code for the app that I used to test these things at the time of the writing. You can find it on Github.

Happy and Merry Coding!

Thanks for reading, I hope you found this article useful and interesting. If you have any suggestions don’t hesitate to contact me. If you found my content useful please consider a small donation. Any support is greatly appreciated! Cheers  😉

afivan

Enthusiast adventurer, software developer with a high sense of creativity, discipline and achievement. I like to travel, I like music and outdoor sports. Because I have a broken ligament, I prefer safer activities like running or biking. In a couple of years, my ambition is to become a good technical lead with entrepreneurial mindset. From a personal point of view, I’d like to establish my own family, so I’ll have lots of things to do, there’s never time to get bored 😂

View all posts by afivan →