ASP.net WebApi - Custom Message for malformed post data

Viewed 2197

I have an MVC 4 WebAPI application. What i want to do is filter out any ModelState errors that happen because of malformed data sent during the put/post.

I have an ActionFilterAttribute that checks if the ModelState is valid. I want to send the state's ErrorMessages back to the user. This part is working fine.

/// <summary>
/// This filter will validate the models that are used in the webapi
/// </summary>
public class MyValidationFilter :System.Web.Http.Filters.ActionFilterAttribute
{
    public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            //ErrorResponse is just a simple data structure used to hold response
            ErrorResponse errorResponse = new ErrorResponse();

            //loop through each key(field) and see if it has any errors
            foreach (var key in actionContext.ModelState.Keys)
            {
                var state = actionContext.ModelState[key];
                if (state.Errors.Any())
                {
                    string validationMessage = state.Errors.First().ErrorMessage;
                    errorResponse.ErrorMessages.Add(new ErrorMessage(validationMessage));                      
                }
            }

            //this is a custom exception class that i have that sends the response to the user.
            throw new WebAPIException(HttpStatusCode.BadRequest, errorResponse );

        }

    }
}

Normal validation (Required, StringLength, Regex) all work fine, because I can control those messages.

[Required(ErrorMessage = "ID is required")]
public string ID { get; set; }

However, I can't control the message if someone passes incorrectly formatted XML or JSON data. If that happens, then I might get

Unterminated string. Expected delimiter: \". Path '', line 1, position 9.

or

Invalid character after parsing property name. Expected ':' but got: }. Path '', line 1, position 9.

or these ones which shows my namespace.

Error converting value \"Medium\" to type 'MyNameSpace.Tenant.WebAPIs.Library.IndividualRegistrationInfo'. Path '', line 1, position 8.

Error in line 1 position 7. Expecting element 'IndividualRegistrationInfo' from namespace 'http://schemas.datacontract.org/2004/07/MyNameSpace.Tenant.WebAPIs.Library.IndividualRegistrationInfo'.. Encountered 'Element' with name 'asdf', namespace ''

I want to somehow send back a generic "Invalid data" message when this happens. Is there another filter that I could use, or some other place that could catch and override these messages?

UPDATE

Here is what I ended up doing based on Chris's advice:

I created 2 new Formatters for JSON and XML.

public class JsonFormatter : JsonMediaTypeFormatter
{
    public override System.Threading.Tasks.Task<object> ReadFromStreamAsync(Type type, System.IO.Stream stream, System.Net.Http.Headers.HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
    {

       System.Threading.Tasks.Task<object> task = base.ReadFromStreamAsync(type, stream, contentHeaders, formatterLogger);

        //parse error if null
       if (task.Result == null)
       {
           //handle error here.
       }

       return task;
    }
}


public class XMLFormatter : XmlMediaTypeFormatter
{
    public override System.Threading.Tasks.Task<object> ReadFromStreamAsync(Type type, System.IO.Stream stream, System.Net.Http.Headers.HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
    {

        System.Threading.Tasks.Task<object> task = base.ReadFromStreamAsync(type, stream, contentHeaders, formatterLogger);

        //parse error if null
        if (task.Result == null)
        {
            //handle error here
        }

        return task;
    }
}

and in the Application_Start method in global.asax

GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonFormatter());
GlobalConfiguration.Configuration.Formatters.Insert(1, new XMLFormatter());

I am not sure if there is a better way, but this seems to work.

3 Answers
Related