asp.net-coreasp.net-core-mvcasp.net-core-1.0asp.net-mvc-filters

Custom Content-Type validation filter?


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));
        });
    ...
}

Solution

  • 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:

    1. Authorization filters' OnAuthorization.. method invocation
    2. Resource filters' OnResourceExecuting.. method invocation Model
    3. Model binding happens (this is the place where the content type check is made)
    4. Action filters' OnActionExecuting.. method invocation
    5. Action execution happens

    You 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:

    1. Action filters' OnActionExecuted... method invocation
    2. Result filters' OnResultExecuting.. method invocation
    3. Result filters' OnResultExecuted.. method invocation
    4. Resource filters' OnResourceExecuted.. method invocation

    An 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 };
            }
        }
    }