2

Our application uses validation attributes to make use of the ASP.NET model validation, however this gives dot separated names for validation errors. When passed through the CamelCasePropertyNamesContractResolver this only applies camelcase to before the first dot, whereas we would like the have camelcase applied to each section of the name.

For example we currently get the current json response:

{
    "body.State": [
        "The state field is required."
    ],
    "body.LatestVersion": [
        "The latestVersion field is required."
    ]
}

But desire to get out:

{
    "body.state": [
        "The state field is required."
    ],
    "body.latestVersion": [
        "The latestVersion field is required."
    ]
}

In our MVC setup we do have a line similar to

services.AddJsonOptions(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());

We'd appreciate any solution, be that modifications to how we set up the resolver, or how we could modify the validation.


Edit: Just for reference the model structure for the request that is generating this request is as follows:

public sealed class RequestModel
{
    [FromRoute, DisplayName("entity"), Required, MaximumLength(255)]
    public string Entity { get; set; }

    [FromBody, DisplayName("body"), Required]
    public BodyModel Body { get; set; }
}

public sealed class BodyModel
{
    [DisplayName("latestVersion"), Required, MaximumLength(255)]
    public string LatestVersion { get; set; }

    [DisplayName("state"), Required]
    public ModelState State { get; set; }
}

and the request body being sent is:

{
}
  • The JSON doesn't match your model at all, neither `RequestModel` nor `BodyModel` will generate the *current json response* shown in the question. Are you using something like `JsonPathConverter` from [this answer](https://stackoverflow.com/a/33094930) to [Can I specify a path in an attribute to map a property in my class to a child property in my JSON?](https://stackoverflow.com/q/33088462)? – dbc Jan 22 '19 at 23:40
  • Are you considering that this is the automatic response generated by validation? I know that just serializing it with JSON.Net wouldn't give the response created, but we are getting this from the built in model validation system. We don't have a JsonPathConverter anywhere in the system. – Jake Conkerton-Darby Jan 23 '19 at 12:11
  • In that case I'm not sure what the c# data model is for the automatic response before it is serialized. But if it's a `Dictionary>` then I can tell you how to do this. – dbc Jan 23 '19 at 20:39
  • It does under the hood represented as a dictionary of some form, yes. I believe it is a [ModelStateDictionary](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.modelbinding.modelstatedictionary?view=aspnetcore-2.2), which is a `string` to `ModelStateEntry` which appears to hold a list of validation errors somewhere within it. – Jake Conkerton-Darby Jan 24 '19 at 15:56
  • @dbc Would you be able to elaborate on the solution for a dictionary? – Jake Conkerton-Darby Jan 25 '19 at 09:05
  • Added. Unit-tested with an `IDictionary`. Please do let me know if it solves this problem. – dbc Jan 27 '19 at 21:31

1 Answers1

3

Assuming that the validation errors are serialized as some sort of IDictionary<string, T> for some T, then the JSON property names corresponding to the dictionary keys can be piecewise camel-cased between each . character by creating a custom naming strategy.

Json.NET encapsulates the logic to algorithmically remap property names and dictionary keys (e.g. to camel case) in the NamingStrategy type, specifically CamelCaseNamingStrategy. To modify the logic of a naming strategy to apply to each portion of a property name between . characters, you can adopt the decorator pattern and create a decorator naming strategy that applies some inner strategy to each portion of the name like so:

public class PiecewiseNamingStrategy : NamingStrategy
{
    readonly NamingStrategy baseStrategy;

    public PiecewiseNamingStrategy(NamingStrategy baseStrategy)
    {
        if (baseStrategy == null)
            throw new ArgumentNullException();
        this.baseStrategy = baseStrategy;
    }

    protected override string ResolvePropertyName(string name)
    {
        return String.Join(".", name.Split('.').Select(n => baseStrategy.GetPropertyName(n, false)));
    }
}

Then, configure MVC as follows:

options.ContractResolver = new DefaultContractResolver
{
    NamingStrategy = new PiecewiseNamingStrategy(new CamelCaseNamingStrategy()) 
    { 
        OverrideSpecifiedNames = true, ProcessDictionaryKeys = true 
    },
};

This takes advantage of the fact that, as shown in the reference source, CamelCasePropertyNamesContractResolver is basically just a subclass of DefaultContractResolver that uses a CamelCaseNamingStrategy with ProcessDictionaryKeys = true and OverrideSpecifiedNames = true.

Notes:

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 2
    This works exactly as desired. We'll evaluate whether we want to implement it (as it does apply to all dictionaries), but thank you very much for your answer – Jake Conkerton-Darby Jan 28 '19 at 09:18