question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Use JSON Property Name attributes when creating ModelState Validation errors

See original GitHub issue

Background and Motivation

Currently, when the model validation produces the ModelErrorDictionary it will use the property name as error’s key, as described in the example bellow, however in scenarios as SPA applications it can be difficult to handle the response since most of the time the property name is an implementation detail and not exactly what is expose through the JSON data.

public class Outer
{
    [Range(1, 100)]
    [JsonPropertyName("bar")]
    public int Num { get; set; }
    
    [Required]
    public InnerClass SubClass { get; set; }

    public class InnerClass
    {
       [Range(1,10)]
       public int Test1 { get; set; }
    }
}
{
  "errors": {
    "Num": [
      "The field Num must be between 1 and 100."
    ],
    "SubClass.Test1": [
      "The field Test1 must be between 1 and 100."
    ]
  }
}

Also, even when the JSON attributes are not used to define the property name the current implementation does not follow the JSON Naming Policy, that have been a complain since 2016 (#5590).

The proposal is to add two new Metadata Providers, JsonMetadataProvider and NewtonsoftJsonMetadataProvider, the last will be added when the AddNewtonsoftJson is called while the first one could be added by the customer using the MvcOptions. These new providers will be responsible for read from Newtonsoft.Json.JsonPropertyAttribute or System.Text.Json.SerializationJsonPropertyNameAttribute to set the new property ValidationMetadata.ValidationModelName that will be used during the Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationEntry creation.

When the new providers are included, the same example provided will produce the following result:

{
  "errors": {
    "bar": [
      "The field bar must be between 1 and 100."
    ],
    "subClass.test1": [
      "The field test1 must be between 1 and 100."
    ]
  }
}

Related:

Proposed API

namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
   
+ /// <summary>
+ /// An implementation of <see cref="IDisplayMetadataProvider"/> and <see cref="IValidationMetadataProvider"/> for
+ /// the System.Text.Json.Serialization attribute classes.
+ /// </summary>
+ public class JsonMetadataProvider: IDisplayMetadataProvider, IValidationMetadataProvider
+ {
+     /// <summary>
+     /// Creates a new <see cref="JsonMetadataProvider"/> with the default <see cref="CamelCaseNamingStrategy"/>
+     /// </summary>
+     public JsonMetadataProvider()

+     /// <summary>
+     /// Creates a new <see cref="JsonMetadataProvider"/> with an optional <see cref="JsonNamingPolicy"/>
+     /// </summary>
+     /// <param name="namingPolicy">The <see cref="JsonNamingPolicy"/> to be used to configure the metadata provider.</param>
+     public JsonMetadataProvider(JsonNamingPolicy namingPolicy)

+     public void CreateDisplayMetadata(DisplayMetadataProviderContext context)

+     public void CreateValidationMetadata(ValidationMetadataProviderContext context)

+ }
namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson;
   
+ /// <summary>
+ /// An implementation of <see cref="IDisplayMetadataProvider"/> and <see cref="IValidationMetadataProvider"/> for
+ /// the Newtonsoft.Json attribute classes.
+ /// </summary>
+ public class NewtonsoftJsonMetadataProvider : IDisplayMetadataProvider, IValidationMetadataProvider
+ {
+     /// <summary>
+     /// Creates a new <see cref="JsonMetadataProvider"/> with the default <see cref="CamelCaseNamingStrategy"/>
+     /// </summary>
+     public NewtonsoftJsonMetadataProvider()

+     /// <summary>
+     /// Initializes a new instance of <see cref="NewtonsoftJsonMetadataProvider"/> with an optional <see cref="NamingStrategy"/>
+     /// </summary>
+     /// <param name="namingStrategy">The <see cref="NamingStrategy"/> to be used to configure the metadata provider.</param>
+     public NewtonsoftJsonMetadataProvider(NamingStrategy namingStrategy)

+     public void CreateDisplayMetadata(DisplayMetadataProviderContext context)

+     public void CreateValidationMetadata(ValidationMetadataProviderContext context)

+ }

namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
{
   public class ValidationMetadata
   {
+     public string? ValidationModelName { get; set; }
   }
}

Usage Examples

services..AddControllers(options => options.ModelMetadataDetailsProviders.Add(new JsonMetadataProvider()))

or

services.Configure<MvcOptions>(options =>
       options.ModelMetadataDetailsProviders.Add(new JsonMetadataProvider(JsonNamingPolicy.CamelCase)));

Is also possible remove the default provider, if needed:

services.Configure<MvcOptions>(options => {
               options.ModelMetadataDetailsProviders.RemoveType<NewtonsoftJsonMetadataProvider>();            
});

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:6
  • Comments:11 (8 by maintainers)

github_iconTop GitHub Comments

1reaction
brunolins16commented, Mar 15, 2022

@niemyjski you can add the provider here:

https://github.com/exceptionless/Exceptionless/blob/940a04c28c74cad34221113a865a979510c2d432/src/Exceptionless.Web/Startup.cs#L48

Example:

        services.AddControllers(o => {
            o.Filters.Add<ApiExceptionFilter>();
+           o.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider());
            o.ModelBinderProviders.Insert(0, new CustomAttributesModelBinderProvider());
            o.InputFormatters.Insert(0, new RawRequestBodyFormatter());
        }).AddNewtonsoftJson(o => {
            o.SerializerSettings.DefaultValueHandling = DefaultValueHandling.Include;
            o.SerializerSettings.NullValueHandling = NullValueHandling.Include;
            o.SerializerSettings.Formatting = Formatting.Indented;
            o.SerializerSettings.ContractResolver = Core.Bootstrapper.GetJsonContractResolver(); // TODO: See if we can resolve this from the di.
        });
1reaction
brunolins16commented, Mar 7, 2022

@niemyjski can you share a sample app, so, I can provide what you need to do.

Right now we do not have plans to backport.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Using [JsonProperty("name")] in ModelState.Errors
I've been playing around with this and found a "do-it-yourself" method for re-creating the error messages using custom property names. I'm ...
Read more >
Model validation in ASP.NET Core MVC and Razor Pages
Use JSON property names in validation errors. By default, when a validation error occurs, model validation produces a ModelStateDictionary with ...
Read more >
How to Use ModelState Validation in ASP.NET Core Web API
In this article, we are going to talk how to use ModelState validation. Also we are going to see manual and automatic validation....
Read more >
Combining ASP.NET Core validation attributes with Value ...
Let's say that all incoming requests must have both Name and Email , and that the Email field must contain a valid email...
Read more >
ASP.NET Core — FluentValidation documentation
ASP.NET Core¶. FluentValidation can be used within ASP.NET Core web applications to validate incoming models. There are two main approaches for doing this:....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found