Prevent ‘Automatic HTTP 400 responses’ for and individual controller action

ASP.NET Core 2.1 introduces a nice feature called Automatic HTTP 400 responses which means you don’t need to put the following in the start of each controller action:

if (!ModelState.IsValid)
{
    return BadRequest(ModelState);
}

That was working out well for me until I didn’t want it to happen for one of my actions. I wanted to do some pre-processing of the payload before validating it. In my case I had value in the path that I wanted to set on the incoming object so that it didn’t need to be set in both that path and the payload.

What I ended up with was the following to prevent the automatic response for an action:

[AttributeUsage(AttributeTargets.Method)]
public class SuppressModelStateInvalidFilterAttribute : Attribute, IActionModelConvention
{
    public void Apply(ActionModel action)
    {
        for (var i = 0; i < action.Filters.Count; i++)
        {
            if (action.Filters[i] is ModelStateInvalidFilter)
            {
                action.Filters.RemoveAt(i);
                break;
            }
        }
    }
}

And my action ended up being:

[Route("v1/{tenantId}/users")]
[ApiController]
public class RulesController : ControllerBase
{
    [HttpPost]
    [SuppressModelStateInvalidFilter]
    [ProducesResponseType((int)HttpStatusCode.Created)]
    public async Task Post(Guid tenantId, IncomingType incomingType)
    {
        if (incomingType == null)
        {
            return BadRequest(ModelState);
        }

        incomingType.TenantId = tenantId;

        ModelState.Clear();
        if (TryValidateModel(incomingType) == false)
        {
            return BadRequest(ModelState);
        }

        //...

        return returnType;
    }
}

I also wanted to stop the TenantId from showing up in Swagger documentation so I created the following IDocumentationFilter:

public class IncomingTypeDocumentFilter : IDocumentFilter
{
    public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
    {
        var properties = swaggerDoc.Definitions[nameof(IncomingType)].Properties;
        foreach (var property in properties)
        {
            if (property.Key.Equals(nameof(IncomingType.TenantId), StringComparison.OrdinalIgnoreCase))
            {
                properties.Remove(property.Key);
                break;
            }
        }
    }
}

And registered it in Startup when configuring Swagger:

services.AddSwaggerGen(options =>
    {
        // ...

        options.DocumentFilter();
    });

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s