Blazored / FluentValidation

A library for using FluentValidation with Blazor
https://blazored.github.io/FluentValidation/
MIT License
587 stars 84 forks source link

Exception when searching for validators #20

Closed nullpainter closed 4 years ago

nullpainter commented 4 years ago

I haven't been able to replicate this locally, but have identified this exception from our test envrionment. This appears to happen if a validator isn't in DI and Blazored FluentValidation scans for it.

I am assuming that this scan is just identifying an existing issue in my solution, but is it possible to handle these errors more gracefully?

System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types.
Could not load file or assembly 'System.DirectoryServices.Protocols, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.
   at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
   at System.Reflection.RuntimeModule.GetTypes()
   at Blazored.FluentValidation.EditContextFluentValidationExtensions.GetValidatorForModel(IServiceProvider serviceProvider, Object model)
   at Blazored.FluentValidation.EditContextFluentValidationExtensions.ValidateField(EditContext editContext, ValidationMessageStore messages, FieldIdentifier fieldIdentifier, IServiceProvider serviceProvider, IValidator validator)
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__139_0(Object state)
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteSynchronously(TaskCompletionSource`1 completion, SendOrPostCallback d, Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
chrissainty commented 4 years ago

What would you like to see happen in this situation? As you point out, this is identifying an error in your code which would break your app at runtime. Are you looking for a more distinct exception message in this scenario?

nullpainter commented 4 years ago

My application works fine at runtime. For what it's worth, I'm using an external IdentityServer instance for authorisation and the assembly in the stack trace appears relating to LDAP - not something I am directly or indirectly using.

I could always add this reference, but it seems an odd thing to do in order to just fix form validation in Blazor.

nullpainter commented 4 years ago

That being said, I will accept it if your position is that this validator search, in an odd way, is assisting in identifying a potential underlying issue in my code.

chrissainty commented 4 years ago

Sorry, I miss read your code. That's what I get for not finishing my morning coffee before answering issues! 😆

You're correct, even if this is identifying an issue somewhere else, it's not the job of this library to do that. Although, if one of your validators was in the assembly it couldn't find then that would lead to an error at runtime?

nullpainter commented 4 years ago

All good! In my case, the validators are in the same assembly as the Blazor components. Are you concerned that if you swallow exceptions during the assembly scan then it may silently fail to resolve validators?

The interesting thing in my case is that there is no validator to find. I have a parent component containing an EditForm using Fluent Validation. This component has a validator registered in DI. It contains a child component with no validation, and it is this search which is failing. To work around it, I've just registered an empty validator in DI.

chrissainty commented 4 years ago

Are you concerned that if you swallow exceptions during the assembly scan then it may silently fail to resolve validators?

Correct, if an assembly couldn't be found and that had the validator in it, then we wouldn't be able to validate the form and there wouldn't be any clues to anyone as to why.

It contains a child component with no validation, and it is this search which is failing. To work around it, I've just registered an empty validator in DI.

That's interesting. I'll have a look play around and see if there is anything we can tweak to improve this.

nullpainter commented 4 years ago

This may need to be raised as a separate ticket, but there is a related issue when scanning for validators, not installed in DI, which have constructor arguments.

I have a validator installed in DI, which new's up a child validator for one property. This child validator is passed a property from the parent model on construction. Here's the rule in the parent validator:

RuleForEach(i => i.FinalProducts).SetValidator(i => new ProductValidator(i.Nature));

This all works well when submitting the form, however when interacting with a component which is bound to a property in the model for myProductValidator, Blazored FluentValidation attempts to retrieve the validator from DI and fails due to the constructor:

System.InvalidOperationException: Unable to resolve service for type 'System.Nullable`1[xxx.Models.Interactions.InteractionNature]' while attempting to activate 'xxx.
Validators.ProductValidator'.
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
   at Blazored.FluentValidation.EditContextFluentValidationExtensions.GetValidatorForModel(IServiceProvider serviceProvider, Object model)
   at Blazored.FluentValidation.EditContextFluentValidationExtensions.ValidateField(EditContext editContext, ValidationMessageStore messages, FieldIdentifier fieldIdentifier, IServiceProvider servi
ceProvider, IValidator validator)

For the time-being, I've added a second no-arg constructor to ProductValidator as a workaround.

chrissainty commented 4 years ago

The component wasn't ever designed to work with that scenario, it was only designed to work with validators registered via DI.

The only way I can see around this is to allow you to pass in a validator instance to use to validate that particular form. I'm not sure how much work that would be. If you could raise that as a separate feature request then it can be looked into.