From c6f0f32e58b131367c9a22ae32eea47dabf6455a Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 31 Oct 2023 09:25:43 +0800 Subject: [PATCH] fix(wrapper): the result of the wrapper should be in the Api description. --- .../LINGYUN.MicroService.SingleProject.sln | 2 +- .../Wrapper/AbpAspNetCoreMvcWrapperModule.cs | 2 + .../AbpWrapResultApiDescriptionProvider.cs | 153 ++++++++++++++++++ .../Mvc/Wrapper/IWrapResultChecker.cs | 5 +- .../Mvc/Wrapper/WrapResultChecker.cs | 53 ++++-- ...rviceApplicationsSingleModule.Configure.cs | 2 +- 6 files changed, 198 insertions(+), 19 deletions(-) create mode 100644 aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/ApiExploring/AbpWrapResultApiDescriptionProvider.cs diff --git a/aspnet-core/LINGYUN.MicroService.SingleProject.sln b/aspnet-core/LINGYUN.MicroService.SingleProject.sln index 34467c4f9..24066a7de 100644 --- a/aspnet-core/LINGYUN.MicroService.SingleProject.sln +++ b/aspnet-core/LINGYUN.MicroService.SingleProject.sln @@ -494,7 +494,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Mvc. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Wrapper", "framework\common\LINGYUN.Abp.AspNetCore.Wrapper\LINGYUN.Abp.AspNetCore.Wrapper.csproj", "{FDBA1B4A-CC5D-4710-AB8C-FA5A91B91BDE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.OpenIddict.AspNetCore", "modules\openIddict\LINGYUN.Abp.OpenIddict.AspNetCore\LINGYUN.Abp.OpenIddict.AspNetCore.csproj", "{6026DAE3-F2AD-4F6B-99EF-EEAAA5873861}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OpenIddict.AspNetCore", "modules\openIddict\LINGYUN.Abp.OpenIddict.AspNetCore\LINGYUN.Abp.OpenIddict.AspNetCore.csproj", "{6026DAE3-F2AD-4F6B-99EF-EEAAA5873861}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperModule.cs b/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperModule.cs index 7181af47a..840736a10 100644 --- a/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperModule.cs +++ b/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperModule.cs @@ -5,7 +5,9 @@ using LINGYUN.Abp.Wrapper; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; +using System.Collections.Generic; using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.ApiExploring; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.AspNetCore.Mvc.ProxyScripting; using Volo.Abp.Content; diff --git a/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/ApiExploring/AbpWrapResultApiDescriptionProvider.cs b/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/ApiExploring/AbpWrapResultApiDescriptionProvider.cs new file mode 100644 index 000000000..25b88ff96 --- /dev/null +++ b/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/ApiExploring/AbpWrapResultApiDescriptionProvider.cs @@ -0,0 +1,153 @@ +using LINGYUN.Abp.Wrapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.ApiExploring; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Reflection; +using Volo.Abp.Threading; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper.ApiExploring; +public class AbpWrapResultApiDescriptionProvider : IApiDescriptionProvider, ITransientDependency +{ + private readonly MvcOptions _mvcOptions; + private readonly AbpWrapperOptions _wrapperOptions; + private readonly AbpRemoteServiceApiDescriptionProviderOptions _providerOptions; + private readonly IWrapResultChecker _wrapResultChecker; + private readonly IModelMetadataProvider _modelMetadataProvider; + + public AbpWrapResultApiDescriptionProvider( + IOptions mvcOptions, + IOptions wrapperOptions, + IOptions providerOptions, + IWrapResultChecker wrapResultChecker, + IModelMetadataProvider modelMetadataProvider) + { + _mvcOptions = mvcOptions.Value; + _wrapperOptions = wrapperOptions.Value; + _providerOptions = providerOptions.Value; + _wrapResultChecker = wrapResultChecker; + _modelMetadataProvider = modelMetadataProvider; + } + + public int Order => -999; + + public virtual void OnProvidersExecuted(ApiDescriptionProviderContext context) + { + } + + public virtual void OnProvidersExecuting(ApiDescriptionProviderContext context) + { + WrapperOKResponse(context); + } + + protected virtual void WrapperOKResponse(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)_wrapperOptions.HttpStatusCode)) + { + continue; + } + + if (_wrapResultChecker.WrapOnAction(result.ActionDescriptor) && + result.ActionDescriptor is ControllerActionDescriptor actionDescriptor) + { + var returnType = AsyncHelper.UnwrapTask(actionDescriptor.MethodInfo.ReturnType); + + Type wrapResultType = null; + if (returnType == null || returnType == typeof(void)) + { + wrapResultType = typeof(WrapResult); + } + else + { + wrapResultType = typeof(WrapResult<>).MakeGenericType(returnType); + } + + var responseType = new ApiResponseType + { + Type = wrapResultType, + StatusCode = (int)_wrapperOptions.HttpStatusCode, + ModelMetadata = _modelMetadataProvider.GetMetadataForType(wrapResultType) + }; + + foreach (var responseTypeMetadataProvider in _mvcOptions.OutputFormatters.OfType()) + { + var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes(null, wrapResultType); + if (formatterSupportedContentTypes == null) + { + continue; + } + + foreach (var formatterSupportedContentType in formatterSupportedContentTypes) + { + responseType.ApiResponseFormats.Add(new ApiResponseFormat + { + Formatter = (IOutputFormatter)responseTypeMetadataProvider, + MediaType = formatterSupportedContentType + }); + } + } + // TODO: 是否有必要对其他响应代码定义包装结果? + // 例外1: 当用户传递 _AbpDontWrapResult 请求头时, 响应结果与预期不一致 + // 例外2: 当控制器Url在被忽略Url中, 响应结果与预期不一致 + // 例外3: 当引发异常在被忽略异常中, 响应结果为 RemoteServiceErrorResponse 对象, 与预期不一致 + + result.SupportedResponseTypes.RemoveAll(x => x.StatusCode == responseType.StatusCode); + result.SupportedResponseTypes.AddIfNotContains( + x => x.StatusCode == responseType.StatusCode, + () => responseType); + WrapperErrorResponse(result); + } + } + } + + protected virtual void WrapperErrorResponse(ApiDescription description) + { + foreach (var apiResponse in _providerOptions.SupportedResponseTypes) + { + var wrapResultType = typeof(WrapResult); + var responseType = new ApiResponseType + { + Type = wrapResultType, + StatusCode = apiResponse.StatusCode, + ModelMetadata = _modelMetadataProvider.GetMetadataForType(wrapResultType) + }; + + foreach (var responseTypeMetadataProvider in _mvcOptions.OutputFormatters.OfType()) + { + var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes(null, responseType.Type); + if (formatterSupportedContentTypes == null) + { + continue; + } + + foreach (var formatterSupportedContentType in formatterSupportedContentTypes) + { + responseType.ApiResponseFormats.Add(new ApiResponseFormat + { + Formatter = (IOutputFormatter)responseTypeMetadataProvider, + MediaType = formatterSupportedContentType + }); + } + } + + description.SupportedResponseTypes.RemoveAll(x => x.StatusCode == responseType.StatusCode); + description.SupportedResponseTypes.AddIfNotContains( + x => x.StatusCode == responseType.StatusCode, + () => responseType); + } + } +} diff --git a/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/IWrapResultChecker.cs b/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/IWrapResultChecker.cs index 9e87aaf18..2ec59073a 100644 --- a/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/IWrapResultChecker.cs +++ b/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/IWrapResultChecker.cs @@ -1,9 +1,12 @@ -using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper { public interface IWrapResultChecker { + bool WrapOnAction(ActionDescriptor actionDescriptor); + bool WrapOnExecution(FilterContext context); bool WrapOnException(ExceptionContext context); diff --git a/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/WrapResultChecker.cs b/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/WrapResultChecker.cs index 0f4615848..8ebaf38a3 100644 --- a/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/WrapResultChecker.cs +++ b/aspnet-core/framework/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/WrapResultChecker.cs @@ -20,6 +20,16 @@ namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper Options = optionsMonitor.CurrentValue; } + public virtual bool WrapOnAction(ActionDescriptor actionDescriptor) + { + if (!Options.IsEnabled) + { + return false; + } + + return CheckForActionDescriptor(actionDescriptor); + } + public bool WrapOnException(ExceptionContext context) { if (!CheckForBase(context)) @@ -32,6 +42,11 @@ namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper public bool WrapOnException(PageHandlerExecutedContext context) { + if (!CheckForBase(context)) + { + return false; + } + return CheckForException(context.Exception); } @@ -40,7 +55,6 @@ namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper return CheckForBase(context); } - protected virtual bool CheckForBase(FilterContext context) { if (!Options.IsEnabled) @@ -48,49 +62,56 @@ namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper return false; } + // 用户传递不包装 if (context.HttpContext.Request.Headers.ContainsKey(AbpHttpWrapConsts.AbpDontWrapResult)) { return false; } - if (context.ActionDescriptor is ControllerActionDescriptor descriptor) + // 用户传递需要包装 + if (context.HttpContext.Request.Headers.ContainsKey(AbpHttpWrapConsts.AbpWrapResult)) { - if (!context.ActionDescriptor.HasObjectResult()) - { - return false; - } + return true; + } - //if (!context.HttpContext.Request.CanAccept(MimeTypes.Application.Json)) - //{ - // return false; - //} + if (!CheckForUrl(context)) + { + return false; + } + + return CheckForActionDescriptor(context.ActionDescriptor); + } - if (!CheckForUrl(context)) + protected virtual bool CheckForActionDescriptor(ActionDescriptor descriptor) + { + if (descriptor is ControllerActionDescriptor controllerActionDescriptor) + { + if (!descriptor.HasObjectResult()) { return false; } - if (!CheckForNamespace(descriptor)) + if (!CheckForNamespace(controllerActionDescriptor)) { return false; } - if (!CheckForController(descriptor)) + if (!CheckForController(controllerActionDescriptor)) { return false; } - if (!CheckForInterfaces(descriptor)) + if (!CheckForInterfaces(controllerActionDescriptor)) { return false; } - if (!CheckForMethod(descriptor)) + if (!CheckForMethod(controllerActionDescriptor)) { return false; } - if (!CheckForReturnType(descriptor)) + if (!CheckForReturnType(controllerActionDescriptor)) { return false; } diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs index 289713f67..71779aad2 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs +++ b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs @@ -722,7 +722,7 @@ public partial class MicroServiceApplicationsSingleModule } }; }); - services.AddAlwaysAllowAuthorization(); + if (isDevelopment) { services.AddAlwaysAllowAuthorization();