Skip to content

Missing IServiceProvider in ObjectGraphDataAnnotationsValidator #57573

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

Open
1 task done
TheBigNeo opened this issue Aug 28, 2024 · 6 comments
Open
1 task done

Missing IServiceProvider in ObjectGraphDataAnnotationsValidator #57573

TheBigNeo opened this issue Aug 28, 2024 · 6 comments
Labels
area-blazor Includes: Blazor, Razor Components
Milestone

Comments

@TheBigNeo
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

In the Validate function of an IValidatableObject model, no services can be loaded with GetRequiredService if Validate was triggered by ObjectGraphDataAnnotationsValidator.

The problem is that in ObjectGraphDataAnnotationsValidator.ValidateObject a new ValidationContext is created but the IServiceProvider is missing.

Source: https://github.com/dotnet/aspnetcore/tree/5da314644ae882ff22fb43101d0c3d89a35c40e9/src/Components/WebAssembly/Validation/src


This would fix the issue.

Class Microsoft.AspNetCore.Components.Forms.ObjectGraphDataAnnotationsValidator:

[Inject] public IServiceProvider ServiceProvider { get; set; }

private void ValidateObject(object value, HashSet<object> visited, List<ValidationResult> validationResults)
{
    var validationContext = new ValidationContext(value);
    validationContext.InitializeServiceProvider(type => ServiceProvider.GetService(type));
    validationContext.Items.Add(ValidationContextValidatorKey, this);
    validationContext.Items.Add(ValidatedObjectsKey,           visited);
    Validator.TryValidateObject(value, validationContext, validationResults, validateAllProperties: true);
}

  • Validate => System.ComponentModel.DataAnnotations.IValidatableObject.Validate
  • IValidatableObject => System.ComponentModel.DataAnnotations.IValidatableObject
  • GetRequiredService => Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T]
  • ObjectGraphDataAnnotationsValidator => Microsoft.AspNetCore.Components.Forms.ObjectGraphDataAnnotationsValidator

Expected Behavior

When calling GetRequiredService[T] in a model with IValidatableObject the service should be returned.

Steps To Reproduce

  • Start sample project
  • Hit Update button on Home page

Project: https://github.com/TheBigNeo/ObjectGraphDataAnnotationsValidator_ServiceProvider

Error Line: https://github.com/TheBigNeo/ObjectGraphDataAnnotationsValidator_ServiceProvider/blob/332b7ac50201309c97a2586a6dd2ef627017e1da/ObjectGraphDataAnnotationsValidator_ServiceProvider/Components/Sample/Animal.cs#L27

Exceptions (if any)

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.InvalidOperationException: No service for type 'ObjectGraphDataAnnotationsValidator_ServiceProvider.Components.Sample.IAnimalService' has been registered.
         at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
         at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
         at ObjectGraphDataAnnotationsValidator_ServiceProvider.Components.Sample.Friend.Validate(ValidationContext validationContext) in C:\Projects\RiderProjects\Sample\ObjectGraphDataAnnotationsValidator_ServiceProvider\ObjectGraphDataAnnotationsValidator_ServiceProvider\Components\Sample\Animal.cs:line 27
         at System.ComponentModel.DataAnnotations.Validator.GetObjectValidationErrors(Object instance, ValidationContext validationContext, Boolean validateAllProperties, Boolean breakOnFirstError)
         at System.ComponentModel.DataAnnotations.Validator.TryValidateObject(Object instance, ValidationContext validationContext, ICollection`1 validationResults, Boolean validateAllProperties)
         at Microsoft.AspNetCore.Components.Forms.ObjectGraphDataAnnotationsValidator.ValidateObject(Object value, HashSet`1 visited, List`1 validationResults)
         at Microsoft.AspNetCore.Components.Forms.ObjectGraphDataAnnotationsValidator.ValidateObject(Object value, HashSet`1 visited)
         at Microsoft.AspNetCore.Components.Forms.ObjectGraphDataAnnotationsValidator.ValidateObject(Object value, HashSet`1 visited)
         at Microsoft.AspNetCore.Components.Forms.ObjectGraphDataAnnotationsValidator.TryValidateRecursive(Object value, ValidationContext validationContext)
         at System.ComponentModel.DataAnnotations.ValidateComplexTypeAttribute.IsValid(Object value, ValidationContext validationContext)
         at System.ComponentModel.DataAnnotations.ValidationAttribute.GetValidationResult(Object value, ValidationContext validationContext)
         at System.ComponentModel.DataAnnotations.Validator.TryValidate(Object value, ValidationContext validationContext, ValidationAttribute attribute, ValidationError& validationError)
         at System.ComponentModel.DataAnnotations.Validator.GetValidationErrors(Object value, ValidationContext validationContext, IEnumerable`1 attributes, Boolean breakOnFirstError)
         at System.ComponentModel.DataAnnotations.Validator.GetObjectPropertyValidationErrors(Object instance, ValidationContext validationContext, Boolean validateAllProperties, Boolean breakOnFirstError)
         at System.ComponentModel.DataAnnotations.Validator.GetObjectValidationErrors(Object instance, ValidationContext validationContext, Boolean validateAllProperties, Boolean breakOnFirstError)
         at System.ComponentModel.DataAnnotations.Validator.TryValidateObject(Object instance, ValidationContext validationContext, ICollection`1 validationResults, Boolean validateAllProperties)
         at Microsoft.AspNetCore.Components.Forms.ObjectGraphDataAnnotationsValidator.ValidateObject(Object value, HashSet`1 visited, List`1 validationResults)
         at Microsoft.AspNetCore.Components.Forms.ObjectGraphDataAnnotationsValidator.ValidateObject(Object value, HashSet`1 visited)
         at Microsoft.AspNetCore.Components.Forms.ObjectGraphDataAnnotationsValidator.<OnInitialized>b__7_0(Object sender, ValidationRequestedEventArgs eventArgs)
         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, ComponentState owningComponentState)
         at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
         at Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.<WaitForNonStreamingPendingTasks>g__Execute|38_0()
         at Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore(HttpContext context)
         at Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore(HttpContext context)
         at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c.<<InvokeAsync>b__10_0>d.MoveNext()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Antiforgery.Internal.AntiforgeryMiddleware.InvokeAwaited(HttpContext context)
         at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

