Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to convert ModelState errors to valid JSON API response #129

Open
rhyek opened this issue Aug 19, 2016 · 5 comments
Open

How to convert ModelState errors to valid JSON API response #129

rhyek opened this issue Aug 19, 2016 · 5 comments

Comments

@rhyek
Copy link
Contributor

rhyek commented Aug 19, 2016

My API does some validation on POST/PUT requests on some non-JSONAPI endpoints and may generate an error response using ModelState. When trying to convert these endpoints to JSONAPI, I notice Saule isn't currently trying to use the information available in the ModelState response to provide useful error messages back to the client.

I have a request filter that does the following:

    public class InvalidModelStateFilterAttribute : ActionFilterAttribute {
        public override void OnActionExecuting(HttpActionContext actionContext) {
            if (!actionContext.ModelState.IsValid) {
                actionContext.Response = actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
            } else {
                base.OnActionExecuting(actionContext);
            }
        }
    }

Currently, that gets serialized to something like this:

{"Message":"The request is invalid.","ModelState":{"json.Name":["The category already exists."]}}

With Saule, I'm now getting something like this:

{"errors":[{"title":"The request is invalid."}]}

What would be necessary to change to have this errors array contain the actual fields and error descriptions?

@joukevandermaas
Copy link
Owner

Maybe this part of the documentation will help?

That generic error gets serialized if your response is of the type HttpError. There is basically no information in there, so Saule cannot serialize the error properly. If Saule somehow encounters an actual Exception object, it will produce more useful output.

I'm not 100% sure on how to do this using an HttpActionContext, but it should be possible.

@pdesjardins90
Copy link

I agree that model state validation should be support by the library, as it is a very common thing to do in an api. Here's what I did as a work around, maybe you can tweak this for you specific needs:

    public class ModelValidationAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ModelState.IsValid) return;

            var message = actionContext.ModelState
                .SelectMany(f => f.Value.Errors)
                .Aggregate("", (current, error) => current + error.ErrorMessage);            

            actionContext.Response = actionContext.Request
                .CreateErrorResponse(HttpStatusCode.BadRequest, message);
         }
    }

@rhyek
Copy link
Contributor Author

rhyek commented Sep 25, 2016

Hi, hadn't checked this in a while. I ended up doing something similar:

        public override void OnActionExecuting(HttpActionContext actionContext) {
            if (!actionContext.ModelState.IsValid) {
                var modelState = actionContext.ModelState
                    .Select(p => new { key = p.Key, errors = p.Value.Errors.Select(e => e.ErrorMessage) })
                    .ToDictionary(kv => kv.key, kv => kv.errors);
                var json = JsonConvert.SerializeObject(new { ModelState = modelState });
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest) {
                    Content = new StringContent(json, Encoding.UTF8, "application/json")
                };
            } else {
                base.OnActionExecuting(actionContext);
            }
        }

This outputs json the same way WebAPI does it natively.

@triynko
Copy link

triynko commented Jun 14, 2018

First of all, the key names are wrong in model state. It doesn't properly build the right path to the field, so it's useless. Build a validation utility to validate fields on the object yourself, and track proper key names. If you use request DTOs, you can even build in key resolution for naming strategies like 'snake_case'.

But even if it did have correct key names, running this select: Errors.Select(e => e.ErrorMessage) is not correct. In many case, ErrorMessage will be an empty string, and it's actually e.Exception.Message that you'd have to check, so you'd be better off doing something like this: Errors.Select(e => string.IsNullOrWhiteSpace(e.ErrorMessage) ? (e.Exception?.Message ?? "No error message.") : e.ErrorMessage).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants