From c9777021ff3b7bd7900fb259779df8d3cba4e217 Mon Sep 17 00:00:00 2001 From: domaindrivendev Date: Tue, 29 May 2018 22:17:35 -0700 Subject: [PATCH] Honor Required/BindRequired attributes on all parameters --- README.md | 5 +- .../SwaggerAttributesOperationFilter.cs | 24 ++-- .../SwaggerResponseAttributeFilter.cs | 11 +- .../Generator/ApiDescriptionExtensions.cs | 24 +++- .../ApiParameterDescriptionExtensions.cs | 40 ++++-- .../ControllerActionDescriptorExtensions.cs | 21 --- .../Generator/IOperationFilter.cs | 11 +- .../Generator/IParameterFilter.cs | 20 ++- .../Generator/JsonPropertyExtensions.cs | 2 +- .../Generator/SwaggerGenerator.cs | 58 ++++---- .../Generator/TypeExtensions.cs | 5 - .../SwaggerAttributesOperationFilterTests.cs | 25 ++-- .../SwaggerResponseAttributeFilterTests.cs | 12 +- .../XmlCommentsDocumentFilterTests.cs | 10 +- .../XmlCommentsOperationFilterTests.cs | 12 +- .../Generator/SchemaRegistryTests.cs | 4 +- .../Generator/SwaggerGeneratorTests.cs | 131 +++++++++--------- ...keApiDescriptionGroupCollectionProvider.cs | 44 +++--- .../{FakeActions.cs => FakeController.cs} | 5 +- .../TestFixtures/Fakes/FakeControllers.cs | 20 +-- .../Fakes/SwaggerAnnotatedController.cs | 9 ++ .../TestFixtures/Types/DataAnnotatedType.cs | 3 - .../Types/MetadataAnnotatedType.cs | 5 - .../Basic/Swagger/FormDataOperationFilter.cs | 2 +- test/WebSites/MultipleVersions/Startup.cs | 7 +- .../Controllers/ProductsController.cs | 5 +- .../SecurityRequirementsOperationFilter.cs | 2 +- 27 files changed, 265 insertions(+), 252 deletions(-) delete mode 100644 src/Swashbuckle.AspNetCore.SwaggerGen/Generator/ControllerActionDescriptorExtensions.cs rename test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/{FakeActions.cs => FakeController.cs} (97%) create mode 100644 test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/SwaggerAnnotatedController.cs diff --git a/README.md b/README.md index 1d0cd75f4..7b1c72f0a 100644 --- a/README.md +++ b/README.md @@ -479,7 +479,10 @@ When selecting actions for a given Swagger document, the generator invokes a _Do ```csharp c.DocInclusionPredicate((docName, apiDesc) => { - var versions = apiDesc.ControllerAttributes() + if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)) return false; + + var versions = methodInfo.DeclaringType + .GetCustomAttributes(true) .OfType() .SelectMany(attr => attr.Versions); diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/Annotations/SwaggerAttributesOperationFilter.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/Annotations/SwaggerAttributesOperationFilter.cs index 7dcd90fd6..1d0235a0b 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/Annotations/SwaggerAttributesOperationFilter.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/Annotations/SwaggerAttributesOperationFilter.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Reflection; +using System.Collections.Generic; using Swashbuckle.AspNetCore.Swagger; namespace Swashbuckle.AspNetCore.SwaggerGen @@ -9,16 +10,18 @@ public class SwaggerAttributesOperationFilter : IOperationFilter { public void Apply(Operation operation, OperationFilterContext context) { - if (context.ControllerActionDescriptor == null) return; + if (context.MethodInfo == null) return; - ApplyOperationAttributes(operation, context); - ApplyOperationFilterAttributes(operation, context); + var actionAttributes = context.MethodInfo.GetCustomAttributes(true); + var controllerAttributes = context.MethodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true); + + ApplyOperationAttributes(operation, actionAttributes); + ApplyOperationFilterAttributes(operation, actionAttributes, controllerAttributes, context); } - private static void ApplyOperationAttributes(Operation operation, OperationFilterContext context) + private static void ApplyOperationAttributes(Operation operation, IEnumerable actionAttributes) { - var swaggerOperationAttribute = context.ControllerActionDescriptor.MethodInfo - .GetCustomAttributes(true) + var swaggerOperationAttribute = actionAttributes .OfType() .FirstOrDefault(); @@ -34,10 +37,13 @@ private static void ApplyOperationAttributes(Operation operation, OperationFilte operation.Schemes = swaggerOperationAttribute.Schemes; } - public static void ApplyOperationFilterAttributes(Operation operation, OperationFilterContext context) + public static void ApplyOperationFilterAttributes( + Operation operation, + IEnumerable actionAttributes, + IEnumerable controllerAttributes, + OperationFilterContext context) { - var swaggerOperationFilterAttributes = context.ControllerActionDescriptor - .GetControllerAndActionAttributes(true) + var swaggerOperationFilterAttributes = actionAttributes.Union(controllerAttributes) .OfType(); foreach (var swaggerOperationFilterAttribute in swaggerOperationFilterAttributes) diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/Annotations/SwaggerResponseAttributeFilter.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/Annotations/SwaggerResponseAttributeFilter.cs index d7131f7dd..81cd9c85d 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/Annotations/SwaggerResponseAttributeFilter.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/Annotations/SwaggerResponseAttributeFilter.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; using Swashbuckle.AspNetCore.Swagger; namespace Swashbuckle.AspNetCore.SwaggerGen @@ -8,10 +9,10 @@ public class SwaggerResponseAttributeFilter : IOperationFilter { public void Apply(Operation operation, OperationFilterContext context) { - if (context.ControllerActionDescriptor == null) return; + if (context.MethodInfo == null) return; - var swaggerResponseAttributes = context.ControllerActionDescriptor - .GetControllerAndActionAttributes(true) + var swaggerResponseAttributes = context.MethodInfo.GetCustomAttributes(true) + .Union(context.MethodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true)) .OfType(); if (!swaggerResponseAttributes.Any()) diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/ApiDescriptionExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/ApiDescriptionExtensions.cs index e32d42803..adf23120d 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/ApiDescriptionExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/ApiDescriptionExtensions.cs @@ -10,7 +10,7 @@ namespace Swashbuckle.AspNetCore.SwaggerGen { public static class ApiDescriptionExtensions { - [Obsolete("Deprecated: Use OperationFilterContext.ControllerActionDescriptor")] + [Obsolete("Deprecated: Use TryGetMethodInfo")] public static IEnumerable ControllerAttributes(this ApiDescription apiDescription) { var controllerActionDescriptor = apiDescription.ActionDescriptor as ControllerActionDescriptor; @@ -19,7 +19,7 @@ public static IEnumerable ControllerAttributes(this ApiDescription apiDe : controllerActionDescriptor.ControllerTypeInfo.GetCustomAttributes(true); } - [Obsolete("Deprecated: Use OperationFilterContext.ControllerActionDescriptor")] + [Obsolete("Deprecated: Use TryGetMethodInfo")] public static IEnumerable ActionAttributes(this ApiDescription apiDescription) { var controllerActionDescriptor = apiDescription.ActionDescriptor as ControllerActionDescriptor; @@ -28,6 +28,15 @@ public static IEnumerable ActionAttributes(this ApiDescription apiDescri : controllerActionDescriptor.MethodInfo.GetCustomAttributes(true); } + public static bool TryGetMethodInfo(this ApiDescription apiDescription, out MethodInfo methodInfo) + { + var controllerActionDescriptor = apiDescription.ActionDescriptor as ControllerActionDescriptor; + + methodInfo = controllerActionDescriptor?.MethodInfo; + + return (methodInfo != null); + } + internal static string FriendlyId(this ApiDescription apiDescription) { var parts = (apiDescription.RelativePathSansQueryString() + "/" + apiDescription.HttpMethod.ToLower()) @@ -68,10 +77,13 @@ internal static IEnumerable SupportedResponseMediaTypes(this ApiDescript internal static bool IsObsolete(this ApiDescription apiDescription) { - var controllerActionDescriptor = apiDescription.ActionDescriptor as ControllerActionDescriptor; - return (controllerActionDescriptor != null) - ? controllerActionDescriptor.GetControllerAndActionAttributes(true).OfType().Any() - : false; + if (!apiDescription.TryGetMethodInfo(out MethodInfo methodInfo)) + return false; + + return methodInfo.GetCustomAttributes(true) + .Union(methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true)) + .Any(attr => attr.GetType() == typeof(ObsoleteAttribute)); } + } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/ApiParameterDescriptionExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/ApiParameterDescriptionExtensions.cs index fede2c4cb..e3e14ceb6 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/ApiParameterDescriptionExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/ApiParameterDescriptionExtensions.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Mvc.ApiExplorer; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Swashbuckle.AspNetCore.SwaggerGen @@ -15,18 +18,35 @@ public static bool IsPartOfCancellationToken(this ApiParameterDescription parame || name.StartsWith("WaitHandle."); } - public static bool IsRequired(this ApiParameterDescription parameterDescription) + internal static bool TryGetParameterInfo( + this ApiParameterDescription apiParameterDescription, + ApiDescription apiDescription, + out ParameterInfo parameterInfo) { - if (parameterDescription.RouteInfo?.IsOptional == false) - return true; + var controllerParameterDescriptor = apiDescription.ActionDescriptor.Parameters + .OfType() + .FirstOrDefault(descriptor => + { + return (apiParameterDescription.Name == descriptor.BindingInfo?.BinderModelName) + || (apiParameterDescription.Name == descriptor.Name); + }); - if (parameterDescription.ModelMetadata?.IsBindingRequired == true) - return true; + parameterInfo = controllerParameterDescriptor?.ParameterInfo; - if (parameterDescription.ModelMetadata?.IsRequired == true && parameterDescription.Type.IsAssignableToNull()) - return true; + return (parameterInfo != null); + } + + internal static bool TryGetPropertyInfo( + this ApiParameterDescription apiParameterDescription, + out PropertyInfo propertyInfo) + { + var modelMetadata = apiParameterDescription.ModelMetadata; + + propertyInfo = (modelMetadata?.ContainerType != null) + ? modelMetadata.ContainerType.GetProperty(modelMetadata.PropertyName) + : null; - return false; + return (propertyInfo != null); } } -} +} \ No newline at end of file diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/ControllerActionDescriptorExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/ControllerActionDescriptorExtensions.cs deleted file mode 100644 index 9800927c6..000000000 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/ControllerActionDescriptorExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.AspNetCore.Mvc.Controllers; - -namespace Swashbuckle.AspNetCore.SwaggerGen -{ - public static class ControllerActionDescriptorExtensions - { - public static IEnumerable GetControllerAndActionAttributes( - this ControllerActionDescriptor controllerActionDescriptor, - bool inherit) - { - var controllerAttributes = controllerActionDescriptor.ControllerTypeInfo.GetCustomAttributes(inherit); - var actionAttributes = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit); - - return actionAttributes.Union(controllerAttributes); - } - - } -} diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/IOperationFilter.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/IOperationFilter.cs index 61dfd7b2a..fbd70a535 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/IOperationFilter.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/IOperationFilter.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc.ApiExplorer; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Controllers; using Swashbuckle.AspNetCore.Swagger; @@ -13,18 +14,18 @@ public class OperationFilterContext { public OperationFilterContext( ApiDescription apiDescription, - ISchemaRegistry schemaRegistry) + ISchemaRegistry schemaRegistry, + MethodInfo methodInfo) { ApiDescription = apiDescription; - ControllerActionDescriptor = apiDescription.ActionDescriptor as ControllerActionDescriptor; SchemaRegistry = schemaRegistry; + MethodInfo = methodInfo; } public ApiDescription ApiDescription { get; private set; } - public ControllerActionDescriptor ControllerActionDescriptor { get; } - public ISchemaRegistry SchemaRegistry { get; private set; } + public MethodInfo MethodInfo { get; } } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/IParameterFilter.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/IParameterFilter.cs index 4e0c9fd72..5e88c9731 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/IParameterFilter.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/IParameterFilter.cs @@ -1,5 +1,7 @@ -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.Controllers; +using System; +using System.Reflection; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Swashbuckle.AspNetCore.Swagger; namespace Swashbuckle.AspNetCore.SwaggerGen @@ -13,18 +15,22 @@ public class ParameterFilterContext { public ParameterFilterContext( ApiParameterDescription apiParameterDescription, - ControllerParameterDescriptor controllerParameterDescriptor, - ISchemaRegistry schemaRegistry) + ISchemaRegistry schemaRegistry, + ParameterInfo parameterInfo, + PropertyInfo propertyInfo) { ApiParameterDescription = apiParameterDescription; - ControllerParameterDescriptor = controllerParameterDescriptor; SchemaRegistry = schemaRegistry; + ParameterInfo = parameterInfo; + PropertyInfo = propertyInfo; } public ApiParameterDescription ApiParameterDescription { get; } - public ControllerParameterDescriptor ControllerParameterDescriptor { get; } - public ISchemaRegistry SchemaRegistry { get; } + + public ParameterInfo ParameterInfo { get; } + + public PropertyInfo PropertyInfo { get; } } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/JsonPropertyExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/JsonPropertyExtensions.cs index 5a0e04701..a8c86e203 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/JsonPropertyExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/JsonPropertyExtensions.cs @@ -17,7 +17,7 @@ internal static bool IsRequired(this JsonProperty jsonProperty) if (jsonProperty.Required == Newtonsoft.Json.Required.Always) return true; - if (jsonProperty.HasAttribute() && jsonProperty.PropertyType.IsAssignableToNull()) + if (jsonProperty.HasAttribute()) return true; return false; diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/SwaggerGenerator.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/SwaggerGenerator.cs index b85ea601e..df5893c09 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/SwaggerGenerator.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/SwaggerGenerator.cs @@ -2,12 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using System.Reflection; +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Swashbuckle.AspNetCore.Swagger; -using System.Reflection; namespace Swashbuckle.AspNetCore.SwaggerGen { @@ -141,6 +140,18 @@ public class SwaggerGenerator : ISwaggerProvider ApiDescription apiDescription, ISchemaRegistry schemaRegistry) { + // Try to retrieve additional metadata that's not provided by ApiExplorer + MethodInfo methodInfo; + + var customAttributes = Enumerable.Empty(); + if (apiDescription.TryGetMethodInfo(out methodInfo)) + { + customAttributes = methodInfo.GetCustomAttributes(true) + .Union(methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true)); + } + + var isDeprecated = customAttributes.Any(attr => attr.GetType() == typeof(ObsoleteAttribute)); + var operation = new Operation { Tags = new[] { _settings.TagSelector(apiDescription) }, @@ -149,12 +160,13 @@ public class SwaggerGenerator : ISwaggerProvider Produces = apiDescription.SupportedResponseMediaTypes().ToList(), Parameters = CreateParameters(apiDescription, schemaRegistry), Responses = CreateResponses(apiDescription, schemaRegistry), - Deprecated = apiDescription.IsObsolete() ? true : (bool?)null + Deprecated = isDeprecated ? true : (bool?)null }; var filterContext = new OperationFilterContext( apiDescription, - schemaRegistry); + schemaRegistry, + methodInfo); foreach (var filter in _settings.OperationFilters) { @@ -198,10 +210,18 @@ public class SwaggerGenerator : ISwaggerProvider ? schemaRegistry.GetOrRegister(apiParameterDescription.Type) : null; - var isRequired = apiParameterDescription.IsRequired(); + // Try to retrieve additional metadata that's not provided by ApiExplorer + ParameterInfo parameterInfo = null; + PropertyInfo propertyInfo = null; + + var customAttributes = Enumerable.Empty(); + if (apiParameterDescription.TryGetParameterInfo(apiDescription, out parameterInfo)) + customAttributes = parameterInfo.GetCustomAttributes(true); + else if (apiParameterDescription.TryGetPropertyInfo(out propertyInfo)) + customAttributes = propertyInfo.GetCustomAttributes(true); - var controllerParameterDescriptor = GetControllerParameterDescriptorOrNull( - apiDescription, apiParameterDescription); + var isRequired = customAttributes.Any(attr => + new[] { typeof(RequiredAttribute), typeof(BindRequiredAttribute) }.Contains(attr.GetType())); var parameter = (location == "body") ? new BodyParameter { Name = name, Schema = schema, Required = isRequired } @@ -209,8 +229,9 @@ public class SwaggerGenerator : ISwaggerProvider var filterContext = new ParameterFilterContext( apiParameterDescription, - controllerParameterDescriptor, - schemaRegistry); + schemaRegistry, + parameterInfo, + propertyInfo); foreach (var filter in _settings.ParameterFilters) { @@ -283,23 +304,6 @@ private Response CreateResponse(ApiResponseType apiResponseType, ISchemaRegistry }; } - private ControllerParameterDescriptor GetControllerParameterDescriptorOrNull( - ApiDescription apiDescription, - ApiParameterDescription apiParameterDescription) - { - if (apiParameterDescription.ModelMetadata?.MetadataKind == ModelMetadataKind.Property) - return null; - - var parameterDescriptor = apiDescription.ActionDescriptor.Parameters - .FirstOrDefault(paramDescriptor => - { - return (apiParameterDescription.Name == paramDescriptor.BindingInfo?.BinderModelName) - || (apiParameterDescription.Name == paramDescriptor.Name); - }); - - return parameterDescriptor as ControllerParameterDescriptor; - } - private static Dictionary ParameterLocationMap = new Dictionary { { BindingSource.Form, "formData" }, diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/TypeExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/TypeExtensions.cs index 5bba63340..8bb78b51b 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/TypeExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/TypeExtensions.cs @@ -38,11 +38,6 @@ internal static bool IsFSharpOption(this Type type) return type.FullNameSansTypeArguments() == "Microsoft.FSharp.Core.FSharpOption`1"; } - internal static bool IsAssignableToNull(this Type type) - { - return !type.GetTypeInfo().IsValueType || type.IsNullable(); - } - private static string FullNameSansTypeArguments(this Type type) { if (string.IsNullOrEmpty(type.FullName)) return string.Empty; diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/SwaggerAttributesOperationFilterTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/SwaggerAttributesOperationFilterTests.cs index 1e695dbc4..8f0310ed1 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/SwaggerAttributesOperationFilterTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/SwaggerAttributesOperationFilterTests.cs @@ -1,8 +1,9 @@ using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Controllers; using Newtonsoft.Json; using Xunit; using Swashbuckle.AspNetCore.Swagger; -using Microsoft.AspNetCore.Mvc.Controllers; namespace Swashbuckle.AspNetCore.SwaggerGen.Test { @@ -15,7 +16,8 @@ public void Apply_AssignsProperties_FromActionAttribute() { OperationId = "foobar" }; - var filterContext = FilterContextFor(nameof(FakeActions.AnnotatedWithSwaggerOperation)); + var filterContext = FilterContextFor( + nameof(FakeController.AnnotatedWithSwaggerOperation)); Subject().Apply(operation, filterContext); @@ -31,10 +33,8 @@ public void Apply_DelegatesToSpecifiedFilter_IfControllerAnnotatedWithFilterAttr { OperationId = "foobar" }; - var filterContext = FilterContextFor( - nameof(FakeActions.ReturnsActionResult), - nameof(FakeControllers.AnnotatedWithSwaggerOperationFilter) - ); + var filterContext = FilterContextFor( + nameof(SwaggerAnnotatedController.ReturnsVoid)); Subject().Apply(operation, filterContext); @@ -48,26 +48,27 @@ public void Apply_DelegatesToSpecifiedFilter_IfActionAnnotatedWithFilterAttribut { OperationId = "foobar" }; - var filterContext = FilterContextFor(nameof(FakeActions.AnnotatedWithSwaggerOperationFilter)); + var filterContext = FilterContextFor( + nameof(FakeController.AnnotatedWithSwaggerOperationFilter)); Subject().Apply(operation, filterContext); Assert.NotEmpty(operation.Extensions); } - private OperationFilterContext FilterContextFor( - string actionFixtureName, - string controllerFixtureName = "NotAnnotated") + private OperationFilterContext FilterContextFor(string actionName) { var fakeProvider = new FakeApiDescriptionGroupCollectionProvider(); var apiDescription = fakeProvider - .Add("GET", "collection", actionFixtureName, controllerFixtureName) + .Add("GET", "collection", actionName, typeof(TController)) .ApiDescriptionGroups.Items.First() .Items.First(); + return new OperationFilterContext( apiDescription, - new SchemaRegistry(new JsonSerializerSettings())); + new SchemaRegistry(new JsonSerializerSettings()), + (apiDescription.ActionDescriptor as ControllerActionDescriptor).MethodInfo); } private SwaggerAttributesOperationFilter Subject() diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/SwaggerResponseAttributeFilterTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/SwaggerResponseAttributeFilterTests.cs index 616d17072..7217a6788 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/SwaggerResponseAttributeFilterTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/SwaggerResponseAttributeFilterTests.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Reflection; using Microsoft.AspNetCore.Mvc.Controllers; using Newtonsoft.Json; using Xunit; @@ -15,7 +16,7 @@ public void Apply_SetsResponses_FromAttributes() { OperationId = "foobar" }; - var filterContext = this.FilterContextFor(nameof(FakeActions.AnnotatedWithSwaggerResponseAttributes)); + var filterContext = FilterContextFor(nameof(FakeController.AnnotatedWithSwaggerResponseAttributes)); this.Subject().Apply(operation, filterContext); @@ -29,19 +30,18 @@ public void Apply_SetsResponses_FromAttributes() Assert.NotNull(response2.Schema); } - private OperationFilterContext FilterContextFor( - string actionFixtureName, - string controllerFixtureName = "NotAnnotated") + private OperationFilterContext FilterContextFor(string actionFixtureName) { var fakeProvider = new FakeApiDescriptionGroupCollectionProvider(); var apiDescription = fakeProvider - .Add("GET", "collection", actionFixtureName, controllerFixtureName) + .Add("GET", "collection", actionFixtureName) .ApiDescriptionGroups.Items.First() .Items.First(); return new OperationFilterContext( apiDescription, - new SchemaRegistry(new JsonSerializerSettings())); + new SchemaRegistry(new JsonSerializerSettings()), + (apiDescription.ActionDescriptor as ControllerActionDescriptor).MethodInfo); } private SwaggerResponseAttributeFilter Subject() diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/XmlCommentsDocumentFilterTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/XmlCommentsDocumentFilterTests.cs index 7015a2f3b..586ea001d 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/XmlCommentsDocumentFilterTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/XmlCommentsDocumentFilterTests.cs @@ -23,16 +23,16 @@ public void Apply_SetsTagDescription_FromControllerSummaryTags() { ActionDescriptor = new ControllerActionDescriptor { - ControllerTypeInfo = typeof(FakeControllers.AnnotatedWithXml).GetTypeInfo(), - ControllerName = nameof(FakeControllers.AnnotatedWithXml) + ControllerTypeInfo = typeof(XmlAnnotatedController).GetTypeInfo(), + ControllerName = nameof(XmlAnnotatedController) } }, new ApiDescription { ActionDescriptor = new ControllerActionDescriptor { - ControllerTypeInfo = typeof(FakeControllers.AnnotatedWithXml).GetTypeInfo(), - ControllerName = nameof(FakeControllers.AnnotatedWithXml) + ControllerTypeInfo = typeof(XmlAnnotatedController).GetTypeInfo(), + ControllerName = nameof(XmlAnnotatedController) } } }, @@ -41,7 +41,7 @@ public void Apply_SetsTagDescription_FromControllerSummaryTags() Subject().Apply(document, filterContext); Assert.Equal(1, document.Tags.Count); - Assert.Equal("summary for AnnotatedWithXml", document.Tags[0].Description); + Assert.Equal("summary for XmlAnnotatedController", document.Tags[0].Description); } private XmlCommentsDocumentFilter Subject() diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/XmlCommentsOperationFilterTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/XmlCommentsOperationFilterTests.cs index f14cedb39..adb428917 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/XmlCommentsOperationFilterTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Annotations/XmlCommentsOperationFilterTests.cs @@ -17,7 +17,7 @@ public void Apply_SetsSummaryAndDescription_FromSummaryAndRemarksTags() { Responses = new Dictionary() }; - var filterContext = FilterContextFor(nameof(FakeActions.AnnotatedWithXml)); + var filterContext = FilterContextFor(nameof(FakeController.AnnotatedWithXml)); Subject().Apply(operation, filterContext); @@ -38,7 +38,7 @@ public void Apply_SetsParameterDescriptions_FromParamTags() }, Responses = new Dictionary() }; - var filterContext = FilterContextFor(nameof(FakeActions.AnnotatedWithXml)); + var filterContext = FilterContextFor(nameof(FakeController.AnnotatedWithXml)); Subject().Apply(operation, filterContext); @@ -55,7 +55,7 @@ public void Apply_SetsParameterDescription_FromSummaryTagsOfParameterBoundProper Parameters = new List() { new NonBodyParameter { Name = "Property" } }, Responses = new Dictionary() }; - var filterContext = FilterContextFor(nameof(FakeActions.AcceptsXmlAnnotatedTypeFromQuery)); + var filterContext = FilterContextFor(nameof(FakeController.AcceptsXmlAnnotatedTypeFromQuery)); Subject().Apply(operation, filterContext); @@ -73,7 +73,7 @@ public void Apply_OverwritesResponseDescriptionFromResponseTag_IfResponsePresent { "400", new Response { Description = "Client Error", Schema = new Schema { Ref = "#/definitions/bar" } } } } }; - var filterContext = FilterContextFor(nameof(FakeActions.AnnotatedWithXml)); + var filterContext = FilterContextFor(nameof(FakeController.AnnotatedWithXml)); Subject().Apply(operation, filterContext); @@ -93,7 +93,7 @@ public void Apply_AddsResponseWithDescriptionFromResponseTag_IfResponseNotPresen { "200", new Response { Description = "Success", Schema = new Schema { Ref = "#/definitions/foo" } } }, } }; - var filterContext = FilterContextFor(nameof(FakeActions.AnnotatedWithXml)); + var filterContext = FilterContextFor(nameof(FakeController.AnnotatedWithXml)); Subject().Apply(operation, filterContext); @@ -109,7 +109,7 @@ private OperationFilterContext FilterContextFor(string actionFixtureName) .ApiDescriptionGroups.Items.First() .Items.First(); - return new OperationFilterContext(apiDescription, null); + return new OperationFilterContext(apiDescription, null, null); } private XmlCommentsOperationFilter Subject() diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Generator/SchemaRegistryTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Generator/SchemaRegistryTests.cs index d65a27d18..da65b719a 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Generator/SchemaRegistryTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Generator/SchemaRegistryTests.cs @@ -245,7 +245,7 @@ public void GetOrRegister_HonorsDataAttributes() Assert.Equal(10, schema.Properties["StringWithStringLength"].MaxLength); Assert.Equal(1, schema.Properties["StringWithMinMaxLength"].MinLength); Assert.Equal(3, schema.Properties["StringWithMinMaxLength"].MaxLength); - Assert.Equal(new[] { "StringWithRequired", "NullableIntWithRequired" }, schema.Required.ToArray()); + Assert.Equal(new[] { "StringWithRequired", "IntWithRequired" }, schema.Required.ToArray()); Assert.Equal("date", schema.Properties["StringWithDataTypeDate"].Format); Assert.Equal("date-time", schema.Properties["StringWithDataTypeDateTime"].Format); Assert.Equal("password", schema.Properties["StringWithDataTypePassword"].Format); @@ -262,7 +262,7 @@ public void GetOrRegister_HonorsDataAttributes_ViaModelMetadataType() Assert.Equal(1, schema.Properties["IntWithRange"].Minimum); Assert.Equal(12, schema.Properties["IntWithRange"].Maximum); Assert.Equal("^[3-6]?\\d{12,15}$", schema.Properties["StringWithRegularExpression"].Pattern); - Assert.Equal(new[] { "StringWithRequired", "NullableIntWithRequired" }, schema.Required.ToArray()); + Assert.Equal(new[] { "StringWithRequired", "IntWithRequired" }, schema.Required.ToArray()); } [Fact] diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Generator/SwaggerGeneratorTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Generator/SwaggerGeneratorTests.cs index 214daa8e0..35f285007 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Generator/SwaggerGeneratorTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Generator/SwaggerGeneratorTests.cs @@ -26,8 +26,8 @@ public void GetSwagger_GeneratesOneOrMoreDocuments_AsSpecifiedBySettings() var subject = Subject( setupApis: apis => { - apis.Add("GET", "v1/collection", nameof(FakeActions.ReturnsEnumerable)); - apis.Add("GET", "v2/collection", nameof(FakeActions.ReturnsEnumerable)); + apis.Add("GET", "v1/collection", nameof(FakeController.ReturnsEnumerable)); + apis.Add("GET", "v2/collection", nameof(FakeController.ReturnsEnumerable)); }, configure: c => { @@ -50,11 +50,11 @@ public void GetSwagger_GeneratesOneOrMoreDocuments_AsSpecifiedBySettings() public void GetSwagger_GeneratesPathItem_PerRelativePathSansQueryString() { var subject = Subject(setupApis: apis => apis - .Add("GET", "collection1", nameof(FakeActions.ReturnsEnumerable)) - .Add("GET", "collection1/{id}", nameof(FakeActions.ReturnsComplexType)) - .Add("GET", "collection2", nameof(FakeActions.AcceptsStringFromQuery)) - .Add("PUT", "collection2", nameof(FakeActions.ReturnsVoid)) - .Add("GET", "collection2/{id}", nameof(FakeActions.ReturnsComplexType)) + .Add("GET", "collection1", nameof(FakeController.ReturnsEnumerable)) + .Add("GET", "collection1/{id}", nameof(FakeController.ReturnsComplexType)) + .Add("GET", "collection2", nameof(FakeController.AcceptsStringFromQuery)) + .Add("PUT", "collection2", nameof(FakeController.ReturnsVoid)) + .Add("GET", "collection2/{id}", nameof(FakeController.ReturnsComplexType)) ); var swagger = subject.GetSwagger("v1"); @@ -73,11 +73,11 @@ public void GetSwagger_GeneratesPathItem_PerRelativePathSansQueryString() public void GetSwagger_GeneratesOperation_PerHttpMethodPerRelativePathSansQueryString() { var subject = Subject(setupApis: apis => apis - .Add("GET", "collection", nameof(FakeActions.ReturnsEnumerable)) - .Add("PUT", "collection/{id}", nameof(FakeActions.AcceptsComplexTypeFromBody)) - .Add("POST", "collection", nameof(FakeActions.AcceptsComplexTypeFromBody)) - .Add("DELETE", "collection/{id}", nameof(FakeActions.ReturnsVoid)) - .Add("PATCH", "collection/{id}", nameof(FakeActions.AcceptsComplexTypeFromBody)) + .Add("GET", "collection", nameof(FakeController.ReturnsEnumerable)) + .Add("PUT", "collection/{id}", nameof(FakeController.AcceptsComplexTypeFromBody)) + .Add("POST", "collection", nameof(FakeController.AcceptsComplexTypeFromBody)) + .Add("DELETE", "collection/{id}", nameof(FakeController.ReturnsVoid)) + .Add("PATCH", "collection/{id}", nameof(FakeController.AcceptsComplexTypeFromBody)) // TODO: OPTIONS & HEAD ); @@ -125,7 +125,7 @@ string expectedOperationId ) { var subject = Subject(setupApis: apis => apis - .Add("GET", routeTemplate, nameof(FakeActions.AcceptsNothing))); + .Add("GET", routeTemplate, nameof(FakeController.AcceptsNothing))); var swagger = subject.GetSwagger("v1"); @@ -133,11 +133,11 @@ string expectedOperationId } [Theory] - [InlineData("collection/{param}", nameof(FakeActions.AcceptsStringFromRoute), "path")] - [InlineData("collection", nameof(FakeActions.AcceptsStringFromQuery), "query")] - [InlineData("collection", nameof(FakeActions.AcceptsStringFromHeader), "header")] - [InlineData("collection", nameof(FakeActions.AcceptsStringFromForm), "formData")] - [InlineData("collection", nameof(FakeActions.AcceptsStringFromQuery), "query")] + [InlineData("collection/{param}", nameof(FakeController.AcceptsStringFromRoute), "path")] + [InlineData("collection", nameof(FakeController.AcceptsStringFromQuery), "query")] + [InlineData("collection", nameof(FakeController.AcceptsStringFromHeader), "header")] + [InlineData("collection", nameof(FakeController.AcceptsStringFromForm), "formData")] + [InlineData("collection", nameof(FakeController.AcceptsStringFromQuery), "query")] public void GetSwagger_GeneratesNonBodyParameters_ForPathQueryHeaderOrFormBoundParams( string routeTemplate, string actionFixtureName, @@ -160,7 +160,7 @@ string expectedOperationId public void GetSwagger_SetsCollectionFormatMulti_ForQueryOrHeaderBoundArrayParams() { var subject = Subject(setupApis: apis => apis - .Add("GET", "resource", nameof(FakeActions.AcceptsArrayFromQuery))); + .Add("GET", "resource", nameof(FakeController.AcceptsArrayFromQuery))); var swagger = subject.GetSwagger("v1"); @@ -172,7 +172,7 @@ public void GetSwagger_SetsCollectionFormatMulti_ForQueryOrHeaderBoundArrayParam public void GetSwagger_GeneratesBodyParams_ForBodyBoundParams() { var subject = Subject(setupApis: apis => apis - .Add("POST", "collection", nameof(FakeActions.AcceptsComplexTypeFromBody))); + .Add("POST", "collection", nameof(FakeController.AcceptsComplexTypeFromBody))); var swagger = subject.GetSwagger("v1"); @@ -190,8 +190,8 @@ public void GetSwagger_GeneratesBodyParams_ForBodyBoundParams() public void GetSwagger_GeneratesQueryParams_ForAllUnboundParams() { var subject = Subject(setupApis: apis => apis - .Add("GET", "collection", nameof(FakeActions.AcceptsString)) - .Add("POST", "collection", nameof(FakeActions.AcceptsComplexType))); + .Add("GET", "collection", nameof(FakeController.AcceptsString)) + .Add("POST", "collection", nameof(FakeController.AcceptsComplexType))); var swagger = subject.GetSwagger("v1"); @@ -208,7 +208,7 @@ public void GetSwagger_GeneratesQueryParams_ForAllUnboundParams() public void GetSwagger_SetsParameterRequired_ForRequiredAndOptionalRouteParams(string routeTemplate) { var subject = Subject(setupApis: apis => apis - .Add("GET", routeTemplate, nameof(FakeActions.AcceptsStringFromRoute))); + .Add("GET", routeTemplate, nameof(FakeController.AcceptsStringFromRoute))); var swagger = subject.GetSwagger("v1"); @@ -217,21 +217,16 @@ public void GetSwagger_SetsParameterRequired_ForRequiredAndOptionalRouteParams(s } [Theory] - [InlineData(nameof(FakeActions.AcceptsModelBoundParams), "stringWithNoAttributes", false)] - //[InlineData(nameof(FakeActions.AcceptsModelBoundParams), "stringWithBindRequired", true)] - //[InlineData(nameof(FakeActions.AcceptsModelBoundParams), "intWithBindRequired", true)] - [InlineData(nameof(FakeActions.AcceptsDataAnnotatedParams), "stringWithNoAttributes", false)] - [InlineData(nameof(FakeActions.AcceptsDataAnnotatedParams), "stringWithRequired", false)] - [InlineData(nameof(FakeActions.AcceptsDataAnnotatedParams), "intWithRequired", false)] - [InlineData(nameof(FakeActions.AcceptsDataAnnotatedParams), "nullableIntWithRequired", false)] - [InlineData(nameof(FakeActions.AcceptsModelBoundType), "StringWithNoAttributes", false)] - [InlineData(nameof(FakeActions.AcceptsModelBoundType), "StringWithBindRequired", true)] - [InlineData(nameof(FakeActions.AcceptsModelBoundType), "IntWithBindRequired", true)] - [InlineData(nameof(FakeActions.AcceptsDataAnnotatedType), "StringWithNoAttributes", false)] - [InlineData(nameof(FakeActions.AcceptsDataAnnotatedType), "StringWithRequired", true)] - [InlineData(nameof(FakeActions.AcceptsDataAnnotatedType), "IntWithRequired", false)] - [InlineData(nameof(FakeActions.AcceptsDataAnnotatedType), "NullableIntWithRequired", true)] - public void GetSwagger_SetsParameterRequired_BasedOnModelBindingAndDataValidationBehavior( + [InlineData(nameof(FakeController.AcceptsDataAnnotatedParams), "stringWithNoAttributes", false)] + [InlineData(nameof(FakeController.AcceptsDataAnnotatedParams), "stringWithRequired", true)] + [InlineData(nameof(FakeController.AcceptsDataAnnotatedParams), "intWithRequired", true)] + [InlineData(nameof(FakeController.AcceptsDataAnnotatedType), "StringWithNoAttributes", false)] + [InlineData(nameof(FakeController.AcceptsDataAnnotatedType), "StringWithRequired", true)] + [InlineData(nameof(FakeController.AcceptsDataAnnotatedType), "IntWithRequired", true)] + [InlineData(nameof(FakeController.AcceptsModelBoundType), "StringWithNoAttributes", false)] + [InlineData(nameof(FakeController.AcceptsModelBoundType), "StringWithBindRequired", true)] + [InlineData(nameof(FakeController.AcceptsModelBoundType), "IntWithBindRequired", true)] + public void GetSwagger_SetsParameterRequired_ForParametersWithRequiredOrBindRequiredAttribute( string actionFixtureName, string parameterName, bool expectedRequired) @@ -248,7 +243,7 @@ public void GetSwagger_SetsParameterRequired_ForRequiredAndOptionalRouteParams(s public void GetSwagger_SetsParameterTypeString_ForUnboundRouteParams() { var subject = Subject(setupApis: apis => apis - .Add("GET", "collection/{param}", nameof(FakeActions.AcceptsNothing))); + .Add("GET", "collection/{param}", nameof(FakeController.AcceptsNothing))); var swagger = subject.GetSwagger("v1"); @@ -264,7 +259,7 @@ public void GetSwagger_SetsParameterTypeString_ForUnboundRouteParams() public void GetSwagger_IgnoresParameters_IfPartOfCancellationToken() { var subject = Subject(setupApis: apis => apis - .Add("GET", "collection", nameof(FakeActions.AcceptsCancellationToken))); + .Add("GET", "collection", nameof(FakeController.AcceptsCancellationToken))); var swagger = subject.GetSwagger("v1"); @@ -276,7 +271,7 @@ public void GetSwagger_IgnoresParameters_IfPartOfCancellationToken() public void GetSwagger_IgnoresParameters_IfDecoratedWithBindNever() { var subject = Subject(setupApis: apis => apis - .Add("GET", "collection", nameof(FakeActions.AcceptsModelBoundType))); + .Add("GET", "collection", nameof(FakeController.AcceptsModelBoundType))); var swagger = subject.GetSwagger("v1"); @@ -290,7 +285,7 @@ public void GetSwagger_IgnoresParameters_IfDecoratedWithBindNever() public void GetSwagger_DescribesParametersInCamelCase_IfSpecifiedBySettings() { var subject = Subject( - setupApis: apis => apis.Add("GET", "collection", nameof(FakeActions.AcceptsModelBoundType)), + setupApis: apis => apis.Add("GET", "collection", nameof(FakeController.AcceptsModelBoundType)), configure: c => c.DescribeAllParametersInCamelCase = true ); @@ -304,11 +299,11 @@ public void GetSwagger_DescribesParametersInCamelCase_IfSpecifiedBySettings() } [Theory] - [InlineData(nameof(FakeActions.ReturnsVoid), "200", "Success", false)] - [InlineData(nameof(FakeActions.ReturnsEnumerable), "200", "Success", true)] - [InlineData(nameof(FakeActions.ReturnsComplexType), "200", "Success", true)] - [InlineData(nameof(FakeActions.ReturnsJObject), "200", "Success", true)] - [InlineData(nameof(FakeActions.ReturnsActionResult), "200", "Success", false)] + [InlineData(nameof(FakeController.ReturnsVoid), "200", "Success", false)] + [InlineData(nameof(FakeController.ReturnsEnumerable), "200", "Success", true)] + [InlineData(nameof(FakeController.ReturnsComplexType), "200", "Success", true)] + [InlineData(nameof(FakeController.ReturnsJObject), "200", "Success", true)] + [InlineData(nameof(FakeController.ReturnsActionResult), "200", "Success", false)] public void GetSwagger_GeneratesResponsesFromReturnTypes_IfResponseTypeAttributesNotPresent( string actionFixtureName, string expectedStatusCode, @@ -334,7 +329,7 @@ public void GetSwagger_DescribesParametersInCamelCase_IfSpecifiedBySettings() public void GetSwagger_GeneratesResponsesFromResponseTypeAttributes_IfResponseTypeAttributesPresent() { var subject = Subject(setupApis: apis => - apis.Add("GET", "collection", nameof(FakeActions.AnnotatedWithResponseTypeAttributes))); + apis.Add("GET", "collection", nameof(FakeController.AnnotatedWithResponseTypeAttributes))); var swagger = subject.GetSwagger("v1"); @@ -352,7 +347,7 @@ public void GetSwagger_GeneratesResponsesFromResponseTypeAttributes_IfResponseTy public void GetSwagger_GeneratesResponsesFromSwaggerResponseAttributes_IfResponseAttributesPresent() { var subject = Subject(setupApis: apis => - apis.Add("GET", "collection", nameof(FakeActions.AnnotatedWithSwaggerResponseAttributes))); + apis.Add("GET", "collection", nameof(FakeController.AnnotatedWithSwaggerResponseAttributes))); var swagger = subject.GetSwagger("v1"); @@ -370,7 +365,7 @@ public void GetSwagger_GeneratesResponsesFromSwaggerResponseAttributes_IfRespons public void GetSwagger_SetsDeprecated_IfActionsMarkedObsolete() { var subject = Subject(setupApis: apis => apis - .Add("GET", "collection", nameof(FakeActions.MarkedObsolete))); + .Add("GET", "collection", nameof(FakeController.MarkedObsolete))); var swagger = subject.GetSwagger("v1"); @@ -460,8 +455,8 @@ public void GetSwagger_IgnoresObsoleteActions_IfSpecifiedBySettings() var subject = Subject( setupApis: apis => { - apis.Add("GET", "collection1", nameof(FakeActions.ReturnsEnumerable)); - apis.Add("GET", "collection2", nameof(FakeActions.MarkedObsolete)); + apis.Add("GET", "collection1", nameof(FakeController.ReturnsEnumerable)); + apis.Add("GET", "collection2", nameof(FakeController.MarkedObsolete)); }, configure: c => c.IgnoreObsoleteActions = true); @@ -476,8 +471,8 @@ public void GetSwagger_TagsActions_AsSpecifiedBySettings() var subject = Subject( setupApis: apis => { - apis.Add("GET", "collection1", nameof(FakeActions.ReturnsEnumerable)); - apis.Add("GET", "collection2", nameof(FakeActions.ReturnsInt)); + apis.Add("GET", "collection1", nameof(FakeController.ReturnsEnumerable)); + apis.Add("GET", "collection2", nameof(FakeController.ReturnsInt)); }, configure: c => c.TagSelector = (apiDesc) => apiDesc.RelativePath); @@ -493,10 +488,10 @@ public void GetSwagger_OrdersActions_AsSpecifiedBySettings() var subject = Subject( setupApis: apis => { - apis.Add("GET", "B", nameof(FakeActions.ReturnsVoid)); - apis.Add("GET", "A", nameof(FakeActions.ReturnsVoid)); - apis.Add("GET", "F", nameof(FakeActions.ReturnsVoid)); - apis.Add("GET", "D", nameof(FakeActions.ReturnsVoid)); + apis.Add("GET", "B", nameof(FakeController.ReturnsVoid)); + apis.Add("GET", "A", nameof(FakeController.ReturnsVoid)); + apis.Add("GET", "F", nameof(FakeController.ReturnsVoid)); + apis.Add("GET", "D", nameof(FakeController.ReturnsVoid)); }, configure: c => { @@ -514,7 +509,7 @@ public void GetSwagger_ExecutesOperationFilters_IfSpecifiedBySettings() var subject = Subject( setupApis: apis => { - apis.Add("GET", "collection", nameof(FakeActions.ReturnsEnumerable)); + apis.Add("GET", "collection", nameof(FakeController.ReturnsEnumerable)); }, configure: c => { @@ -542,7 +537,7 @@ public void GetSwagger_ExecutesDocumentFilters_IfSpecifiedBySettings() public void GetSwagger_HandlesUnboundRouteParams() { var subject = Subject(setupApis: apis => apis - .Add("GET", "{version}/collection", nameof(FakeActions.AcceptsNothing))); + .Add("GET", "{version}/collection", nameof(FakeController.AcceptsNothing))); var swagger = subject.GetSwagger("v1"); @@ -556,11 +551,11 @@ public void GetSwagger_HandlesUnboundRouteParams() public void GetSwagger_ThrowsInformativeException_IfActionsHaveNoHttpBinding() { var subject = Subject(setupApis: apis => apis - .Add(null, "collection", nameof(FakeActions.AcceptsNothing))); + .Add(null, "collection", nameof(FakeController.AcceptsNothing))); var exception = Assert.Throws(() => subject.GetSwagger("v1")); Assert.Equal( - "Ambiguous HTTP method for action - Swashbuckle.AspNetCore.SwaggerGen.Test.FakeControllers+NotAnnotated.AcceptsNothing (Swashbuckle.AspNetCore.SwaggerGen.Test). " + + "Ambiguous HTTP method for action - Swashbuckle.AspNetCore.SwaggerGen.Test.FakeController.AcceptsNothing (Swashbuckle.AspNetCore.SwaggerGen.Test). " + "Actions require an explicit HttpMethod binding for Swagger 2.0", exception.Message); } @@ -569,15 +564,15 @@ public void GetSwagger_ThrowsInformativeException_IfActionsHaveNoHttpBinding() public void GetSwagger_ThrowsInformativeException_IfActionsHaveConflictingHttpMethodAndPath() { var subject = Subject(setupApis: apis => apis - .Add("GET", "collection", nameof(FakeActions.AcceptsNothing)) - .Add("GET", "collection", nameof(FakeActions.AcceptsStringFromQuery)) + .Add("GET", "collection", nameof(FakeController.AcceptsNothing)) + .Add("GET", "collection", nameof(FakeController.AcceptsStringFromQuery)) ); var exception = Assert.Throws(() => subject.GetSwagger("v1")); Assert.Equal( "HTTP method \"GET\" & path \"collection\" overloaded by actions - " + - "Swashbuckle.AspNetCore.SwaggerGen.Test.FakeControllers+NotAnnotated.AcceptsNothing (Swashbuckle.AspNetCore.SwaggerGen.Test)," + - "Swashbuckle.AspNetCore.SwaggerGen.Test.FakeControllers+NotAnnotated.AcceptsStringFromQuery (Swashbuckle.AspNetCore.SwaggerGen.Test). " + + "Swashbuckle.AspNetCore.SwaggerGen.Test.FakeController.AcceptsNothing (Swashbuckle.AspNetCore.SwaggerGen.Test)," + + "Swashbuckle.AspNetCore.SwaggerGen.Test.FakeController.AcceptsStringFromQuery (Swashbuckle.AspNetCore.SwaggerGen.Test). " + "Actions require unique method/path combination for Swagger 2.0. Use ConflictingActionsResolver as a workaround", exception.Message); } @@ -587,8 +582,8 @@ public void GetSwagger_MergesActionsWithConflictingHttpMethodAndPath_IfResolverI { var subject = Subject(setupApis: apis => apis - .Add("GET", "collection", nameof(FakeActions.AcceptsNothing)) - .Add("GET", "collection", nameof(FakeActions.AcceptsStringFromQuery)), + .Add("GET", "collection", nameof(FakeController.AcceptsNothing)) + .Add("GET", "collection", nameof(FakeController.AcceptsStringFromQuery)), configure: c => { c.ConflictingActionsResolver = (apiDescriptions) => apiDescriptions.First(); } ); diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeApiDescriptionGroupCollectionProvider.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeApiDescriptionGroupCollectionProvider.cs index 5cccc9605..689ca6550 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeApiDescriptionGroupCollectionProvider.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeApiDescriptionGroupCollectionProvider.cs @@ -39,12 +39,11 @@ public FakeApiDescriptionGroupCollectionProvider() public FakeApiDescriptionGroupCollectionProvider Add( string httpMethod, string routeTemplate, - string actionFixtureName, - string controllerFixtureName = "NotAnnotated" - ) + string actionName, + Type controllerType = null) { - _actionDescriptors.Add( - CreateActionDescriptor(httpMethod, routeTemplate, actionFixtureName, controllerFixtureName)); + controllerType = controllerType ?? typeof(FakeController); + _actionDescriptors.Add(CreateActionDescriptor(httpMethod, routeTemplate, controllerType, actionName)); return this; } @@ -61,11 +60,11 @@ public ApiDescriptionGroupCollection ApiDescriptionGroups private ControllerActionDescriptor CreateActionDescriptor( string httpMethod, string routeTemplate, - string actionFixtureName, - string controllerFixtureName - ) + Type controllerType, + string actionName) { var descriptor = new ControllerActionDescriptor(); + descriptor.SetProperty(new ApiDescriptionActionData()); descriptor.ActionConstraints = new List(); @@ -74,31 +73,32 @@ string controllerFixtureName descriptor.AttributeRouteInfo = new AttributeRouteInfo { Template = routeTemplate }; - descriptor.MethodInfo = typeof(FakeActions).GetMethod(actionFixtureName); + descriptor.MethodInfo = controllerType.GetMethod(actionName); if (descriptor.MethodInfo == null) throw new InvalidOperationException( - string.Format("{0} is not declared in ActionFixtures", actionFixtureName)); + string.Format("{0} is not declared in {1}", actionName, controllerType)); - descriptor.Parameters = descriptor.MethodInfo.GetParameters() - .Select(paramInfo => new ParameterDescriptor + descriptor.Parameters = new List(); + foreach (var parameterInfo in descriptor.MethodInfo.GetParameters()) + { + descriptor.Parameters.Add(new ControllerParameterDescriptor { - Name = paramInfo.Name, - ParameterType = paramInfo.ParameterType, - BindingInfo = BindingInfo.GetBindingInfo(paramInfo.GetCustomAttributes(false)) - }) - .ToList(); + Name = parameterInfo.Name, + ParameterType = parameterInfo.ParameterType, + ParameterInfo = parameterInfo, + BindingInfo = BindingInfo.GetBindingInfo(parameterInfo.GetCustomAttributes(false)) + }); + }; - var controllerType = typeof(FakeControllers).GetNestedType(controllerFixtureName, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); - if (controllerType == null) - throw new InvalidOperationException( - string.Format("{0} is not declared in ControllerFixtures", controllerFixtureName)); descriptor.ControllerTypeInfo = controllerType.GetTypeInfo(); descriptor.FilterDescriptors = descriptor.MethodInfo.GetCustomAttributes() .Select((filter) => new FilterDescriptor(filter, FilterScope.Action)) .ToList(); - descriptor.RouteValues = new Dictionary { { "controller", controllerFixtureName } }; + descriptor.RouteValues = new Dictionary { + { "controller", controllerType.Name.Replace("Controller", string.Empty) } + }; return descriptor; } diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeActions.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeController.cs similarity index 97% rename from test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeActions.cs rename to test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeController.cs index eddba94e9..52ebcb5c5 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeActions.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeController.cs @@ -7,7 +7,7 @@ namespace Swashbuckle.AspNetCore.SwaggerGen.Test { - public class FakeActions + public class FakeController { public void ReturnsVoid() { } @@ -60,8 +60,7 @@ public void AcceptsModelBoundParams(string stringWithNoAttributes) public void AcceptsDataAnnotatedParams( string stringWithNoAttributes, [Required]string stringWithRequired, - [Required]int intWithRequired, - [Required]int? nullableIntWithRequired) + [Required]int intWithRequired) { } public void AcceptsModelBoundType(ModelBoundType param) diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeControllers.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeControllers.cs index 4918916c5..5baae838b 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeControllers.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/FakeControllers.cs @@ -1,18 +1,8 @@ namespace Swashbuckle.AspNetCore.SwaggerGen.Test { - public class FakeControllers - { - public class NotAnnotated - {} - - [SwaggerOperationFilter(typeof(VendorExtensionsOperationFilter))] - public class AnnotatedWithSwaggerOperationFilter - {} - - /// - /// summary for AnnotatedWithXml - /// - public class AnnotatedWithXml - {} - } + /// + /// summary for XmlAnnotatedController + /// + public class XmlAnnotatedController + {} } \ No newline at end of file diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/SwaggerAnnotatedController.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/SwaggerAnnotatedController.cs new file mode 100644 index 000000000..78128d357 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Fakes/SwaggerAnnotatedController.cs @@ -0,0 +1,9 @@ +namespace Swashbuckle.AspNetCore.SwaggerGen.Test +{ + [SwaggerOperationFilter(typeof(VendorExtensionsOperationFilter))] + public class SwaggerAnnotatedController + { + public void ReturnsVoid() + { } + } +} \ No newline at end of file diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Types/DataAnnotatedType.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Types/DataAnnotatedType.cs index f896c9e8a..d1ea67553 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Types/DataAnnotatedType.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Types/DataAnnotatedType.cs @@ -12,9 +12,6 @@ public class DataAnnotatedType [Required] public int IntWithRequired { get; set; } - [Required] - public int? NullableIntWithRequired { get; set; } - [Range(1, 12)] public int IntWithRange { get; set; } diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Types/MetadataAnnotatedType.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Types/MetadataAnnotatedType.cs index 0440f6d15..7c12ccefa 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Types/MetadataAnnotatedType.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/TestFixtures/Types/MetadataAnnotatedType.cs @@ -10,8 +10,6 @@ public class MetadataAnnotatedType public int IntWithRequired { get; set; } - public int? NullableIntWithRequired { get; set; } - public int IntWithRange { get; set; } public string StringWithRegularExpression { get; set; } @@ -25,9 +23,6 @@ public class MetadataType [Required] public int IntWithRequired { get; set; } - [Required] - public int? NullableIntWithRequired { get; set; } - [Range(1, 12)] public int IntWithRange { get; set; } diff --git a/test/WebSites/Basic/Swagger/FormDataOperationFilter.cs b/test/WebSites/Basic/Swagger/FormDataOperationFilter.cs index 182d0054f..e76dcfde6 100644 --- a/test/WebSites/Basic/Swagger/FormDataOperationFilter.cs +++ b/test/WebSites/Basic/Swagger/FormDataOperationFilter.cs @@ -9,7 +9,7 @@ public class FormDataOperationFilter : IOperationFilter { public void Apply(Operation operation, OperationFilterContext context) { - var formMediaType = context.ControllerActionDescriptor.MethodInfo + var formMediaType = context.MethodInfo .GetCustomAttributes(true) .OfType() .SelectMany(attr => attr.ContentTypes) diff --git a/test/WebSites/MultipleVersions/Startup.cs b/test/WebSites/MultipleVersions/Startup.cs index 56cb044a2..73bae68fe 100644 --- a/test/WebSites/MultipleVersions/Startup.cs +++ b/test/WebSites/MultipleVersions/Startup.cs @@ -8,6 +8,7 @@ using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; using MultipleVersions.Swagger; +using System.Reflection; namespace MultipleVersions { @@ -35,11 +36,9 @@ public void ConfigureServices(IServiceCollection services) c.DocInclusionPredicate((docName, apiDesc) => { - var controllerActionDescriptor = apiDesc.ActionDescriptor as ControllerActionDescriptor; + if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)) return false; - if (controllerActionDescriptor == null) return false; - - var versions = controllerActionDescriptor.MethodInfo + var versions = methodInfo.DeclaringType .GetCustomAttributes(true) .OfType() .SelectMany(attr => attr.Versions); diff --git a/test/WebSites/OAuth2Integration/ResourceServer/Controllers/ProductsController.cs b/test/WebSites/OAuth2Integration/ResourceServer/Controllers/ProductsController.cs index 810c2833a..94bc6fdba 100644 --- a/test/WebSites/OAuth2Integration/ResourceServer/Controllers/ProductsController.cs +++ b/test/WebSites/OAuth2Integration/ResourceServer/Controllers/ProductsController.cs @@ -5,10 +5,11 @@ namespace OAuth2Integration.ResourceServer.Controllers { [Route("products")] + [Authorize(AuthenticationSchemes = "Bearer")] public class ProductsController : Controller { [HttpGet] - [Authorize(AuthenticationSchemes = "Bearer", Policy = "readAccess")] + [Authorize("readAccess")] public IEnumerable GetAll() { yield return new Product @@ -19,7 +20,7 @@ public IEnumerable GetAll() } [HttpGet("{id}")] - [Authorize(AuthenticationSchemes = "Bearer", Policy = "readAccess")] + [Authorize("readAccess")] public Product GetById(int id) { return new Product diff --git a/test/WebSites/OAuth2Integration/ResourceServer/Swagger/SecurityRequirementsOperationFilter.cs b/test/WebSites/OAuth2Integration/ResourceServer/Swagger/SecurityRequirementsOperationFilter.cs index 7d74e0586..f76bd7c7f 100644 --- a/test/WebSites/OAuth2Integration/ResourceServer/Swagger/SecurityRequirementsOperationFilter.cs +++ b/test/WebSites/OAuth2Integration/ResourceServer/Swagger/SecurityRequirementsOperationFilter.cs @@ -11,7 +11,7 @@ public class SecurityRequirementsOperationFilter : IOperationFilter public void Apply(Operation operation, OperationFilterContext context) { // Policy names map to scopes - var requiredScopes = context.ControllerActionDescriptor.MethodInfo + var requiredScopes = context.MethodInfo .GetCustomAttributes(true) .OfType() .Select(attr => attr.Policy)