Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto Validation does not fire when using SimpleInjector as DI container #37

Open
nhaberl opened this issue Sep 25, 2024 · 8 comments
Open

Comments

@nhaberl
Copy link

nhaberl commented Sep 25, 2024

Hello,

I am workong on asp.net 6 with Simple Injector as DI container which works perfectly but when trying to use AutoValidation nothing is fired.

services.AddFluentValidationAutoValidation(configuration => { configuration.OverrideDefaultResultFactoryWith<CustomResultFactory>(); }); container.Register(typeof(IValidator<>), assemblies, Lifestyle.Singleton);

When trying to validate manually in Controller it works, so I guess the Validator is correct

public class InsertRequestValidator : AbstractValidator<InsertRequestDto> { public InsertRequestValidator() { RuleFor(request => request.Count).InclusiveBetween(1, 1000) .WithMessage("Oida des is afoch zu zvü"); } }

[ApiController] [Route("test")] public class TestController : BaseController

[HttpPost] [Route("command")] public async Task<IActionResult> Command(InsertRequestDto request) { // var validationResult = validator.Validate(request); // if (!validationResult.IsValid) // { // return BadRequest(validationResult.Errors); // } var x = await commandEngine.ExecuteAsync<InsertCommand, int>(new InsertCommand() {Count = request.Count}); return HandleResult(x); }

I have really no idea what I am doing wrong, any help appreciated

@mvdgun
Copy link
Member

mvdgun commented Sep 25, 2024

Hi there,

I’m not familiar with SimpleInjector, as I haven’t used it. Does it integrate well with the default .NET DI provider? One possibility that comes to mind is that the RequestServices property of HttpContext might not be correctly overridden by your SimpleInjector container.

If you try adding an IGlobalValidationInterceptor, can you check whether it's being invoked?

Another potential issue could be that SimpleInjector might not handle resolving open generics the same way the default DI does. This could affect the filter's ability to locate the validator.

public static object? GetValidator(this IServiceProvider serviceProvider, Type type)
{
    return serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(type));
}

@dotnetjunkie
Copy link

Although Simple Injector does integrate with ASP.NET Core, it does so by running Simple Injector side-by-side with the built-in DI Container. This is contrary to how Containers like Autofac function, which tend to replace the buil-in Container. With Simple Injector it's common practice to leave framework and third-party components registered in the built-in Container, while having application components registered inside of Simple Injector.

But this model has consequences and migth be the reason why @nhaberl is running into trouble, because when validators are registered inside Simple Injector, any orchestration by AutoValidation that is registered inside of MS.DI, won't know about the existence of these Simpe Injector-registered validators, unless some special wiring exists.

I'm not familiar with AutoValidation so I can't specifically describe the fix, but @mvdgun, hopefully you can point me at the class or code that ensures that validators are resolved and invoked. That would give me some starting point to look for.

But in more general terms, I think validators can be considered application components as they will contain application-specific business rules and will have dependencies on other application components. It’s best practice to register application components in Simple Injector. But that does require the MS.DI-registered orchestration to either be replaced with something specific that resolves the validators from Simple Injector -or- requires the registration for the orchestration to be made in Simple Injector as well and ensure that the registration for that piece of orchestration inside of MS.DI to simply resolve that component from Simple Injector (cross wiring). E.g. services.AddTransient<IValidationDispatcher>(c => siContainer.GetInstance<IValidationDispatcher>());

One last note for @nhaberl, please make sure that any code examples that you post are well formatted, readable, and preferably syntax highlighted to make it as easy for us as possible to help you.

@mvdgun
Copy link
Member

mvdgun commented Sep 25, 2024

Hi Steven,

Thank you for the detailed explanation!

As for your questions:

Validator registration
This is the responsibility of the user of the library, in my docs I point them to the official FluentValidation docs at: https://docs.fluentvalidation.net/en/latest/di.html.

Validator resolving
Inside the MVC filter I am grabbing the service provider from the actionExecutingContext.HttpContext.RequestServices property.

var serviceProvider = actionExecutingContext.HttpContext.RequestServices;

From the service provider I resolve a validator via the extension method below:

return serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(type));

Validator invoking
Invoking happens at:

var validationResult = await validator.ValidateAsync(validationContext, actionExecutingContext.HttpContext.RequestAborted);

If I need to resolve validators from any source other than a IServiceProvider I'm afraid there is no quick fix, I do like to be surprised though :).

I hope I have answered your question, if not please let me know!

@dotnetjunkie
Copy link

