Tomas Aschan
Tomas Aschan

Reputation: 60664

How do I make an ASP.NET Core void/Task action method return 204 No Content

How do I configure the response type of a void/Task action method to be 204 No Content rather than 200 OK?

For example, consider a simple controller:

public class MyController : Controller
{
    [HttpPost("foo")]
    public async Task Foo() {
        await Task.CompletedTask;
    }

    [HttpPost("bar")]
    public async Task<IActionResult> Bar() {
        await Task.CompletedTask;
        return NoContent();
    }
}

I'd like both methods to return 204 No Content, but the default (and thus what /foo returns) seems to be 200 OK. I tried various things, such as adding a [ProducesResponseType(204)] attribute to /foo, but I haven't found anything that seems to have any effect.

Upvotes: 16

Views: 11621

Answers (5)

menxin
menxin

Reputation: 2244

You can remove HttpNoContent formatter from outputformatters.Ms doc

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        // requires using Microsoft.AspNetCore.Mvc.Formatters;
        options.OutputFormatters.RemoveType<StringOutputFormatter>();
        options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
    });
}

Upvotes: 0

0xced
0xced

Reputation: 26558

Here is a solution that returns 204 instead of 200 for all controller methods that return void or Task. First, create a result filter:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using static Microsoft.AspNetCore.Http.StatusCodes;

namespace StackOverflow.SampleCode
{
    /// <summary>
    /// A filter that transforms http status code 200 OK to 204 No Content for controller actions that return nothing,
    /// i.e. <see cref="System.Void"/> or <see cref="Task"/>.
    /// </summary>
    internal class VoidAndTaskTo204NoContentFilter : IResultFilter
    {
        /// <inheritdoc/>
        public void OnResultExecuting(ResultExecutingContext context)
        {
            if (context.ActionDescriptor is ControllerActionDescriptor actionDescriptor)
            {
                var returnType = actionDescriptor.MethodInfo.ReturnType;
                if (returnType == typeof(void) || returnType == typeof(Task))
                {
                    context.HttpContext.Response.StatusCode = Status204NoContent;
                }
            }
        }

        /// <inheritdoc/>
        public void OnResultExecuted(ResultExecutedContext context)
        {
        }
    }
}

Then register the filter globally:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options => options.Filters.Add<VoidAndTaskTo204NoContentFilter>());
}

This will affect all your controller methods.

Upvotes: 11

AlbertK
AlbertK

Reputation: 13227

A workable solution provided by Nish26. But if you don't want to clog up the controller by an excess code then another option is to create a ResultFilter:

public class ResponseCodeAttribute : Attribute, IResultFilter
{
    private readonly int _statusCode;
    public ResponseCode(int statusCode)
    {
        this._statusCode = statusCode;
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
    }
    public void OnResultExecuted(ResultExecutedContext context)
    {
        context.HttpContext.Response.StatusCode = _statusCode;
    }
}

And then use it with the action method:

[ResponseCode(204)]
[HttpPost("foo")]
public async Task Foo() {
    await Task.CompletedTask;
}

BTW, ProducesResponseType is just an attribute that helps to create an API metadata.

Upvotes: 3

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131704

First of all, I don't think this is a good idea. Controller actions should be testable without creating the entire pipeline. You can test Bar() to ensure it returns 204 simply by checking the return result. You can't do that with an action that modifies the return result through filters.

That said, it is possible to modify the result by using Result Filters, attributes that implement the IResultFilter, IAsyncResultFilter interfaces. There's also an abstract ResultFilterAttribute class that implements both interfaces provides an implementation for IAsyncResultFilter that calls the IResultFilter methods.

You could create an attribute that modifies the status code like this :

public class ResponseCodeAttribute : ResultFilterAttribute
{
    //Public property to enable reflection, inspection
    public int StatusCode {get;}

    public ResponseCodeAttribute(int statusCode)=>StatusCode=statusCode;

    public override void OnResultExecuted(ResultExecutedContext context)
    {
        context.HttpContext.Response.StatusCode = StatusCode;
    }
}

And use it with :

[HttpPost,ResponseCode(204)]
public async Task Foo() {
    await Task.CompletedTask;
}

This is not enough though.

Callers of this method have no way of knowing that it will return 204 instead of the expected 200. That's where the metadata-only attribute ProducesResponseTypeAttribute comes in. This attribute implements the IApiResponseMetadataProvider which is used to provide metadata to API Explorer and the proxy/documentation tools like Swagger. At the very least you should use both attributes, eg :

[HttpPost,ResponseCode(204),ProducesResponseType(204)]
public async Task Foo() {
    await Task.CompletedTask;
}

Even better, combine ResponseCodeAttribute with IApiResponseMetadataProvider :

public class ResponseCodeAttribute : ResultFilterAttribute,IApiResponseMetadataProvider
{
    public int StatusCode {get;}

    public Type Type { get; }=typeof(void);

    public ResponseCodeAttribute(int statusCode)=>StatusCode=statusCode;

    public override void OnResultExecuted(ResultExecutedContext context)
    {
        context.HttpContext.Response.StatusCode = StatusCode;
    }

    void IApiResponseMetadataProvider.SetContentTypes(MediaTypeCollection contentTypes)
    {            
    }
}

And apply it with the initial :

[HttpPost,ResponseCode(204)]
public async Task Foo() {
    await Task.CompletedTask;
}

Upvotes: 1

Nish26
Nish26

Reputation: 987

Controller base class has a Response property of type HttpResponse. You can directly set :

 Response.StatusCode = 204 ;

Upvotes: 0

Related Questions