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

SystemTextJsonValidationMetadataProvider passes null values to the JsonNamingPolicy #47835

Closed
patrik-sabo opened this issue Apr 14, 2023 · 4 comments · Fixed by dotnet/runtime#85002 or #47775
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates

Comments

@patrik-sabo
Copy link

Description

Compared to JsonCamelCaseNamingPolicy which has a check for IsNullOrEmpty on the name, new newly added support for snake_case and kebab-case doesn't have this.
Tried to use the new snake_case naming policy with SystemTextJsonValidationMetadataProvider which crashes when you pass in an array.

Reproduction Steps

Send in an array of strings to the endpoint and it'll crash.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services
    .AddControllers(options => options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider(System.Text.Json.JsonNamingPolicy.SnakeCaseLower)))
// It works as expected if using CamelCase
//    .AddControllers(options => options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider(System.Text.Json.JsonNamingPolicy.CamelCase)))
    .AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseLower);
var app = builder.Build();
app.MapControllers();
app.Run();

[ApiController]
[Route("")]
public class TestController : ControllerBase
{
    [HttpPost]
    public async Task<IActionResult> Test([FromBody] string[] test)
    {
        return Ok(test);
    }
}

Expected behavior

Return the passed in string array

Actual behavior

System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Text.Json.JsonSeparatorNamingPolicy.ConvertName(String name)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.SystemTextJsonValidationMetadataProvider.CreateValidationMetadata(ValidationMetadataProviderContext context)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultCompositeMetadataDetailsProvider.CreateValidationMetadata(ValidationMetadataProviderContext context)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata.get_ValidationMetadata()
   at Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata.get_PropertyValidationFilter()
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitImplementation(ModelMetadata& metadata, String& key, Object model)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ObjectModelValidator.Validate(ActionContext actionContext, ValidationStateDictionary validationState, String prefix, Object model, ModelMetadata metadata, Object container)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.EnforceBindRequiredAndValidate(ObjectModelValidator baseObjectValidator, ActionContext actionContext, ParameterDescriptor parameter, ModelMetadata metadata, ModelBindingContext modelBindingContext, ModelBindingResult modelBindingResult, Object container)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value, Object container)
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Apr 14, 2023
@ghost ghost added the untriaged label Apr 14, 2023
@vcsjones vcsjones removed the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Apr 14, 2023
@ghost
Copy link

ghost commented Apr 14, 2023

Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

Compared to JsonCamelCaseNamingPolicy which has a check for IsNullOrEmpty on the name, new newly added support for snake_case and kebab-case doesn't have this.
Tried to use the new snake_case naming policy with SystemTextJsonValidationMetadataProvider which crashes when you pass in an array.

Reproduction Steps

Send in an array of strings to the endpoint and it'll crash.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services
    .AddControllers(options => options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider(System.Text.Json.JsonNamingPolicy.SnakeCaseLower)))
// It works as expected if using CamelCase
//    .AddControllers(options => options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider(System.Text.Json.JsonNamingPolicy.CamelCase)))
    .AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseLower);
var app = builder.Build();
app.MapControllers();
app.Run();

[ApiController]
[Route("")]
public class TestController : ControllerBase
{
    [HttpPost]
    public async Task<IActionResult> Test([FromBody] string[] test)
    {
        return Ok(test);
    }
}

Expected behavior

Return the passed in string array

Actual behavior

System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Text.Json.JsonSeparatorNamingPolicy.ConvertName(String name)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.SystemTextJsonValidationMetadataProvider.CreateValidationMetadata(ValidationMetadataProviderContext context)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultCompositeMetadataDetailsProvider.CreateValidationMetadata(ValidationMetadataProviderContext context)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata.get_ValidationMetadata()
   at Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata.get_PropertyValidationFilter()
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitImplementation(ModelMetadata& metadata, String& key, Object model)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ObjectModelValidator.Validate(ActionContext actionContext, ValidationStateDictionary validationState, String prefix, Object model, ModelMetadata metadata, Object container)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.EnforceBindRequiredAndValidate(ObjectModelValidator baseObjectValidator, ActionContext actionContext, ParameterDescriptor parameter, ModelMetadata metadata, ModelBindingContext modelBindingContext, ModelBindingResult modelBindingResult, Object container)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value, Object container)
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

Author: patrik-sabo
Assignees: -
Labels:

area-System.Text.Json, untriaged

Milestone: -

@eiriktsarpalis
Copy link
Member

Tagging @dotnet/aspnet-team since the stacktrace seems to suggest that MVC components are passing null strings directly to the the JsonNamingPolicy which doesn't support them.

eiriktsarpalis referenced this issue Apr 19, 2023
Fixing what appears to be a negation bug causing null names to be passed to the underlying `JsonNamingPolicy`.
@eiriktsarpalis
Copy link
Member

The issue is due to the SystemTextJsonValidationMetadataProvider class in aspnetcore is suppressing null warnings for null property names:

propertyName = _jsonNamingPolicy.ConvertName(context.Key.Name!);

I've posted a PR that should fix the issue.

@eiriktsarpalis
Copy link
Member

Reopening and transfering the issue, since it's caused by an aspnetcore component.

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label Apr 22, 2023
@eiriktsarpalis eiriktsarpalis transferred this issue from dotnet/runtime Apr 22, 2023
@eiriktsarpalis eiriktsarpalis changed the title The ConvertName function in JsonSeparatorNamingPolicy doesn't handle null value SystemTextJsonValidationMetadataProvider passes null values to the JsonNamingPolicy Apr 22, 2023
@ghost ghost locked as resolved and limited conversation to collaborators May 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates
Projects
None yet
4 participants