Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC validation return lowercase property name

In my ASP.NET MVC Core web application the Json serialization of properties is set to camel case (with first letter lowercase):

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
    .AddJsonOptions(opt =>
    {
        opt.SerializerSettings.ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() };
        opt.SerializerSettings.Converters.Add(new StringEnumConverter(true));
    });

The serialization to the client is working as expected.

But when the javascript client tries to post data and this data is not valid, he receives a validation message with capital letter properties, this validation messages are the ModelState:

{"Info":["The Info field is required."]}

Is there a way to make ASP.NET return lowercase property in validation messages of the ModelState to reflect the naming strategy?

like image 301
Martin Staufcik Avatar asked Nov 18 '25 16:11

Martin Staufcik


2 Answers

The solution is to disable the automatic api validation filter and create own json result with the validation messages:

services.Configure<ApiBehaviorOptions>(options =>
{
    options.SuppressModelStateInvalidFilter = true; 
});

And in the controller:

protected ActionResult ValidationFailed()
{
    var errorList = ModelState.ToDictionary(
        kvp => kvp.Key.ToCamelCase(),
        kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
    );

    return BadRequest(errorList);
}

public async Task<ActionResult> Create([FromBody]TCreateDto model)
{
    if (ModelState.IsValid == false)
    {
        return ValidationFailed();
    }

    ...
}

The string helper method:

public static string ToCamelCase(this string name)
{
    if (string.IsNullOrEmpty(name))
    {
        return name;
    }
    return name.Substring(0, 1).ToLower() + name.Substring(1);
}
like image 50
Martin Staufcik Avatar answered Nov 21 '25 09:11

Martin Staufcik


There is an easier solution. Use Fluent Validator's ValidatorOptions.Global.PropertyNameResolver. Taken from here and converted to C# 8 and Fluent Validation 9:

In Startup.cs, ConfigureServices use:

services
    .AddControllers()
    .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
    .AddFluentValidation(fv =>
    {
        fv.RegisterValidatorsFromAssemblyContaining<MyValidator>();
        // Convert property names to camelCase as Asp.Net Core does https://github.com/FluentValidation/FluentValidation/issues/226 
        ValidatorOptions.Global.PropertyNameResolver = CamelCasePropertyNameResolver.ResolvePropertyName;
    })
    .AddNewtonsoftJson(NewtonsoftUtils.SetupNewtonsoftOptionsDefaults);

and resolver itself:

/// <summary>
/// Convert property names to camelCase as Asp.Net Core does 
/// https://github.com/FluentValidation/FluentValidation/issues/226
/// </summary>
public class CamelCasePropertyNameResolver
{

    public static string? ResolvePropertyName(Type type, MemberInfo memberInfo, LambdaExpression expression)
    {
        return ToCamelCase(DefaultPropertyNameResolver(type, memberInfo, expression));
    }

    private static string? DefaultPropertyNameResolver(Type type, MemberInfo memberInfo, LambdaExpression expression)
    {
        if (expression != null)
        {
            var chain = PropertyChain.FromExpression(expression);
            if (chain.Count > 0)
            {
                return chain.ToString();
            }
        }

        if (memberInfo != null)
        {
            return memberInfo.Name;
        }

        return null;
    }

    private static string? ToCamelCase(string? s)
    {
        if (string.IsNullOrEmpty(s) || !char.IsUpper(s[0]))
        {
            return s;
        }

        var chars = s.ToCharArray();

        for (var i = 0; i < chars.Length; i++)
        {
            if (i == 1 && !char.IsUpper(chars[i]))
            {
                break;
            }

            var hasNext = (i + 1 < chars.Length);
            if (i > 0 && hasNext && !char.IsUpper(chars[i + 1]))
            {
                break;
            }

            chars[i] = char.ToLower(chars[i], CultureInfo.InvariantCulture);
        }

        return new string(chars);
    }
}
like image 21
Karel Kral Avatar answered Nov 21 '25 09:11

Karel Kral



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!