From dd2ee0ccc62288540a1d165cd7e8374240e5c136 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 15 Jan 2026 10:18:05 +0800 Subject: [PATCH 1/2] Add `AbpNoContentApiDescriptionProvider` to handle NoContent responses Introduces a custom API description provider to automatically add 204 No Content response types for remote service actions returning Task or void, improving API documentation accuracy. Resolve #24647 --- .../AbpNoContentApiDescriptionProvider.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpNoContentApiDescriptionProvider.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpNoContentApiDescriptionProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpNoContentApiDescriptionProvider.cs new file mode 100644 index 0000000000..f15bbc17e4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpNoContentApiDescriptionProvider.cs @@ -0,0 +1,48 @@ +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Reflection; + +namespace Volo.Abp.AspNetCore.Mvc.ApiExploring; + +public class AbpNoContentApiDescriptionProvider : IApiDescriptionProvider, ITransientDependency +{ + public void OnProvidersExecuted(ApiDescriptionProviderContext context) + { + } + + /// + /// The order -999 ensures that this provider is executed right after the + /// Microsoft.AspNetCore.Mvc.ApiExplorer.DefaultApiDescriptionProvider. + /// + public int Order => -999; + + public void OnProvidersExecuting(ApiDescriptionProviderContext context) + { + foreach (var result in context.Results.Where(x => x.IsRemoteService())) + { + var actionProducesResponseTypeAttributes = + ReflectionHelper.GetAttributesOfMemberOrDeclaringType( + result.ActionDescriptor.GetMethodInfo()); + if (actionProducesResponseTypeAttributes.Any(x => x.StatusCode == (int) HttpStatusCode.NoContent)) + { + continue; + } + + var returnType = result.ActionDescriptor.GetReturnType(); + if (returnType == typeof(Task) || returnType == typeof(void)) + { + result.SupportedResponseTypes.Add(new ApiResponseType + { + // If the return type is Task, then we should treat it as a void return type since we can't infer anything without additional metadata or requiring unreferenced code. + Type = typeof(void), + StatusCode = (int) HttpStatusCode.NoContent + }); + } + } + } +} From d38c2021249876098a1d3e810a4827f6543858be Mon Sep 17 00:00:00 2001 From: Ma Liming Date: Thu, 15 Jan 2026 10:34:44 +0800 Subject: [PATCH 2/2] Change methods to virtual in AbpNoContentApiDescriptionProvider --- .../Mvc/ApiExploring/AbpNoContentApiDescriptionProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpNoContentApiDescriptionProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpNoContentApiDescriptionProvider.cs index f15bbc17e4..a0c9570fc6 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpNoContentApiDescriptionProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpNoContentApiDescriptionProvider.cs @@ -11,7 +11,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApiExploring; public class AbpNoContentApiDescriptionProvider : IApiDescriptionProvider, ITransientDependency { - public void OnProvidersExecuted(ApiDescriptionProviderContext context) + public virtual void OnProvidersExecuted(ApiDescriptionProviderContext context) { } @@ -21,7 +21,7 @@ public class AbpNoContentApiDescriptionProvider : IApiDescriptionProvider, ITran /// public int Order => -999; - public void OnProvidersExecuting(ApiDescriptionProviderContext context) + public virtual void OnProvidersExecuting(ApiDescriptionProviderContext context) { foreach (var result in context.Results.Where(x => x.IsRemoteService())) {