From c3f38382d0d5bc4b95cec63a2398a04cd0bfc082 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 9 Apr 2020 13:34:40 +0800 Subject: [PATCH 1/2] Introducing AbpRemoteServiceApiDescriptionProvider Resolve #3458 --- .../AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs | 23 +++++ .../AbpRemoteServiceApiDescriptionProvider.cs | 92 +++++++++++++++++++ ...oteServiceApiDescriptionProviderOptions.cs | 10 ++ 3 files changed, 125 insertions(+) create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProviderOptions.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index 21a675ddc4..9a6a224c48 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -11,15 +11,19 @@ using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation; using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.Extensions.Hosting; using Volo.Abp.ApiVersioning; +using Volo.Abp.AspNetCore.Mvc.ApiExploring; using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Json; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.VirtualFileSystem; using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; using Volo.Abp.Http.Modeling; using Volo.Abp.Localization; using Volo.Abp.Modularity; @@ -50,6 +54,25 @@ namespace Volo.Abp.AspNetCore.Mvc options.IgnoredInterfaces.AddIfNotContains(typeof(IActionFilter)); }); + Configure(options => + { + var statusCodes = new List + { + (int) HttpStatusCode.Forbidden, + (int) HttpStatusCode.Unauthorized, + (int) HttpStatusCode.BadRequest, + (int) HttpStatusCode.NotFound, + (int) HttpStatusCode.NotImplemented, + (int) HttpStatusCode.InternalServerError + }; + + options.SupportedResponseTypes.AddIfNotContains(statusCodes.Select(statusCode => new ApiResponseType + { + Type = typeof(RemoteServiceErrorResponse), + StatusCode = statusCode + })); + }); + context.Services.PostConfigure(options => { if (options.MinifyGeneratedScript == null) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs new file mode 100644 index 0000000000..1714d74e65 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Linq; +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 Volo.Abp.DependencyInjection; +using Volo.Abp.Reflection; + +namespace Volo.Abp.AspNetCore.Mvc.ApiExploring +{ + public class AbpRemoteServiceApiDescriptionProvider : IApiDescriptionProvider, ITransientDependency + { + private readonly IModelMetadataProvider _modelMetadataProvider; + private readonly MvcOptions _mvcOptions; + private readonly AbpRemoteServiceApiDescriptionProviderOptions _options; + + public AbpRemoteServiceApiDescriptionProvider( + IModelMetadataProvider modelMetadataProvider, + IOptions mvcOptionsAccessor, + IOptions optionsAccessor) + { + _modelMetadataProvider = modelMetadataProvider; + _mvcOptions = mvcOptionsAccessor.Value; + _options = optionsAccessor.Value; + } + + 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 apiResponseType in GetApiResponseTypes()) + { + foreach (var result in context.Results.Where(x => ShouldAddResponseTypes(x.ActionDescriptor))) + { + result.SupportedResponseTypes.AddIfNotContains(x => x.StatusCode == apiResponseType.StatusCode, () => apiResponseType); + } + } + } + + protected virtual IEnumerable GetApiResponseTypes() + { + foreach (var apiResponse in _options.SupportedResponseTypes) + { + apiResponse.ModelMetadata = _modelMetadataProvider.GetMetadataForType(apiResponse.Type); + + foreach (var responseTypeMetadataProvider in _mvcOptions.OutputFormatters.OfType()) + { + var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes(null, apiResponse.Type); + if (formatterSupportedContentTypes == null) + { + continue; + } + + foreach (var formatterSupportedContentType in formatterSupportedContentTypes) + { + apiResponse.ApiResponseFormats.Add(new ApiResponseFormat + { + Formatter = (IOutputFormatter) responseTypeMetadataProvider, + MediaType = formatterSupportedContentType + }); + } + } + } + + return _options.SupportedResponseTypes; + } + + protected virtual bool ShouldAddResponseTypes(ActionDescriptor actionDescriptor) + { + if (ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(actionDescriptor.GetMethodInfo()) != null || + ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(actionDescriptor.GetMethodInfo()) != null) + { + return false; + } + + var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(actionDescriptor.GetMethodInfo()); + return remoteServiceAttr != null && remoteServiceAttr.IsEnabled; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProviderOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProviderOptions.cs new file mode 100644 index 0000000000..74e631c53c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProviderOptions.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ApiExplorer; + +namespace Volo.Abp.AspNetCore.Mvc.ApiExploring +{ + public class AbpRemoteServiceApiDescriptionProviderOptions + { + public HashSet SupportedResponseTypes { get; set; } = new HashSet(); + } +} \ No newline at end of file From a04805a6077fa3150424c4439edcb009c655b371 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 11 Apr 2020 17:10:23 +0800 Subject: [PATCH 2/2] Filter ProducesResponseType. --- .../AbpRemoteServiceApiDescriptionProvider.cs | 22 ++++++++++--------- .../Volo/Abp/Reflection/ReflectionHelper.cs | 17 ++++++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs index 1714d74e65..ae09573d60 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs @@ -3,7 +3,6 @@ using System.Linq; 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; @@ -42,9 +41,18 @@ namespace Volo.Abp.AspNetCore.Mvc.ApiExploring { foreach (var apiResponseType in GetApiResponseTypes()) { - foreach (var result in context.Results.Where(x => ShouldAddResponseTypes(x.ActionDescriptor))) + foreach (var result in context.Results.Where(x => IsRemoteService(x.ActionDescriptor))) { - result.SupportedResponseTypes.AddIfNotContains(x => x.StatusCode == apiResponseType.StatusCode, () => apiResponseType); + var actionProducesResponseTypeAttributes = + ReflectionHelper.GetAttributesOfMemberOrDeclaringType( + result.ActionDescriptor.GetMethodInfo()); + if (actionProducesResponseTypeAttributes.Any(x => x.StatusCode == apiResponseType.StatusCode)) + { + continue; + } + + result.SupportedResponseTypes.AddIfNotContains(x => x.StatusCode == apiResponseType.StatusCode, + () => apiResponseType); } } } @@ -77,14 +85,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApiExploring return _options.SupportedResponseTypes; } - protected virtual bool ShouldAddResponseTypes(ActionDescriptor actionDescriptor) + protected virtual bool IsRemoteService(ActionDescriptor actionDescriptor) { - if (ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(actionDescriptor.GetMethodInfo()) != null || - ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(actionDescriptor.GetMethodInfo()) != null) - { - return false; - } - var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(actionDescriptor.GetMethodInfo()); return remoteServiceAttr != null && remoteServiceAttr.IsEnabled; } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs index 8d6e6a42cf..fd47d8b859 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs @@ -108,6 +108,23 @@ namespace Volo.Abp.Reflection ?? defaultValue; } + /// + /// Tries to gets attributes defined for a class member and it's declaring type including inherited attributes. + /// + /// Type of the attribute + /// MemberInfo + /// Inherit attribute from base classes + public static IEnumerable GetAttributesOfMemberOrDeclaringType(MemberInfo memberInfo, bool inherit = true) + where TAttribute : class + { + var customAttributes = memberInfo.GetCustomAttributes(true).OfType(); + var declaringTypeCustomAttributes = + memberInfo.DeclaringType?.GetTypeInfo().GetCustomAttributes(true).OfType(); + return declaringTypeCustomAttributes != null + ? customAttributes.Concat(declaringTypeCustomAttributes).Distinct() + : customAttributes; + } + /// /// Gets value of a property by it's full path from given object ///