diff --git a/CHANGELOG.md b/CHANGELOG.md index f1b8346..462ebcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ These are the changes to each version that has been released on the [nuget](https://www.nuget.org/packages/Unchase.Swashbuckle.AspNetCore.Extensions/). +## v2.2.0 `(2020-02-28)` + +- [x] Add feature: remove Paths and Definitions from OpenApi documentation for specific controller action without accepted roles + ## v2.1.6 `(2020-02-21)` - [x] Fix bug: replace `
` tag to `{Envirinment.NewLine}{Envirinment.NewLine}` for enum descriptions diff --git a/README.md b/README.md index 1ec84b0..7447528 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ public void ConfigureServices(IServiceCollection services) } ``` -2. **Hide Paths and Defenitions from OpenApi documentation** without accepted roles in OpenApi document: +2. **Hide Paths and Definitions from OpenApi documentation** without accepted roles: - In the _ConfigureServices_ method of _Startup.cs_, inside your `AddSwaggerGen` call, enable `HidePathsAndDefinitionsByRolesDocumentFilter` document filter: ```csharp @@ -127,6 +127,28 @@ public void ConfigureServices(IServiceCollection services) } ``` +- Since [v2.2.0](https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions/releases/tag/v2.2.0) you can hide Paths and Definitions from OpenApi documentation for specific controller action without accepted roles like this: + +```csharp +// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. +public void Configure(IApplicationBuilder app, IWebHostEnvironment env) +{ + //... + + // enable middleware to serve generated Swagger as a JSON endpoint. + app.UseSwagger(c => + { + c.PreSerializeFilters.Add((openApiDoc, httpRequest) => + { + // remove Paths and Components from OpenApi documentation for specific controller action without accepted roles + openApiDoc.RemovePathsAndComponentsWithoutAcceptedRolesFor(controller => nameof(controller.SomeAction), new List {"AcceptedRole"}); + }); + }); + + //... +} +``` + 3. **Append action count into the SwaggetTag's descriptions in OpenApi document**: - In the _ConfigureServices_ method of _Startup.cs_, inside your `AddSwaggerGen` call, enable `AppendActionCountToTagSummaryDocumentFilter` document filter: diff --git a/appveyor.yml b/appveyor.yml index 440bbf0..216a1b3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.1.{build} +version: 2.2.{build} pull_requests: do_not_increment_build_number: true skip_tags: true diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/EnumTypeExtensions.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/EnumTypeExtensions.cs index 76b3dc7..eb6b8dc 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/EnumTypeExtensions.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/EnumTypeExtensions.cs @@ -57,8 +57,8 @@ internal static string AddEnumValuesDescription(this OpenApiSchema schema, bool if (!schema.Extensions.ContainsKey("x-enumDescriptions")) continue; - var xenumDescriptions = (OpenApiArray)schema.Extensions["x-enumDescriptions"]; - if (xenumDescriptions?.Count == schema.Enum.Count) + var xEnumDescriptions = (OpenApiArray)schema.Extensions["x-enumDescriptions"]; + if (xEnumDescriptions?.Count == schema.Enum.Count) { var description = ((OpenApiString)((OpenApiArray)schema.Extensions["x-enumDescriptions"])[i]).Value; sb.Append($" ({description})"); diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/OpenApiDocumentExtensions.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/OpenApiDocumentExtensions.cs new file mode 100644 index 0000000..322b254 --- /dev/null +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/OpenApiDocumentExtensions.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.OpenApi.Models; +using Unchase.Swashbuckle.AspNetCore.Extensions.Factories; +using Unchase.Swashbuckle.AspNetCore.Extensions.Filters; + +namespace Unchase.Swashbuckle.AspNetCore.Extensions.Extensions +{ + /// + /// Extension methods for . + /// + public static class OpenApiDocumentExtensions + { + /// + /// Remove Paths and Components from OpenApi documentation without accepted roles. + /// + /// . + /// Action name selector. + /// Collection of accepted roles. + /// + /// Returns . + /// + public static OpenApiDocument RemovePathsAndComponentsWithoutAcceptedRolesFor(this OpenApiDocument openApiDoc, Func actionNameSelector, + IReadOnlyList acceptedRoles) where TController : class, new() + { + var actionDescriptor = ApiDescriptionFactory.Create(actionNameSelector, typeof(TController).GetCustomAttribute().Template)?.ActionDescriptor; + if (actionDescriptor != null) + { + var paths = new Dictionary + { + { ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)actionDescriptor).MethodInfo, actionDescriptor.AttributeRouteInfo.Template } + }; + + HidePathsAndDefinitionsByRolesDocumentFilter.RemovePathsAndComponents(openApiDoc, paths, openApiDoc.Components.Schemas, acceptedRoles); + } + + return openApiDoc; + } + } +} diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/SwaggerGenOptionsExtensions.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/SwaggerGenOptionsExtensions.cs index 338948c..8d8f4c3 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/SwaggerGenOptionsExtensions.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/SwaggerGenOptionsExtensions.cs @@ -6,6 +6,9 @@ namespace Unchase.Swashbuckle.AspNetCore.Extensions.Extensions { + /// + /// Extension methods for . + /// public static class SwaggerGenOptionsExtensions { #region Extensions diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Factories/ApiDescriptionFactory.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Factories/ApiDescriptionFactory.cs new file mode 100644 index 0000000..44cac4c --- /dev/null +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Factories/ApiDescriptionFactory.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Unchase.Swashbuckle.AspNetCore.Extensions.Factories +{ + /// + /// factory. + /// + internal static class ApiDescriptionFactory + { + /// + /// Create . + /// + /// Type of Controller. + /// Controller action name. + /// Group name. + /// Http method. + /// Relative path. + /// Collection of . + /// Collection of . + /// Collection of . + /// + /// Returns . + /// + internal static ApiDescription Create( + Type controllerType, + string actionName, + string groupName = null, + string httpMethod = null, + string relativePath = null, + IEnumerable parameterDescriptions = null, + IEnumerable supportedRequestFormats = null, + IEnumerable supportedResponseTypes = null) + { + var methodInfo = controllerType.GetMethod(actionName); + + if (methodInfo == null) + return null; + + var actionDescriptor = CreateActionDescriptor(methodInfo); + + var routAttr = controllerType.GetCustomAttributes().OfType().LastOrDefault(); + + if (string.IsNullOrWhiteSpace(actionDescriptor.AttributeRouteInfo.Template)) + return null; + //throw new InvalidOperationException($"HttpMethod attribute of \"{methodInfo.Name}\" action in \"{controllerType.Name}\" controller must have a template specified."); + + if (routAttr != null && !string.IsNullOrWhiteSpace(routAttr.Template)) + { + var template = routAttr.Template; + actionDescriptor.AttributeRouteInfo.Template = template + "/" + actionDescriptor.AttributeRouteInfo.Template; + } + + foreach (var routeValue in actionDescriptor.RouteValues) + { + actionDescriptor.AttributeRouteInfo.Template = + actionDescriptor.AttributeRouteInfo.Template.Replace($"[{routeValue.Key}]", routeValue.Value); + } + + httpMethod = httpMethod ?? methodInfo?.GetCustomAttributes().OfType().FirstOrDefault()?.HttpMethods?.FirstOrDefault(); + + var apiDescription = new ApiDescription + { + ActionDescriptor = actionDescriptor, + GroupName = groupName, + HttpMethod = httpMethod, + RelativePath = relativePath, + }; + + if (parameterDescriptions != null) + { + foreach (var parameter in parameterDescriptions) + { + // If the provided action has a matching parameter - use it to assign ParameterDescriptor & ModelMetadata + var controllerParameterDescriptor = actionDescriptor.Parameters + .OfType() + .FirstOrDefault(parameterDescriptor => parameterDescriptor.Name == parameter.Name); + + if (controllerParameterDescriptor != null) + { + parameter.ParameterDescriptor = controllerParameterDescriptor; + parameter.ModelMetadata = ModelMetadataFactory.CreateForParameter(controllerParameterDescriptor.ParameterInfo); + } + + apiDescription.ParameterDescriptions.Add(parameter); + } + } + + if (supportedRequestFormats != null) + { + foreach (var requestFormat in supportedRequestFormats) + { + apiDescription.SupportedRequestFormats.Add(requestFormat); + } + } + + if (supportedResponseTypes != null) + { + foreach (var responseType in supportedResponseTypes) + { + // If the provided action has a return value AND the response status is 2XX - use it to assign ModelMetadata + if (methodInfo?.ReturnType != null && responseType.StatusCode / 100 == 2) + { + responseType.ModelMetadata = ModelMetadataFactory.CreateForType(methodInfo.ReturnType); + } + + apiDescription.SupportedResponseTypes.Add(responseType); + } + } + + return apiDescription; + } + + /// + /// Create . + /// + /// Type of Controller. + /// Action name selector. + /// Group name. + /// Http method. + /// Relative path. + /// Collection of . + /// Collection of . + /// Collection of . + /// + /// Returns . + /// + internal static ApiDescription Create( + Func actionNameSelector, + string groupName = null, + string httpMethod = null, + string relativePath = null, + IEnumerable parameterDescriptions = null, + IEnumerable supportedRequestFormats = null, + IEnumerable supportedResponseTypes = null) + where TController : new() + { + return Create( + typeof(TController), + actionNameSelector(new TController()), + groupName, + httpMethod, + relativePath, + parameterDescriptions, + supportedRequestFormats, + supportedResponseTypes + ); + } + + private static ActionDescriptor CreateActionDescriptor(MethodInfo methodInfo) + { + var httpMethodAttribute = methodInfo.GetCustomAttribute(); + var attributeRouteInfo = (httpMethodAttribute != null) + ? new AttributeRouteInfo { Template = httpMethodAttribute.Template, Name = httpMethodAttribute.Name } + : null; + + var parameterDescriptors = methodInfo.GetParameters() + .Select(CreateParameterDescriptor) + .ToList(); + + var routeValues = new Dictionary + { + ["controller"] = methodInfo.DeclaringType?.Name.Replace("Controller", string.Empty) + }; + + return new ControllerActionDescriptor + { + AttributeRouteInfo = attributeRouteInfo, + ControllerTypeInfo = methodInfo.DeclaringType.GetTypeInfo(), + MethodInfo = methodInfo, + Parameters = parameterDescriptors, + RouteValues = routeValues + }; + } + + private static ParameterDescriptor CreateParameterDescriptor(ParameterInfo parameterInfo) + { + return new ControllerParameterDescriptor + { + Name = parameterInfo.Name, + ParameterInfo = parameterInfo, + ParameterType = parameterInfo.ParameterType, + }; + } + } +} diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Factories/ModelMetadataFactory.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Factories/ModelMetadataFactory.cs new file mode 100644 index 0000000..74e6493 --- /dev/null +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Factories/ModelMetadataFactory.cs @@ -0,0 +1,25 @@ +using System; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Unchase.Swashbuckle.AspNetCore.Extensions.Factories +{ + public static class ModelMetadataFactory + { + public static ModelMetadata CreateForType(Type type) + { + return new EmptyModelMetadataProvider().GetMetadataForType(type); + } + + public static ModelMetadata CreateForProperty(Type containingType, string propertyName) + { + return new EmptyModelMetadataProvider().GetMetadataForProperty(containingType, propertyName); + } + + public static ModelMetadata CreateForParameter(ParameterInfo parameter) + { + return new EmptyModelMetadataProvider().GetMetadataForParameter(parameter); + } + + } +} diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/ChangeResponseByHttpStatusCodeDocumentFilter.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/ChangeResponseByHttpStatusCodeDocumentFilter.cs index 4c61527..c873c6c 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/ChangeResponseByHttpStatusCodeDocumentFilter.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/ChangeResponseByHttpStatusCodeDocumentFilter.cs @@ -34,13 +34,13 @@ internal class ChangeResponseByHttpStatusCodeDocumentFilter : IDocumentFilter { #region Fileds - private int _httpStatusCode; + private readonly int _httpStatusCode; - private string _responseDescription; + private readonly string _responseDescription; - private ResponseExampleOptions _responseExampleOption; + private readonly ResponseExampleOptions _responseExampleOption; - private T _responseExample; + private readonly T _responseExample; #endregion diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/HidePathsAndDefinitionsByRolesDocumentFilter.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/HidePathsAndDefinitionsByRolesDocumentFilter.cs index 44166b8..6093c7a 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/HidePathsAndDefinitionsByRolesDocumentFilter.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/HidePathsAndDefinitionsByRolesDocumentFilter.cs @@ -4,11 +4,12 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using Microsoft.AspNetCore.Mvc.Controllers; namespace Unchase.Swashbuckle.AspNetCore.Extensions.Filters { /// - /// Filter for removing Paths and Defenitions from OpenApi documentation without accepted roles. + /// Filter for removing Paths and Components from OpenApi documentation without accepted roles. /// public class HidePathsAndDefinitionsByRolesDocumentFilter : IDocumentFilter { @@ -33,7 +34,9 @@ public HidePathsAndDefinitionsByRolesDocumentFilter(IEnumerable accepted #region Methods - private List GetRequiredDefinitions(DocumentFilterContext context, OpenApiReference reference) + #region GetRequiredDefinitions + + private static List GetRequiredDefinitions(IDictionary schemas, OpenApiReference reference) { var result = new List(); @@ -44,7 +47,7 @@ private List GetRequiredDefinitions(DocumentFilterContext context, OpenA result.Add(reference.Id); } - var responseSchema = context.SchemaRepository.Schemas[reference?.Id]; + var responseSchema = schemas[reference?.Id]; if (responseSchema != null) { if (responseSchema.Properties?.Count > 0) @@ -54,16 +57,16 @@ private List GetRequiredDefinitions(DocumentFilterContext context, OpenA if (schemaProperty.Value?.Reference?.Id != null) { result.Add(schemaProperty.Value?.Reference?.Id); - var responseSchemaPropertySchema = context.SchemaRepository.Schemas[schemaProperty.Value?.Reference?.Id]; + var responseSchemaPropertySchema = schemas[schemaProperty.Value?.Reference?.Id]; if (responseSchemaPropertySchema?.Reference != null) { - result.AddRange(GetRequiredDefinitions(context, responseSchemaPropertySchema.Reference)); + result.AddRange(GetRequiredDefinitions(schemas, responseSchemaPropertySchema.Reference)); if (responseSchemaPropertySchema.Items?.Reference?.Id != null) { - var itemsSchema = context.SchemaRepository.Schemas[responseSchemaPropertySchema.Items?.Reference?.Id]; + var itemsSchema = schemas[responseSchemaPropertySchema.Items?.Reference?.Id]; if (itemsSchema?.Reference?.Id != null) { - result.AddRange(GetRequiredDefinitions(context, itemsSchema.Reference)); + result.AddRange(GetRequiredDefinitions(schemas, itemsSchema.Reference)); } } } @@ -72,10 +75,10 @@ private List GetRequiredDefinitions(DocumentFilterContext context, OpenA if (schemaProperty.Value?.Items?.Reference?.Id != null) { result.Add(schemaProperty.Value?.Items?.Reference?.Id); - var itemsSchema = context.SchemaRepository.Schemas[schemaProperty.Value?.Items?.Reference?.Id]; + var itemsSchema = schemas[schemaProperty.Value?.Items?.Reference?.Id]; if (itemsSchema?.Reference?.Id != null) { - result.AddRange(GetRequiredDefinitions(context, itemsSchema.Reference)); + result.AddRange(GetRequiredDefinitions(schemas, itemsSchema.Reference)); } if (itemsSchema?.Properties?.Count > 0) @@ -93,7 +96,7 @@ private List GetRequiredDefinitions(DocumentFilterContext context, OpenA { if (itemsSchemaPropertyAllOf?.Reference?.Id != null) { - result.AddRange(GetRequiredDefinitions(context, itemsSchemaPropertyAllOf?.Reference)); + result.AddRange(GetRequiredDefinitions(schemas, itemsSchemaPropertyAllOf?.Reference)); } } } @@ -107,7 +110,7 @@ private List GetRequiredDefinitions(DocumentFilterContext context, OpenA { if (schemaPropertyAllOf?.Reference?.Id != null) { - result.AddRange(GetRequiredDefinitions(context, schemaPropertyAllOf.Reference)); + result.AddRange(GetRequiredDefinitions(schemas, schemaPropertyAllOf.Reference)); } } } @@ -117,10 +120,10 @@ private List GetRequiredDefinitions(DocumentFilterContext context, OpenA if (responseSchema?.Items?.Reference?.Id != null) { result.Add(responseSchema?.Items?.Reference?.Id); - var itemsSchema = context.SchemaRepository.Schemas[responseSchema?.Items?.Reference?.Id]; + var itemsSchema = schemas[responseSchema?.Items?.Reference?.Id]; if (itemsSchema?.Reference?.Id != null) { - result.AddRange(GetRequiredDefinitions(context, itemsSchema?.Reference)); + result.AddRange(GetRequiredDefinitions(schemas, itemsSchema?.Reference)); } } } @@ -131,36 +134,34 @@ private List GetRequiredDefinitions(DocumentFilterContext context, OpenA return result; } + #endregion + + #region RemovePathsAndComponents + /// - /// Apply filter. + /// Remove Paths and Components from OpenApi documentation without accepted roles. /// /// . - /// . - public void Apply(OpenApiDocument openApiDoc, DocumentFilterContext context) + /// Dictionary of openApi paths with keys. + /// Dictionary with openApi schemas with schame name keys. + /// Collection of accepted roles. + internal static void RemovePathsAndComponents(OpenApiDocument openApiDoc, IDictionary paths, IDictionary schemas, IReadOnlyList acceptedRoles) { - if (!this._acceptedRoles.Any()) - return; - var keysForRemove = new List(); - var refsForProbablyRemove = new List(); var requiredRefs = new List(); #region Remove Paths - foreach (var apiDescriptionActioDescription in context.ApiDescriptions.Select(ad => ad.ActionDescriptor)) + foreach (var path in paths) { - var methodInfo = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)apiDescriptionActioDescription).MethodInfo; - var authorizeAttributes = methodInfo.GetCustomAttributes(false); - if (authorizeAttributes != null) + var authorizeAttributes = path.Key.GetCustomAttributes(false); + foreach (var authorizeAttribute in authorizeAttributes) { - foreach (var authorizeAttribute in authorizeAttributes) + var authorizeAttributeRoles = authorizeAttribute?.Roles?.Split(',').Select(r => r.Trim()).ToList(); + var intersect = authorizeAttributeRoles?.Intersect(acceptedRoles); + if (intersect == null || !intersect.Any()) { - var authorizeAttributeRoles = authorizeAttribute?.Roles?.Split(',').Select(r => r.Trim()).ToList(); - var intersect = authorizeAttributeRoles?.Intersect(this._acceptedRoles); - if (intersect == null || !intersect.Any()) - { - keysForRemove.Add($"/{apiDescriptionActioDescription.AttributeRouteInfo.Template}"); - } + keysForRemove.Add($"/{path.Value}"); } } } @@ -172,16 +173,16 @@ public void Apply(OpenApiDocument openApiDoc, DocumentFilterContext context) #endregion - #region Remove components + #region Remove Components - foreach (var operation in openApiDoc.Paths?.Where(p => p.Value?.Operations != null).SelectMany(p => p.Value?.Operations)) + foreach (var operation in openApiDoc?.Paths?.Where(p => p.Value?.Operations != null)?.SelectMany(p => p.Value?.Operations)) { if (operation.Value?.Responses != null) foreach (var response in operation.Value?.Responses) { if (response.Value?.Reference?.Id != null && !requiredRefs.Contains(response.Value?.Reference?.Id)) { - requiredRefs.AddRange(GetRequiredDefinitions(context, response.Value?.Reference)); + requiredRefs.AddRange(GetRequiredDefinitions(schemas, response.Value?.Reference)); requiredRefs = requiredRefs.Distinct().ToList(); } @@ -189,7 +190,7 @@ public void Apply(OpenApiDocument openApiDoc, DocumentFilterContext context) { if (responseContentSchema?.Reference?.Id != null && !requiredRefs.Contains(responseContentSchema?.Reference?.Id)) { - requiredRefs.AddRange(GetRequiredDefinitions(context, responseContentSchema?.Reference)); + requiredRefs.AddRange(GetRequiredDefinitions(schemas, responseContentSchema?.Reference)); requiredRefs = requiredRefs.Distinct().ToList(); } } @@ -199,7 +200,7 @@ public void Apply(OpenApiDocument openApiDoc, DocumentFilterContext context) { if (parameter?.Schema?.Reference?.Id != null && !requiredRefs.Contains(parameter.Schema.Reference.Id)) { - requiredRefs.AddRange(GetRequiredDefinitions(context, parameter.Schema.Reference)); + requiredRefs.AddRange(GetRequiredDefinitions(schemas, parameter.Schema.Reference)); requiredRefs = requiredRefs.Distinct().ToList(); } @@ -207,7 +208,7 @@ public void Apply(OpenApiDocument openApiDoc, DocumentFilterContext context) { if (parameterContentSchema?.Reference?.Id != null && !requiredRefs.Contains(parameterContentSchema?.Reference?.Id)) { - requiredRefs.AddRange(GetRequiredDefinitions(context, parameterContentSchema?.Reference)); + requiredRefs.AddRange(GetRequiredDefinitions(schemas, parameterContentSchema?.Reference)); requiredRefs = requiredRefs.Distinct().ToList(); } } @@ -249,9 +250,9 @@ public void Apply(OpenApiDocument openApiDoc, DocumentFilterContext context) { if (tagsDict[tag] == 0) { - var seaggerTag = openApiDoc.Tags.FirstOrDefault(t => t.Name == tag); - if (seaggerTag != null) - openApiDoc.Tags.Remove(seaggerTag); + var swaggerTag = openApiDoc.Tags.FirstOrDefault(t => t.Name == tag); + if (swaggerTag != null) + openApiDoc.Tags.Remove(swaggerTag); } } @@ -259,5 +260,33 @@ public void Apply(OpenApiDocument openApiDoc, DocumentFilterContext context) } #endregion + + #region Apply + + /// + /// Apply filter. + /// + /// . + /// . + public void Apply(OpenApiDocument openApiDoc, DocumentFilterContext context) + { + if (!this._acceptedRoles.Any()) + return; + + var apiDescriptions = context.ApiDescriptions; + var schemas = context.SchemaRepository.Schemas; + + var paths = new Dictionary(); + foreach (var actionDescriptor in apiDescriptions.Select(ad => ad.ActionDescriptor)) + { + paths.Add(((ControllerActionDescriptor)actionDescriptor).MethodInfo, actionDescriptor.AttributeRouteInfo.Template); + } + + RemovePathsAndComponents(openApiDoc, paths, schemas, this._acceptedRoles.ToList()); + } + + #endregion + + #endregion } } diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XEnumNamesParameterFilter.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XEnumNamesParameterFilter.cs index c36662c..0550e24 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XEnumNamesParameterFilter.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XEnumNamesParameterFilter.cs @@ -12,7 +12,7 @@ public class XEnumNamesParameterFilter : IParameterFilter { #region Fields - private bool _includeXEnumDescriptions; + private readonly bool _includeXEnumDescriptions; #endregion diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XEnumNamesSchemaFilter.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XEnumNamesSchemaFilter.cs index b309349..b84f083 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XEnumNamesSchemaFilter.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XEnumNamesSchemaFilter.cs @@ -13,7 +13,7 @@ public class XEnumNamesSchemaFilter : ISchemaFilter { #region Fields - private bool _includeXEnumDescriptions; + private readonly bool _includeXEnumDescriptions; #endregion diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.csproj b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.csproj index d555fb5..4884ce9 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.csproj +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.csproj @@ -14,9 +14,9 @@ 7.3 https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions/blob/master/assets/icon.png?raw=true - 2.1.6 - 2.1.6.0 - 2.1.6.0 + 2.2.0 + 2.2.0.0 + 2.2.0.0 false Unchase.Swashbuckle.AspNetCore.Extensions.xml diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.xml b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.xml index 89da3b6..d154045 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.xml +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.xml @@ -4,6 +4,27 @@ Unchase.Swashbuckle.AspNetCore.Extensions + + + Extension methods for . + + + + + Remove Paths and Components from OpenApi documentation without accepted roles. + + . + Action name selector. + Collection of accepted roles. + + Returns . + + + + + Extension methods for . + + Change all responses by specific http status codes in OpenApi document. @@ -42,6 +63,43 @@ Returns . + + + factory. + + + + + Create . + + Type of Controller. + Controller action name. + Group name. + Http method. + Relative path. + Collection of . + Collection of . + Collection of . + + Returns . + + + + + Create . + + Type of Controller. + Action name selector. + Group name. + Http method. + Relative path. + Collection of . + Collection of . + Collection of . + + Returns . + + Options for response example. @@ -86,7 +144,7 @@ - Filter for removing Paths and Defenitions from OpenApi documentation without accepted roles. + Filter for removing Paths and Components from OpenApi documentation without accepted roles. @@ -95,6 +153,15 @@ + + + Remove Paths and Components from OpenApi documentation without accepted roles. + + . + Dictionary of openApi paths with keys. + Dictionary with openApi schemas with schame name keys. + Collection of accepted roles. + Apply filter. diff --git a/test/WebApi3.1-Swashbuckle/Controllers/HidedController.cs b/test/WebApi3.1-Swashbuckle/Controllers/HidedController.cs index 67d6b05..8bfa1b5 100644 --- a/test/WebApi3.1-Swashbuckle/Controllers/HidedController.cs +++ b/test/WebApi3.1-Swashbuckle/Controllers/HidedController.cs @@ -16,7 +16,7 @@ public class HidedController : ControllerBase /// /// Hided action. /// - [HttpGet()] + [HttpGet("HidedAction")] [Authorize(Roles = "NotAcceptedRole")] public IActionResult HidedAction() { diff --git a/test/WebApi3.1-Swashbuckle/Startup.cs b/test/WebApi3.1-Swashbuckle/Startup.cs index 680eb36..3e79c8d 100644 --- a/test/WebApi3.1-Swashbuckle/Startup.cs +++ b/test/WebApi3.1-Swashbuckle/Startup.cs @@ -11,6 +11,7 @@ using TodoApi.Models; using Unchase.Swashbuckle.AspNetCore.Extensions.Extensions; using Unchase.Swashbuckle.AspNetCore.Extensions.Filters; +using WebApi3._1_Swashbuckle.Controllers; using WebApi3._1_Swashbuckle.Models; namespace WebApi3._1_Swashbuckle @@ -47,7 +48,7 @@ public void ConfigureServices(IServiceCollection services) #region HidePathsAndDefinitionsByRolesDocumentFilter - // remove Paths and Defenitions from OpenApi documentation without accepted roles + // remove Paths and Components from OpenApi documentation without accepted roles options.DocumentFilter(new List { "AcceptedRole" }); #endregion @@ -105,8 +106,15 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseDeveloperExceptionPage(); } - // Enable middleware to serve generated Swagger as a JSON endpoint. - app.UseSwagger(); + // enable middleware to serve generated Swagger as a JSON endpoint. + app.UseSwagger(c => + { + c.PreSerializeFilters.Add((openApiDoc, httpRequest) => + { + // remove Paths and Components from OpenApi documentation for specific controller action without accepted roles + openApiDoc.RemovePathsAndComponentsWithoutAcceptedRolesFor(controller => nameof(controller.HidedAction), new List {"AcceptedRole"}); + }); + }); // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), // specifying the Swagger JSON endpoint.