简体   繁体   中英

ASP.net WebApi - Custom Message for malformed post data

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 ErrorMessage s 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.

I'm not completely certain, but you can probably just overload the default JsonMediaTypeFormatter and XmlMediaTypeFormatter implementations and wrap their exceptions with your own exception. This has the benefit of directly tying your exception handling to the source of the problem (ie the formatters) rather than trying to handle it via an IExceptionFilter .

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

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 ReadFromStreamAsync(Type type, System.IO.Stream stream, System.Net.Http.Headers.HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
    {

        System.Threading.Tasks.Task 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.

So here's the rub:

  1. It's the MediaTypeFormatter which consumes the System.IO.Stream associated with the HTTP request;
  2. While that formatter will invoke IFormatterLogger.OnError(string,Exception) , that first argument is simply a 'path' (however the parser chooses to conceive of it) and not a copy of the input itself.

So yeah, if you want to see and react to (or log) the request that caused the fault, you'll need to implement and register your own MediaTypeFormatter, do your own parsing/de-serialization, and handle it according to your own protocols (in addition to the WebApi ones).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM