Closed Sebazzz closed 5 years ago
Interesting. What is this? What does that do? Please elaborate the side effect and use cases of doing that.
That method is also apparently is NOT the built-in method of FluentValidation core library, only the integration library: https://github.com/JeremySkinner/FluentValidation/blob/master/src/FluentValidation.AspNetCore/FluentValidationMvcExtensions.cs#L82
In my code, IServiceProvider
is accessible just like any other services because I'm registering the validator into my Dependency Injection.
On Startup.cs
services.AddTransient<IValidator<EditAccountFormModel>, EditAccountFormModelValidator>();
(Example: https://github.com/ryanelian/FluentValidation.Blazor/blob/master/Demo/Startup.cs#L36 registering validator https://github.com/ryanelian/FluentValidation.Blazor/blob/master/Demo/Models/FormModel2.cs#L15-L32 which calls another service as part of the validation logic https://github.com/ryanelian/FluentValidation.Blazor/blob/master/Demo/Services/EmailCheckerService.cs)
^ Look at the debugger values
Interesting. What is this? What does that do?
This allows injecting scope-dependent services in the validator without relying on things like IHttpContextAccessor
. This is especially useful if you have a factory for a child validator (especially for things like string
).
Isn't current method of registering validator to the DI already allowed injecting services to the validator?
My example above doesn't rely on IHttpContextAccessor
but INJECTS IServiceProvider
and MyDbContext
to the validator which then be used to get the IHttpContextAccessor
(read again)
Check again:
(Example: https://github.com/ryanelian/FluentValidation.Blazor/blob/master/Demo/Startup.cs#L36 registering validator https://github.com/ryanelian/FluentValidation.Blazor/blob/master/Demo/Models/FormModel2.cs#L15-L32 which calls another service as part of the validation logic https://github.com/ryanelian/FluentValidation.Blazor/blob/master/Demo/Services/EmailCheckerService.cs)
Please give specific code example for your specific use case because I can't understand what you meant.
There are two IServiceProvider: one global and one which is able to provide per-scope services. Note that a scope in Blazor different as opposed to a scope in regular MVC: Because Blazor is a websockets connection there is a long running scope and no HTTP context.
There are two IServiceProvider:
This is the first time I've ever heard of this. Please give reference to the documentation and code examples?
The services available within an ASP.NET Core request from HttpContext are exposed through the HttpContext.RequestServices collection.
Request Services represent the services configured and requested as part of the app. When the objects specify dependencies, these are satisfied by the types found in RequestServices, not ApplicationServices.
Some instances can only be retrieved through the service provider for the specific scope.
If you look at CircuitFactory.CreateCircuitHost
you see that at line 44 a new serviceprovider is initiated.
Okay...
That doesn't explain what you told me about there being two IServiceProvider
I can't find the code you quoted on the page:
Anyway, what's stopping you from requesting a service from the validator (like my examples above)? Just give me your service code example so I can work with real example.
That doesn't explain what you told me about there being two IServiceProvider
One ServiceProvider is scope based and will resolve scope-based services, while the other will not.
I can't find the code you quoted on the page
That is the Blazor source code, check out the 3.0.0 tag on the AspNetCore repository.
As for the validation, take this rule for instance:
this.RuleFor(e => e.Passphrase).
InjectValidator((sp, context) =>
{
var validatorFactory = sp.GetRequiredService<PassphraseValidatorFactory>();
MyCommand obj = context.InstanceToValidate;
return validatorFactory.MakePassphraseValidator(obj.RetroId, obj.JoiningAsManager, obj.Passphrase);
});
It depends on a PassphraseValidatorFactory
which takes a DbContext
which is request scoped.
As for the validation, take this rule for instance:
Ah, so you're using the CUSTOM MVC validation rule . Not built-in in-the-box rule for FluentValidation.
(https://github.com/JeremySkinner/FluentValidation/blob/master/src/FluentValidation.DependencyInjectionExtensions/DependencyInjectionExtensions.cs#L64 https://github.com/JeremySkinner/FluentValidation/blob/master/src/FluentValidation.DependencyInjectionExtensions/DependencyInjectionExtensions.cs#L84-L109 note that it's in the integration project, not the core FluentValidation project)
Keep in mind, this project is designed to be integrated with the vanilla / core FluentValidation project as a Blazor Component, not as MVC integration library. They are two different beasts.
As for your rule, it's not too hard to rewrite it for my library:
public class SebazzFormValidator : AbstractValidator<SebazzFormModel>
{
private readonly SebazzRequiredService MyService;
public SebazzFormValidator(SebazzRequiredService svc)
{
this.MyService = svc;
RuleFor(e => e.Passphrase).Must(ValidatePassphrase).WithMessage("ErrorWhenValidatingPassPhrase")
}
public bool ValidatePassphrase(value)
{
// var something = MyService.UseMyMethod(value);
// etc. etc.
}
}
As you can see, whatever service / factory you require will be injected through the constructor, as per ASP.NET Core Dependency Injection Best Practice:
You can then register the validator in the DI alongside your required service:
services.AddTransient<IValidator<SebazzFormModel>, SebazzFormValidator>();
services.AddTransient<SebazzRequiredService>();
If you need support for scoped validation, use OwningComponentBase
class provided by the Blazor framework: https://docs.microsoft.com/en-us/aspnet/core/blazor/dependency-injection?view=aspnetcore-3.0#utility-base-component-classes-to-manage-a-di-scope
Is my above example clear enough?
Upon observation, it also might be possible to add support for FluentValidation.DependencyInjectionExtensions
although very hacky-in-nature and prone to breaking. Not sure if want to support this:
(The author explicitly wrote Making use of InjectValidator or GetServiceProvider is only supported when using the automatic MVC integration.
in the class)
Setting Root Context Data _FV_ServiceProvider
value to IServiceProvider
will allow consumers of FluentValidation.Blazor to use InjectValidator
methods.
Disregard that, it exploded when attempted.
[2019-10-07T20:47:33.420Z] Error: System.NullReferenceException: Object reference not set to an instance of an object.
at FluentValidation.DependencyInjectionExtensions.<>c__2`2.<InjectValidator>b__2_0(IServiceProvider s, ValidationContext`1 ctx) in /home/jskinner/code/FluentValidation/src/FluentValidation.DependencyInjectionExtensions/DependencyInjectionExtensions.cs:line 85
at FluentValidation.DependencyInjectionExtensions.<>c__DisplayClass3_0`2.<InjectValidator>b__0(IValidationContext context) in /home/jskinner/code/FluentValidation/src/FluentValidation.DependencyInjectionExtensions/DependencyInjectionExtensions.cs:line 102
at FluentValidation.Validators.ChildValidatorAdaptor.GetValidator(PropertyValidatorContext context) in /home/jskinner/code/FluentValidation/src/FluentValidation/Validators/ChildValidatorAdaptor.cs:line 86
at FluentValidation.Validators.ChildValidatorAdaptor.Validate(PropertyValidatorContext context) in /home/jskinner/code/FluentValidation/src/FluentValidation/Validators/ChildValidatorAdaptor.cs:line 49
at FluentValidation.Internal.PropertyRule.InvokePropertyValidator(ValidationContext context, IPropertyValidator validator, String propertyName) in /home/jskinner/code/FluentValidation/src/FluentValidation/Internal/PropertyRule.cs:line 423
at FluentValidation.Internal.PropertyRule.Validate(ValidationContext context)+MoveNext() in /home/jskinner/code/FluentValidation/src/FluentValidation/Internal/PropertyRule.cs:line 282
at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()
at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
at FluentValidation.AbstractValidator`1.Validate(ValidationContext`1 context) in /home/jskinner/code/FluentValidation/src/FluentValidation/AbstractValidator.cs:line 115
at FluentValidation.AbstractValidator`1.FluentValidation.IValidator.Validate(ValidationContext context) in /home/jskinner/code/FluentValidation/src/FluentValidation/AbstractValidator.cs:line 69
at FluentValidation.FluentValidator.ValidateModel(EditContext editContext, ValidationMessageStore messages) in D:\VS\FluentValidation.Blazor\FluentValidation.Blazor\FluentValidator.cs:line 97
at FluentValidation.FluentValidator.<>c__DisplayClass14_0.<AddValidation>b__0(Object sender, ValidationRequestedEventArgs eventArgs) in D:\VS\FluentValidation.Blazor\FluentValidation.Blazor\FluentValidator.cs:line 79
at Microsoft.AspNetCore.Components.Forms.EditContext.Validate()
at Microsoft.AspNetCore.Components.Forms.EditForm.HandleSubmitAsync()
at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
EDIT 2: Closing this issue, because the goal of this project is to enable Blazor integration against FluentValidation with Dependency Injection / IServiceProvider support; NOT FluentValidation.AspNetCore
or FluentValidation.DependencyInjectionExtensions
integrations.
Added InjectValidator
workaround with SetValidator
plus standard Dependency Injection to README.
It is currently not possible to use any
IServiceProvider
in validators.Apparently it is because the service provider has not been set. This should be easily fixed, like so: https://github.com/JeremySkinner/FluentValidation/blob/48047d3bc558d2c95de5c13800dd5f091b4c0bbb/src/FluentValidation.AspNetCore/FluentValidationModelValidatorProvider.cs#L83