Reputation: 60664
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
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
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
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
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
Reputation: 987
Controller base class has a Response property of type HttpResponse. You can directly set :
Response.StatusCode = 204 ;
Upvotes: 0