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:
MediaTypeFormatter
which consumes the System.IO.Stream
associated with the HTTP request; 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.