diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index a633edb37e..4aedf0b087 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -5,6 +5,7 @@ using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; using Volo.Abp.AspNetCore.Mvc.Features; using Volo.Abp.AspNetCore.Mvc.ModelBinding; +using Volo.Abp.AspNetCore.Mvc.Response; using Volo.Abp.AspNetCore.Mvc.Uow; using Volo.Abp.AspNetCore.Mvc.Validation; @@ -28,6 +29,7 @@ namespace Volo.Abp.AspNetCore.Mvc private static void AddFilters(MvcOptions options) { options.Filters.AddService(typeof(AbpAuditActionFilter)); + options.Filters.AddService(typeof(AbpNoContentActionFilter)); options.Filters.AddService(typeof(AbpFeatureActionFilter)); options.Filters.AddService(typeof(AbpValidationActionFilter)); options.Filters.AddService(typeof(AbpUowActionFilter)); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs new file mode 100644 index 0000000000..b6d56b2f2a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs @@ -0,0 +1,31 @@ +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Mvc.Response +{ + public class AbpNoContentActionFilter : IAsyncActionFilter, ITransientDependency + { + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + if (!context.ActionDescriptor.IsControllerAction()) + { + await next(); + return; + } + + await next(); + + if (context.HttpContext.Response.StatusCode == (int)HttpStatusCode.OK) + { + var returnType = context.ActionDescriptor.GetReturnType(); + if (returnType == typeof(Task) || returnType == typeof(void)) + { + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.NoContent; + } + } + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs new file mode 100644 index 0000000000..dc0ae30d71 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs @@ -0,0 +1,51 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace Volo.Abp.AspNetCore.Mvc.Response +{ + [Route("api/NoContent-Test")] + public class NoContentTestController : AbpController + { + [HttpGet] + [Route("TestMethod")] + public void TestMethod() + { + } + + [HttpGet] + [Route("TestMethodWithReturn")] + public string TestMethodWithReturn() + { + return "TestReturn"; + } + + [HttpGet] + [Route("TestCustomHttpStatusCodeMethod")] + public void TestCustomHttpStatusCodeMethod() + { + Response.Redirect("/"); + } + + [HttpGet] + [Route("TestAsyncMethod")] + public async Task TestAsyncMethod() + { + await Task.CompletedTask; + } + + [HttpGet] + [Route("TestAsyncMethodWithReturn")] + public async Task TestAsyncMethodWithReturn() + { + return await Task.FromResult("TestReturn"); + } + + [HttpGet] + [Route("TestAsyncCustomHttpStatusCodeMethod")] + public async Task TestAsyncCustomHttpStatusCodeMethod() + { + Response.Redirect("/"); + await Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs new file mode 100644 index 0000000000..6c68c13f07 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs @@ -0,0 +1,58 @@ +using System.Net; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Response +{ + public class NoContentTestController_Tests : AspNetCoreMvcTestBase + { + [Fact] + public async Task Should_Set_No_Content_For_Void_Action() + { + var result = await GetResponseAsync("/api/NoContent-Test/TestMethod", HttpStatusCode.NoContent) + .ConfigureAwait(false); + result.StatusCode.ShouldBe(HttpStatusCode.NoContent); + } + + [Fact] + public async Task Should_Not_Set_No_Content_For_Not_Void_Action() + { + var result = await GetResponseAsync("/api/NoContent-Test/TestMethodWithReturn") + .ConfigureAwait(false); + result.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + [Fact] + public async Task Should_Not_Set_No_Content_For_Custom_Http_Status_Code_Action() + { + var result = await GetResponseAsync("/api/NoContent-Test/TestCustomHttpStatusCodeMethod", HttpStatusCode.Redirect) + .ConfigureAwait(false); + result.StatusCode.ShouldBe(HttpStatusCode.Redirect); + } + + [Fact] + public async Task Should_Set_No_Content_For_Task_Action() + { + var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncMethod", HttpStatusCode.NoContent) + .ConfigureAwait(false); + result.StatusCode.ShouldBe(HttpStatusCode.NoContent); + } + + [Fact] + public async Task Should_Not_Set_No_Content_For_Not_Task_Action() + { + var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncMethodWithReturn") + .ConfigureAwait(false); + result.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + [Fact] + public async Task Should_Not_Set_No_Content_For_Custom_Http_Status_Code_Async_Action() + { + var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncCustomHttpStatusCodeMethod", HttpStatusCode.Redirect) + .ConfigureAwait(false); + result.StatusCode.ShouldBe(HttpStatusCode.Redirect); + } + } +}