Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using [JsonProperty("name")] in ModelState.Errors

We have a couple of models that override the name via JsonProperty, but this causes an issue when we get validation errors through ModelState. For example:

class MyModel
{
    [JsonProperty("id")]
    [Required]
    public string MyModelId {get;set;}
}

class MyModelController
{
    public IHttpActionResult Post([FromBody] MyModel model)
    {
        if (!ModelState.IsValid)
        {
            return HttpBadRequest(ModelState);
        }

        /* etc... */
    }
}

The above Post will return the error The MyModelId field is required. which isn't accurate. We'd like this to say The id field is required.. We've attempted using [DataMember(Name="id")] but get the same result.

Question 1: Is there a way we can get ModelState errors to show the JSON property name rather than the C# property name aside from providing our own error messages on every [Required] attribute?

-- Update --

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 really hoping there's a built-in way to do this, but this seems to do the job...

https://gist.github.com/Blackbaud-JasonTremper/b64dc6ddb460afa1698daa6d075857e4

Question 2: Can ModelState.Key be assumed to match the <parameterName>.<reflectedProperty> syntax or are there cases where this might not be true?

Question 3: Is there an easier way to determine what the JSON parameter name is expected to be rather than searching via reflection on [DataMember] or [JsonProperty] attributes?

like image 659
jt000 Avatar asked Nov 16 '16 19:11

jt000


2 Answers

Did you try using DisplayName attribute?

displayname attribute vs display attribute

Also, you can assign an error message to [Required] attribute.

[Required(ErrorMessage = "Name is required")]

like image 64
bobek Avatar answered Nov 01 '22 07:11

bobek


I also faced this problem, I modified some code from your link to fit my WebAPI. modelState will also store the old key which is the variable name of the model, plus the Json Property names.

  1. First, create the filter ValidateModelStateFilter
  2. Add [ValidateModelStateFilter] above controller method

The filter source code:

public class ValidateModelStateFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var descriptor = actionContext.ActionDescriptor;
        var modelState = actionContext.ModelState;

        if (descriptor != null)
        {
            var parameters = descriptor.GetParameters();

            var subParameterIssues = modelState.Keys
                                               .Where(s => s.Contains("."))
                                               .Where(s => modelState[s].Errors.Any())
                                               .GroupBy(s => s.Substring(0, s.IndexOf('.')))
                                               .ToDictionary(g => g.Key, g => g.ToArray());

            foreach (var parameter in parameters)
            {
                var argument = actionContext.ActionArguments[parameter.ParameterName];

                if (subParameterIssues.ContainsKey(parameter.ParameterName))
                {
                    var subProperties = subParameterIssues[parameter.ParameterName];
                    foreach (var subProperty in subProperties)
                    {
                        var propName = subProperty.Substring(subProperty.IndexOf('.') + 1);
                        var property = parameter.ParameterType.GetProperty(propName);
                        var validationAttributes = property.GetCustomAttributes(typeof(ValidationAttribute), true);

                        var value = property.GetValue(argument);

                        modelState[subProperty].Errors.Clear();
                        foreach (var validationAttribute in validationAttributes)
                        {
                            var attr = (ValidationAttribute)validationAttribute;
                            if (!attr.IsValid(value))
                            {
                                var parameterName = GetParameterName(property);
                                // modelState.AddModelError(subProperty, attr.FormatErrorMessage(parameterName));
                                modelState.AddModelError(parameterName, attr.FormatErrorMessage(parameterName));
                            }
                        }
                    }
                }


            }
        }

    }

    private string GetParameterName(PropertyInfo property)
    {
        var dataMemberAttribute = property.GetCustomAttributes<DataMemberAttribute>().FirstOrDefault();
        if (dataMemberAttribute?.Name != null)
        {
            return dataMemberAttribute.Name;
        }

        var jsonProperty = property.GetCustomAttributes<JsonPropertyAttribute>().FirstOrDefault();
        if (jsonProperty?.PropertyName != null)
        {
            return jsonProperty.PropertyName;
        }

        return property.Name;
    }
}
like image 43
code4j Avatar answered Nov 01 '22 07:11

code4j