diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsRequestBodyFilter.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsRequestBodyFilter.cs index dcd4f96267..48d07a0684 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsRequestBodyFilter.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsRequestBodyFilter.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.Linq; +using System.Reflection; using System.Xml.XPath; using Microsoft.OpenApi.Models; @@ -15,22 +16,26 @@ public XmlCommentsRequestBodyFilter(XPathDocument xmlDoc) public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext context) { - var bodyParameterDescription = context.BodyParameterDescription; + var parameterDescription = + context.BodyParameterDescription ?? + context.FormParameterDescriptions.FirstOrDefault((p) => p is not null); - if (bodyParameterDescription == null) return; + if (parameterDescription is null) + { + return; + } - var propertyInfo = bodyParameterDescription.PropertyInfo(); - if (propertyInfo != null) + var propertyInfo = parameterDescription.PropertyInfo(); + if (propertyInfo is not null) { ApplyPropertyTags(requestBody, context, propertyInfo); return; } - var parameterInfo = bodyParameterDescription.ParameterInfo(); - if (parameterInfo != null) + var parameterInfo = parameterDescription.ParameterInfo(); + if (parameterInfo is not null) { ApplyParamTags(requestBody, context, parameterInfo); - return; } } @@ -39,46 +44,63 @@ private void ApplyPropertyTags(OpenApiRequestBody requestBody, RequestBodyFilter var propertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(propertyInfo); var propertyNode = _xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{propertyMemberName}']"); - if (propertyNode == null) return; + if (propertyNode is null) + { + return; + } var summaryNode = propertyNode.SelectSingleNode("summary"); - if (summaryNode != null) + if (summaryNode is not null) + { requestBody.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml); + } var exampleNode = propertyNode.SelectSingleNode("example"); - if (exampleNode == null) return; + if (exampleNode is null || requestBody.Content?.Count is 0) + { + return; + } + + var example = exampleNode.ToString(); foreach (var mediaType in requestBody.Content.Values) { - mediaType.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, mediaType.Schema, exampleNode.ToString()); + mediaType.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, mediaType.Schema, example); } } private void ApplyParamTags(OpenApiRequestBody requestBody, RequestBodyFilterContext context, ParameterInfo parameterInfo) { - if (!(parameterInfo.Member is MethodInfo methodInfo)) return; + if (parameterInfo.Member is not MethodInfo methodInfo) + { + return; + } // If method is from a constructed generic type, look for comments from the generic type method var targetMethod = methodInfo.DeclaringType.IsConstructedGenericType ? methodInfo.GetUnderlyingGenericTypeMethod() : methodInfo; - if (targetMethod == null) return; + if (targetMethod is null) + { + return; + } var methodMemberName = XmlCommentsNodeNameHelper.GetMemberNameForMethod(targetMethod); var paramNode = _xmlNavigator.SelectSingleNode( $"/doc/members/member[@name='{methodMemberName}']/param[@name='{parameterInfo.Name}']"); - if (paramNode != null) + if (paramNode is not null) { requestBody.Description = XmlCommentsTextHelper.Humanize(paramNode.InnerXml); var example = paramNode.GetAttribute("example", ""); - if (string.IsNullOrEmpty(example)) return; - - foreach (var mediaType in requestBody.Content.Values) + if (!string.IsNullOrEmpty(example)) { - mediaType.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, mediaType.Schema, example); + foreach (var mediaType in requestBody.Content.Values) + { + mediaType.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, mediaType.Schema, example); + } } } } diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/FakeControllerWithXmlComments.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/FakeControllerWithXmlComments.cs index 431b33333c..f41d63ec07 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/FakeControllerWithXmlComments.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/FakeControllerWithXmlComments.cs @@ -1,54 +1,48 @@ -using Swashbuckle.AspNetCore.TestSupport; -using System; +using Microsoft.AspNetCore.Mvc; -namespace Swashbuckle.AspNetCore.SwaggerGen.Test +namespace Swashbuckle.AspNetCore.SwaggerGen.Test; + +/// +/// Summary for FakeControllerWithXmlComments +/// +/// Description for default response +public class FakeControllerWithXmlComments { /// - /// Summary for FakeControllerWithXmlComments + /// Summary for ActionWithSummaryAndRemarksTags /// - /// Description for default response - public class FakeControllerWithXmlComments + /// + /// Remarks for ActionWithSummaryAndRemarksTags + /// + public void ActionWithSummaryAndRemarksTags() { - /// - /// Summary for ActionWithSummaryAndRemarksTags - /// - /// - /// Remarks for ActionWithSummaryAndRemarksTags - /// - public void ActionWithSummaryAndRemarksTags() - { } - - /// Description for param1 - /// Description for param2 - public void ActionWithParamTags(string param1, string param2) - { } + } + /// Description for param1 + /// Description for param2 + public void ActionWithParamTags(string param1, string param2) + { + } + /// Description for 200 response + /// Description for 400 response + public void ActionWithResponseTags() + { + } - /// Description for 200 response - /// Description for 400 response - public void ActionWithResponseTags() - { } + /// + /// An action with a JSON body + /// + /// Parameter from JSON body + public void PostBody([FromBody] string name) + { + } - ///// - ///// - ///// - ///// - ///// - ///// - ///// - ///// - ///// - //public void ActionWithExampleParams( - // bool boolParam, - // int intParam, - // long longParam, - // float floatParam, - // double doubleParam, - // IntEnum enumParam, - // Guid guidParam, - // string stringParam, - // int badExampleIntParam) - //{ } + /// + /// An action with a form body + /// + /// Parameter from form body + public void PostForm([FromForm] string name) + { } -} \ No newline at end of file +} diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsRequestBodyFilterTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsRequestBodyFilterTests.cs index 9d4bd7dabf..ef0b6e43bf 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsRequestBodyFilterTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsRequestBodyFilterTests.cs @@ -1,11 +1,11 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; using System.Xml.XPath; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.OpenApi.Models; -using Xunit; using Swashbuckle.AspNetCore.TestSupport; -using System.Collections.Generic; +using Xunit; namespace Swashbuckle.AspNetCore.SwaggerGen.Test { @@ -109,12 +109,59 @@ public void Apply_SetsDescriptionAndExample_FromUriTypePropertySummaryAndExample Assert.NotNull(requestBody.Content["application/json"].Example); Assert.Equal("\"https://test.com/a?b=1&c=2\"", requestBody.Content["application/json"].Example.ToJson()); } - private XmlCommentsRequestBodyFilter Subject() + + [Fact] + public void Apply_SetsDescription_ForParameterFromBody() + { + var requestBody = new OpenApiRequestBody + { + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "string" } } + } + }; + var parameterInfo = typeof(FakeControllerWithXmlComments) + .GetMethod(nameof(FakeControllerWithXmlComments.PostBody)) + .GetParameters()[0]; + var bodyParameterDescription = new ApiParameterDescription + { + ParameterDescriptor = new ControllerParameterDescriptor { ParameterInfo = parameterInfo } + }; + var filterContext = new RequestBodyFilterContext(bodyParameterDescription, null, null, null); + + Subject().Apply(requestBody, filterContext); + + Assert.Equal("Parameter from JSON body", requestBody.Description); + } + + [Fact] + public void Apply_SetsDescription_ForParameterFromForm() { - using (var xmlComments = File.OpenText(typeof(FakeControllerWithXmlComments).Assembly.GetName().Name + ".xml")) + var requestBody = new OpenApiRequestBody { - return new XmlCommentsRequestBodyFilter(new XPathDocument(xmlComments)); - } + Content = new Dictionary + { + ["multipart/form-data"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "string" } } + } + }; + var parameterInfo = typeof(FakeControllerWithXmlComments) + .GetMethod(nameof(FakeControllerWithXmlComments.PostForm)) + .GetParameters()[0]; + var bodyParameterDescription = new ApiParameterDescription + { + ParameterDescriptor = new ControllerParameterDescriptor { ParameterInfo = parameterInfo } + }; + var filterContext = new RequestBodyFilterContext(null, [bodyParameterDescription], null, null); + + Subject().Apply(requestBody, filterContext); + + Assert.Equal("Parameter from form body", requestBody.Description); + } + + private static XmlCommentsRequestBodyFilter Subject() + { + using var xmlComments = File.OpenText(typeof(FakeControllerWithXmlComments).Assembly.GetName().Name + ".xml"); + return new XmlCommentsRequestBodyFilter(new XPathDocument(xmlComments)); } } }