dotnetjunkie commented Sep 25, 2024

Generally speaking, by taking a hard dependency on IServiceProvider you are tightly coupling to the .NET DI infrastructure, making it harder, if not impossible, for non-conformig containers, like Simple Injector, to plug in to your reusable library. It's, therefore, advised to define interception points that decouple the library from the container. MVC, for instance, contains abstractions such as IContollerFactory and IControllerActivator for this purpose.

But before adding such interception point, let's try something different first, because the main abstraction you are pulling from the DI infrastructure is the IValidator<T>. So perhaps we can make a generic dispatcher implementation that forwards the call to a Simple Injector-registered component.

@nhaberl, please add the following code and report back whether that helps:

services.AddTransient(
    typeof(IValidator<T>),
    typeof(SimpleInjectorValidatorDispatcher<>));

class SimpleInjectorValidatorDispatcher<T>(Container container) : AbstractValidator<T>
{
    public override Task<ValidationResult> ValidateAsync(
        ValidationContext<T> context, CancellationToken cancellation = default)
    {
        var validator = container.GetService<IValidator<T>>();

        if (validator is null)
        {
            return Task.FromResult(new ValidationResult());
        }
        else
        {
            return validator.ValidateAsync(context, cancellation);
        }
    }
}

@nhaberl
Copy link
Author

nhaberl commented Sep 26, 2024

@dotnetjunkie thanks a lot, this works!
...although I do not exactly know why here :)

{
  "title": "Validation errors",
  "validationErrors": {
    "Count": [
      "Please provide an appropriate range bewteen 1 and 1000"
    ]
  }
}

@mvdgun
Copy link
Member

mvdgun commented Sep 28, 2024

Hi @nhaberl, so it all works? I assume the validation message is as expected?

@nhaberl
Copy link
Author

nhaberl commented Sep 28, 2024

Thanks, yes the code provided works and it showed the message like expected.
Did not try async validations explicitely but will give Feedback tomorrow..

@dotnetjunkie
Copy link

...although I do not exactly know why here

Let me explain. I previously referred to MVC's IControllerActivator, and I think this is a good analogy to understand what I did in my example.

When a request comes in, MVC creates a controller for that. For the creation of controllers, MVC uses a built-in class that does this: DefaultControllerActivator. But instead of taking a hard dependency on DefaultControllerActivator it hides this implementation behind the IControllerActivator abstraction. This DefaultControllerActivator is registered by MVC in the IServiceCollection and at runtime, MVC requests the registered IControllerActivator from the IServiceProvider. This allows anyone to intercept or replace the creation of controller instances by swapping out the default IControllerActivator from the ISericeCollection with their own one.

Simple Injector users, for instance, use such a custom IControllerActivator implementation (typically without knowing, because it's provided by the Simple Injector integration packages). This custom implementation requests controllers from the Simple Injector container. This custom implementation is registered inside the IServiceCollection. When MVC requests the IControllerActivator from its IServiceProvider it then gets the custom Simple Injector implementation instead of the default one. When MVC calls the resolved IControllerActivator, this custom implementation resolves controller instances from the Simple Injector container, instead of from MS.DI. This allows Simple Injector to come the complete object graph for a controller, which likely depends on other application-specific components, which are also registered inside of Simple Injector.

This is exactly what is happening in the implementation I provided in my previous comment, although this might be a bit harder to see. But because of the lack of an IValidatorFactory, my implementation uses (or, so you will, abuses) the IValidator<T> abstraction for this. This works because the only abstraction that AutoValidation resolves is IValidator<T>.

The only IValidator<T> implementation registered inside the IServiceCollection will be the SimpleInjectorValidatorDispatcher<T>. This means that for any IValidator<T> that AutoValidation resolves from the IServiceProvider, it doesn't get a 'real' validator, it gets a specific/closed SimpleInjectorValidatorDispatcher<T> implementation (even in the absence of a real validator, a SimpleInjectorValidatorDispatcher<T> is returned).

However, when AutoValidation calls that SimpleInjectorValidatorDispatcher<T>, the dispatcher will go to the Simple Injector container to request the real validator, as the validators are registered with Simple Injector. This means that the SimpleInjectorValidatorDispatcher<T> functions as a factory, like the IControllerActivator does. But in contrast to the IControllerActivator implementation, the dispatcher is a generic class. Generic factories aren't unique; SignalR, for instance, has a generic factory abstraction (it's IHubActivator<T>) that can be used to intercept the creation of hub classes.

I hope this makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants