I want to implement a custom Content-Type validation filter so that a custom error model on a 415 Unsupported Media Type can be provided.
Something like this:
public class ValidateContentTypeFilterAttribute : ActionFilterAttribute
{
private const string JsonMimeType = "application/json";
public override void OnActionExecuting(ActionExecutingContext context)
{
string requestMethod = context.HttpContext.Request.Method.ToUpper();
if (requestMethod == WebRequestMethods.Http.Post || requestMethod == WebRequestMethods.Http.Put)
{
if (request.ContentType != JsonMimeType)
{
// "Unsupported Media Type" HTTP result.
context.Result = new HttpUnsupportedMediaTypeResult();
return;
}
}
}
}
The problem is that the MVC pipeline seems to be "catching" unsupported or invalid Content-Type values before executing any custom filters. Even the 'application/xml' content type will be refused.
Where would this be configured?
My MVC configuration consists of not much more than this:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.DefaultValueHandling = DefaultValueHandling.Include;
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
options.SerializerSettings.Converters.Add(new SquidJsonConverter());
})
.AddMvcOptions(options =>
{
options.Filters.Add(typeof(ValidateAntiForgeryTokenAttribute));
options.Filters.Add(typeof(ValidateContentTypeFilterAttribute));
options.Filters.Add(typeof(ValidateAcceptFilterAttribute));
options.Filters.Add(typeof(ValidateModelFilterAttribute));
});
...
}
Action filters are too late in the processing pipeline for what you are trying to achieve here.
The filter execution order for an "incoming" request is the following:
OnAuthorization..
method invocationOnResourceExecuting..
method invocation ModelOnActionExecuting..
method invocationYou could instead create a resource filter. An example:
public class CustomResourceFilter : IResourceFilter
{
private readonly string jsonMediaType = "application/json";
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
if (context.HttpContext.Request.Method == "PUT" || context.HttpContext.Request.Method == "POST")
{
if (!string.Equals(
MediaTypeHeaderValue.Parse(context.HttpContext.Request.ContentType).MediaType,
jsonMediaType,
StringComparison.OrdinalIgnoreCase))
{
context.Result = new JsonResult(new { Error = "An error message here" }) { StatusCode = 415 };
}
}
}
}
If you would like to modify all types of UnsupportedMediaTypeResult
responses, then you could write a Result filter instead.
The filter pipeline for outgoing response is:
OnActionExecuted...
method invocation OnResultExecuting..
method invocationOnResultExecuted..
method invocationOnResourceExecuted..
method invocationAn example with a Result filter:
public class CustomResultFilter : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var result = context.Result as UnsupportedMediaTypeResult;
if (result != null)
{
context.Result = new JsonResult(new { Error = "An error message here" }) { StatusCode = 415 };
}
}
}