How to remove prefix from ModelState keys?

Topics: ASP.NET Web API
Jun 14, 2013 at 9:36 AM
Is there a way to remove prefixes from ModelState keys? Consider a scenario, where you have an additional validation in your business layer classes, inside which you don't know about ModelState key prefixes.. you just add another model error by property name .AddModelError("Password", "Too strong.") expecting validation reporting logic work as expected down the road, but it won't since some keys will be prefixed and some not.
public object Post(User user)
{
    if (/* userName is taken */)
    {
        ModelState.AddModelError("UserName", "This username already exists.");
    }

    return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
Also error messages returned to a client shouldn't contain those prefixes! Currently Web Api returns model state errors like this:
{
    "message": "Your request is invalid.",
    "modelState":
    {
        "user.Password": "Your password is too strong."
    }
}
I need them look more like this instead (without ModelState key prefixes, elso replace "modelState" with "errors"):
{
    "message": "Your request is invalid.",
    "errors":
    {
        "password": "Your password is too strong."
    }
}
Ho do I do this? Please advise.
Apr 21 at 5:50 PM
Can you give me an example how to do it with ASP.NET Web Api 2.1?
Coordinator
Apr 21 at 7:07 PM
The prefixes used by ModelState follow a specific convention to indicate where the model errors are coming from. If you want to use ModelState to manage your validation errors you really should follow the same conventions.

The CreateErrorResponse() helper method provides a default serialization of the model state, but you are certainly welcome to use whatever serialization you want. For example, you could define your own serializable ResponseErrors type, populate it with data from the ModelState and then call CreateResponse() with an error status code.

Daniel Roth
Apr 22 at 12:26 PM
Why does a client have to know about implementation details of Web Api action methods (which name was given to an argument with model data)?

For example, a client sends a request:

HTTP POST: /api/users { "userName": "hello", "password": "world" }

And gets a response

{ "message": "Your request is invalid.", "modelState": { "userDto.Password": "Your password is too string." } }

Now the client has to parse model state values, and remove "userDto." prefix.. Why? Doesn't it complicate things?

The other day RESTful service developer decides give that argument some other name "userDto" becomes "dto", this doesn't change any existing functionality of the RESTful service, but client apps may stop working, the ones who potentially hardcoded a previous "userDto" prefix into the code, and there is now way to force them not to hardcode these model state prefixs, because client apps might be developed by 3rd party companies for which you don't have control.

If you really need to send this argument name as a prefix to all the bound model properties, why not to do it for DEBUG mode only? In case you need it for debugging, i don't know..
Coordinator
Apr 22 at 5:11 PM
Ah, good point. The error format is a carry over from model binding form data where the name of the method argument actually does matter. For JSON data the parameter name isn't used at all and the error payload is leaking information about your service implementation.

I would still argue though that this is really an issue with the ModelState serialization, not the ModelState API. While we do provide a built in serialization for ModelState you can still replace that serialization with whatever you want. Unfortunately we can't really change the existing serialization as this might break existing clients.

Daniel Roth
Apr 22 at 5:35 PM
Edited Apr 22 at 5:37 PM
Another issue with it is that you can have some additional validation inside an action method and if you add validation errors like this this.ModelState.AddModelError("UserName", "This username has been already taken.") you will end up having mixed error messages inside ModelState - some with action method argument name prefix, and some without it.

To replace the built-in serialization it has to know which prefix name was used, then it would become ugly.. something like this:
public HttpResponseMessage Post(UserDto userDto)
{
    if (!this.ModelState.IsValid)
    {
        return this.Request.CreateCustomErrorResponse(HttpStatusCode.BadRequest, this.ModelState, modelName: "userDto");
    }
    ...
}
and inside ModelState serializer: if propertyName starts with modelName strip it off

Is there a better way doing it?

I guess you could embed the method argument name inside ModelState without breaking anything, and we could use this string during a custom serialization.