.NET Version

8.0.400

Anything else?

The line to fix this bug comes from here:
https://stackoverflow.com/a/63855575/1847143

dotnet --info

.NET SDK:
 Version:           8.0.400
 Commit:            36fe6dda56
 Workload version:  8.0.400-manifests.56cd0383
 MSBuild version:   17.11.3+0c8610977

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.19045
 OS Platform: Windows
 RID:         win-x64
 Base Path:   C:\Program Files\dotnet\sdk\8.0.400\

.NET workloads installed:
Configured to use loose manifests when installing new manifests.
 [wasm-tools]
   Installation Source: SDK 8.0.400
   Manifest Version:    8.0.8/8.0.100
   Manifest Path:       C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.workload.mono.toolchain.current\8.0.8\WorkloadManifest.json
   Install Type:              Msi


Host:
  Version:      8.0.8
  Architecture: x64
  Commit:       08338fcaa5

.NET SDKs installed:
  8.0.102 [C:\Program Files\dotnet\sdk]
  8.0.300 [C:\Program Files\dotnet\sdk]
  8.0.302 [C:\Program Files\dotnet\sdk]
  8.0.400 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 8.0.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 8.0.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 8.0.8 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
  x86   [C:\Program Files (x86)\dotnet]
    registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
  Not set

global.json file:
  Not found

@ghost ghost added the area-blazor Includes: Blazor, Razor Components label Aug 28, 2024
@javiercn javiercn added this to the Backlog milestone Aug 28, 2024
@S-Luiten
Copy link
Contributor

Just ran into a similar issue with a custom validation attribute: In ValidationAttribute.IsValid(object value, ValidationContext validationContext), the ObjectGraphDataAnnotationsValidator does not pass the serviceProvider to the validationContext, which results in an exception when trying to call validationContext.GetRequiredService<T>(). Changing ObjectGraphDataAnnotationsValidator to DataAnnotationsValidator fixed the issue, but then you lose the ability to validate models recursively.

@TheBigNeo
Copy link
Author

TheBigNeo commented Feb 25, 2025

@S-Luiten
We finally solved this problem with the ApplicationServiceProvider.
This class makes it possible to safely access services even in a static environment, ensuring that they are correctly initialized and disposed of again.

I wrote a post about it here and here is the whole project.

@mrpmorris
Copy link

mrpmorris commented Mar 20, 2025

I just spent hours trying to work out why my ValidationAttribute descendant wasn't working only to find out this is the cause.

@SteveSandersonMS @danroth27 is there any chance of this being fixed in a service release?

Repro

@page "/"
@using System.ComponentModel.DataAnnotations

<PageTitle>Home</PageTitle>

<EditForm Model=@Model>
    <ObjectGraphDataAnnotationsValidator/>
    <InputText @bind-Value=Model.CustomString/>
    <button type="submit">Submit</button>
</EditForm>

@code {
    Person Model = new Person();

    class Person
    {
        [CustomValidation]
        public string? CustomString { get; set; }
    }

    public class CustomValidationAttribute : ValidationAttribute
    {
        protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
        {
            validationContext.GetRequiredService<IServiceProvider>();
            return ValidationResult.Success;
        }
    }
}

@danroth27
Copy link
Member

@danroth27 is there any chance of this being fixed in a service release?

@mrpmorris No, instead our focus is on shipping #46349 as part of .NET 10, which will be our stable and supported story for object graph validation in minimal APIs and Blazor.

@mrpmorris
Copy link

@danroth27 I reverse engineered the dll and all it needed was a few really minor changes.

Is the source available somewhere, can I submit a PR, and would it be released if I did the work? I'd rather not have our own copy to maintain, and surely other people would benefit in the meantime before the V10 release?

@danroth27
Copy link
Member

@mrpmorris We never shipped a stable release of the ObjectGraphDataAnnotationsValidator and we don't have any plans to release a new version. The last preview version we shipped over five years ago and the source code isn't even in the aspnetcore repo anymore. There may be community forks of the code around that you could contribute to. We expect to a have a more integrated validation solution for .NET 10.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

No branches or pull requests

5 participants