From 2831dc0d04887e2e43398d45a75de79817115b7f Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 26 Nov 2025 10:19:29 +0100 Subject: [PATCH 1/9] Add test demonstrating the issue --- .../OperationTests.Controllers.cs | 47 +- ...ApiXmlCommentSupport.generated.verified.cs | 2 +- ...ApiXmlCommentSupport.generated.verified.cs | 651 ++++++++++++++++++ 3 files changed, 698 insertions(+), 2 deletions(-) create mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs index ef26c9590ec3..c70d4e7c1ccb 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs @@ -124,7 +124,7 @@ public class TestController : ControllerBase { /// The id of the user. [HttpGet("{userId}")] - public string Get() + public string Get(int userId) { return "Hello, World!"; } @@ -141,4 +141,49 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => Assert.Equal("The id of the user.", path.Parameters[0].Description); }); } + + [Fact] + public async Task SupportsRouteParametersWithCustomNamesFromControllers() + { + var source = """ + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.DependencyInjection; + + var builder = WebApplication.CreateBuilder(); + + builder.Services + .AddControllers() + .AddApplicationPart(typeof(TestController).Assembly); + builder.Services.AddOpenApi(); + + var app = builder.Build(); + + app.MapControllers(); + + app.Run(); + + [ApiController] + [Route("[controller]")] + public class TestController : ControllerBase + { + /// The id of the user. + [HttpGet("{user_id}")] + public string Get([FromRoute(Name = "user_id")] int userId) + { + return "Hello, World!"; + } + } + + public partial class Program {} + + """; + var generator = new XmlCommentGenerator(); + await SnapshotTestHelper.Verify(source, generator, out var compilation); + await SnapshotTestHelper.VerifyOpenApi(compilation, document => + { + var path = document.Paths["/Test/{user_id}"].Operations[HttpMethod.Get]; + Assert.Equal("The id of the user.", path.Parameters[0].Description); + }); + } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 9f2b1de95ca8..63308d730037 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -71,7 +71,7 @@ private static Dictionary GenerateCacheEntries() { var cache = new Dictionary(); - cache.Add(@"M:TestController.Get", new XmlComment(null, null, null, null, null, false, null, [new XmlParameterComment(@"userId", @"The id of the user.", null, false)], null)); + cache.Add(@"M:TestController.Get(System.Int32)", new XmlComment(null, null, null, null, null, false, null, [new XmlParameterComment(@"userId", @"The id of the user.", null, false)], null)); return cache; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs new file mode 100644 index 000000000000..034d851e2964 --- /dev/null +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -0,0 +1,651 @@ +//HintName: OpenApiXmlCommentSupport.generated.cs +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable +// Suppress warnings about obsolete types and members +// in generated code +#pragma warning disable CS0612, CS0618 + +namespace System.Runtime.CompilerServices +{ + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : System.Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + } + } +} + +namespace Microsoft.AspNetCore.OpenApi.Generated +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Text.Json; + using System.Text.Json.Nodes; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.AspNetCore.OpenApi; + using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.AspNetCore.Mvc.ModelBinding; + using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.OpenApi; + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file record XmlComment( + string? Summary, + string? Description, + string? Remarks, + string? Returns, + string? Value, + bool Deprecated, + List? Examples, + List? Parameters, + List? Responses); + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file record XmlParameterComment(string? Name, string? Description, string? Example, bool Deprecated); + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file record XmlResponseComment(string Code, string? Description, string? Example); + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class XmlCommentCache + { + private static Dictionary? _cache; + public static Dictionary Cache => _cache ??= GenerateCacheEntries(); + + private static Dictionary GenerateCacheEntries() + { + var cache = new Dictionary(); + + cache.Add(@"M:TestController.Get(System.Int32)", new XmlComment(null, null, null, null, null, false, null, [new XmlParameterComment(@"userId", @"The id of the user.", null, false)], null)); + + return cache; + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class DocumentationCommentIdHelper + { + /// + /// Generates a documentation comment ID for a type. + /// Example: T:Namespace.Outer+Inner`1 becomes T:Namespace.Outer.Inner`1 + /// + public static string CreateDocumentationId(this Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + return "T:" + GetTypeDocId(type, includeGenericArguments: false, omitGenericArity: false); + } + + /// + /// Generates a documentation comment ID for a property. + /// Example: P:Namespace.ContainingType.PropertyName or for an indexer P:Namespace.ContainingType.Item(System.Int32) + /// + public static string CreateDocumentationId(this PropertyInfo property) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + + var sb = new StringBuilder(); + sb.Append("P:"); + + if (property.DeclaringType != null) + { + sb.Append(GetTypeDocId(property.DeclaringType, includeGenericArguments: false, omitGenericArity: false)); + } + + sb.Append('.'); + sb.Append(property.Name); + + // For indexers, include the parameter list. + var indexParams = property.GetIndexParameters(); + if (indexParams.Length > 0) + { + sb.Append('('); + for (int i = 0; i < indexParams.Length; i++) + { + if (i > 0) + { + sb.Append(','); + } + + sb.Append(GetTypeDocId(indexParams[i].ParameterType, includeGenericArguments: true, omitGenericArity: false)); + } + sb.Append(')'); + } + + return sb.ToString(); + } + + /// + /// Generates a documentation comment ID for a property given its container type and property name. + /// Example: P:Namespace.ContainingType.PropertyName + /// + public static string CreateDocumentationId(Type containerType, string propertyName) + { + if (containerType == null) + { + throw new ArgumentNullException(nameof(containerType)); + } + if (string.IsNullOrEmpty(propertyName)) + { + throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName)); + } + + var sb = new StringBuilder(); + sb.Append("P:"); + sb.Append(GetTypeDocId(containerType, includeGenericArguments: false, omitGenericArity: false)); + sb.Append('.'); + sb.Append(propertyName); + + return sb.ToString(); + } + + /// + /// Generates a documentation comment ID for a method (or constructor). + /// For example: + /// M:Namespace.ContainingType.MethodName(ParamType1,ParamType2)~ReturnType + /// M:Namespace.ContainingType.#ctor(ParamType) + /// + public static string CreateDocumentationId(this MethodInfo method) + { + if (method == null) + { + throw new ArgumentNullException(nameof(method)); + } + + var sb = new StringBuilder(); + sb.Append("M:"); + + // Append the fully qualified name of the declaring type. + if (method.DeclaringType != null) + { + sb.Append(GetTypeDocId(method.DeclaringType, includeGenericArguments: false, omitGenericArity: false)); + } + + sb.Append('.'); + + // Append the method name, handling constructors specially. + if (method.IsConstructor) + { + sb.Append(method.IsStatic ? "#cctor" : "#ctor"); + } + else + { + sb.Append(method.Name); + if (method.IsGenericMethod) + { + sb.Append("``"); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}", method.GetGenericArguments().Length); + } + } + + // Append the parameter list, if any. + var parameters = method.GetParameters(); + if (parameters.Length > 0) + { + sb.Append('('); + for (int i = 0; i < parameters.Length; i++) + { + if (i > 0) + { + sb.Append(','); + } + + // Omit the generic arity for the parameter type. + sb.Append(GetTypeDocId(parameters[i].ParameterType, includeGenericArguments: true, omitGenericArity: true)); + } + sb.Append(')'); + } + + // Append the return type after a '~' (if the method returns a value). + if (method.ReturnType != typeof(void)) + { + sb.Append('~'); + // Omit the generic arity for the return type. + sb.Append(GetTypeDocId(method.ReturnType, includeGenericArguments: true, omitGenericArity: true)); + } + + return sb.ToString(); + } + + /// + /// Generates a documentation ID string for a type. + /// This method handles nested types (replacing '+' with '.'), + /// generic types, arrays, pointers, by-ref types, and generic parameters. + /// The flag controls whether + /// constructed generic type arguments are emitted, while + /// controls whether the generic arity marker (e.g. "`1") is appended. + /// + private static string GetTypeDocId(Type type, bool includeGenericArguments, bool omitGenericArity) + { + if (type.IsGenericParameter) + { + // Use `` for method-level generic parameters and ` for type-level. + if (type.DeclaringMethod != null) + { + return "``" + type.GenericParameterPosition; + } + else if (type.DeclaringType != null) + { + return "`" + type.GenericParameterPosition; + } + else + { + return type.Name; + } + } + + if (type.IsGenericType) + { + Type genericDef = type.GetGenericTypeDefinition(); + string fullName = genericDef.FullName ?? genericDef.Name; + + var sb = new StringBuilder(fullName.Length); + + // Replace '+' with '.' for nested types + for (var i = 0; i < fullName.Length; i++) + { + char c = fullName[i]; + if (c == '+') + { + sb.Append('.'); + } + else if (c == '`') + { + break; + } + else + { + sb.Append(c); + } + } + + if (!omitGenericArity) + { + int arity = genericDef.GetGenericArguments().Length; + sb.Append('`'); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}", arity); + } + + if (includeGenericArguments && !type.IsGenericTypeDefinition) + { + var typeArgs = type.GetGenericArguments(); + sb.Append('{'); + + for (int i = 0; i < typeArgs.Length; i++) + { + if (i > 0) + { + sb.Append(','); + } + + sb.Append(GetTypeDocId(typeArgs[i], includeGenericArguments, omitGenericArity)); + } + + sb.Append('}'); + } + + return sb.ToString(); + } + + // For non-generic types, use FullName (if available) and replace nested type separators. + return (type.FullName ?? type.Name).Replace('+', '.'); + } + + /// + /// Normalizes a documentation comment ID to match the compiler-style format. + /// Strips the return type suffix for ordinary methods but retains it for conversion operators. + /// + /// The documentation comment ID to normalize. + /// The normalized documentation comment ID. + public static string NormalizeDocId(string docId) + { + // Find the tilde character that indicates the return type suffix + var tildeIndex = docId.IndexOf('~'); + if (tildeIndex == -1) + { + // No return type suffix, return as-is + return docId; + } + + // Check if this is a conversion operator (op_Implicit or op_Explicit) + // For these operators, we need to keep the return type suffix + if (docId.Contains("op_Implicit") || docId.Contains("op_Explicit")) + { + return docId; + } + + // For ordinary methods, strip the return type suffix + return docId.Substring(0, tildeIndex); + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file class XmlCommentOperationTransformer : IOpenApiOperationTransformer + { + public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken) + { + var methodInfo = context.Description.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor + ? controllerActionDescriptor.MethodInfo + : context.Description.ActionDescriptor.EndpointMetadata.OfType().SingleOrDefault(); + + if (methodInfo is null) + { + return Task.CompletedTask; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(methodInfo.CreateDocumentationId()), out var methodComment)) + { + if (methodComment.Summary is { } summary) + { + operation.Summary = summary; + } + if (methodComment.Description is { } description) + { + operation.Description = description; + } + if (methodComment.Remarks is { } remarks) + { + operation.Description = remarks; + } + if (methodComment.Parameters is { Count: > 0}) + { + foreach (var parameterComment in methodComment.Parameters) + { + var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); + if (parameterInfo is null) + { + continue; + } + + var operationParameter = GetOperationParameter(operation, parameterInfo); + if (operationParameter is not null) + { + var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); + targetOperationParameter.Description = parameterComment.Description; + if (parameterComment.Example is { } jsonString) + { + targetOperationParameter.Example = jsonString.Parse(); + } + targetOperationParameter.Deprecated = parameterComment.Deprecated; + } + else + { + var requestBody = operation.RequestBody; + if (requestBody is not null) + { + requestBody.Description = parameterComment.Description; + if (parameterComment.Example is { } jsonString) + { + var content = requestBody?.Content?.Values; + if (content is null) + { + continue; + } + foreach (var mediaType in content) + { + mediaType.Example = jsonString.Parse(); + } + } + } + } + } + } + // Applies `` on XML comments for operation with single response value. + if (methodComment.Returns is { } returns && operation.Responses is { Count: 1 }) + { + var response = operation.Responses.First(); + response.Value.Description = returns; + } + // Applies `` on XML comments for operation with multiple response values. + if (methodComment.Responses is { Count: > 0} && operation.Responses is { Count: > 0 }) + { + foreach (var response in operation.Responses) + { + var responseComment = methodComment.Responses.SingleOrDefault(xmlResponse => xmlResponse.Code == response.Key); + if (responseComment is not null) + { + response.Value.Description = responseComment.Description; + } + } + } + } + foreach (var parameterDescription in context.Description.ParameterDescriptions) + { + var metadata = parameterDescription.ModelMetadata; + if (metadata is not null + && metadata.MetadataKind == ModelMetadataKind.Property + && metadata.ContainerType is { } containerType + && metadata.PropertyName is { } propertyName) + { + var propertyDocId = DocumentationCommentIdHelper.CreateDocumentationId(containerType, propertyName); + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyDocId), out var propertyComment)) + { + var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); + var description = propertyComment.Summary; + if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) + { + description = $"{description}\n{propertyComment.Value}"; + } + else if (string.IsNullOrEmpty(description)) + { + description = propertyComment.Value; + } + if (parameter is null) + { + if (operation.RequestBody is not null) + { + operation.RequestBody.Description = description; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + var content = operation.RequestBody.Content?.Values; + if (content is null) + { + continue; + } + var parsedExample = jsonString.Parse(); + foreach (var mediaType in content) + { + mediaType.Example = parsedExample; + } + } + } + continue; + } + var targetOperationParameter = UnwrapOpenApiParameter(parameter); + if (targetOperationParameter is not null) + { + targetOperationParameter.Description = description; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + targetOperationParameter.Example = jsonString.Parse(); + } + } + } + } + } + + return Task.CompletedTask; + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + if (operation.Parameters is null) + { + return null; + } + + var names = parameterInfo.GetCustomAttributes(inherit: false) + .OfType() + .Select(x => x.Name) + .Append(parameterInfo.Name) + .ToHashSet(); + + foreach (var operationParameter in operation.Parameters) + { + // Optimize for the most common case + if (operationParameter.Name == parameterInfo.Name) + { + return operationParameter; + } + + // Check all attributes implementing IModelNameProvider for custom names + foreach (var modelNameProvider in parameterInfo.GetCustomAttributes(inherit: false).OfType()) + { + var modelName = modelNameProvider.Name; + if (!string.IsNullOrEmpty(modelName) && operationParameter.Name == modelName) + { + return operationParameter; + } + } + } + + return null; + } + + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) + { + if (sourceParameter is OpenApiParameterReference parameterReference) + { + if (parameterReference.Target is OpenApiParameter target) + { + return target; + } + else + { + throw new InvalidOperationException($"The input schema must be an {nameof(OpenApiParameter)} or {nameof(OpenApiParameterReference)}."); + } + } + else if (sourceParameter is OpenApiParameter directParameter) + { + return directParameter; + } + else + { + throw new InvalidOperationException($"The input schema must be an {nameof(OpenApiParameter)} or {nameof(OpenApiParameterReference)}."); + } + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file class XmlCommentSchemaTransformer : IOpenApiSchemaTransformer + { + public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) + { + // Apply comments from the type + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) + { + schema.Description = typeComment.Summary; + if (typeComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + + if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + { + // Apply comments from the property + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var description = propertyComment.Summary; + if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) + { + description = $"{description}\n{propertyComment.Value}"; + } + else if (string.IsNullOrEmpty(description)) + { + description = propertyComment.Value; + } + if (schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string)) + { + // Inlined schema + schema.Description = description; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + else + { + // Schema Reference + if (!string.IsNullOrEmpty(description)) + { + schema.Metadata["x-ref-description"] = description; + } + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Metadata["x-ref-example"] = jsonString.Parse()!; + } + } + } + } + return Task.CompletedTask; + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class JsonNodeExtensions + { + public static JsonNode? Parse(this string? json) + { + if (json is null) + { + return null; + } + + try + { + return JsonNode.Parse(json); + } + catch (JsonException) + { + try + { + // If parsing fails, try wrapping in quotes to make it a valid JSON string + return JsonNode.Parse($"\"{json.Replace("\"", "\\\"")}\""); + } + catch (JsonException) + { + return null; + } + } + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class GeneratedServiceCollectionExtensions + { + [InterceptsLocation] + public static IServiceCollection AddOpenApi(this IServiceCollection services) + { + return services.AddOpenApi("v1", options => + { + options.AddSchemaTransformer(new XmlCommentSchemaTransformer()); + options.AddOperationTransformer(new XmlCommentOperationTransformer()); + }); + } + + } +} From d6a6aaa1e72f44d189382a84b5abde9a4ff433a2 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 26 Nov 2025 09:42:56 +0100 Subject: [PATCH 2/9] Respect IModelNameProvider when matching OpenAPI parameters --- .../gen/XmlCommentGenerator.Emitter.cs | 42 +++++++++++++++++- ...ApiXmlCommentSupport.generated.verified.cs | 44 ++++++++++++++++++- ...ApiXmlCommentSupport.generated.verified.cs | 44 ++++++++++++++++++- ...ApiXmlCommentSupport.generated.verified.cs | 44 ++++++++++++++++++- ...ApiXmlCommentSupport.generated.verified.cs | 44 ++++++++++++++++++- ...ApiXmlCommentSupport.generated.verified.cs | 27 ++++++------ ...ApiXmlCommentSupport.generated.verified.cs | 44 ++++++++++++++++++- ...ApiXmlCommentSupport.generated.verified.cs | 44 ++++++++++++++++++- ...ApiXmlCommentSupport.generated.verified.cs | 44 ++++++++++++++++++- ...ApiXmlCommentSupport.generated.verified.cs | 44 ++++++++++++++++++- ...ApiXmlCommentSupport.generated.verified.cs | 44 ++++++++++++++++++- 11 files changed, 432 insertions(+), 33 deletions(-) diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs index 2dd60e993cc9..02f0968db047 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs @@ -57,6 +57,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using System.Threading.Tasks; using Microsoft.AspNetCore.OpenApi; using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi; @@ -389,7 +390,12 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); + if (parameterInfo is null) + { + continue; + } + + var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); @@ -499,6 +505,40 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + var parameters = operation.Parameters; + if (parameters is null) + { + return null; + } + + var modelNames = parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name) + .Append(parameterInfo.Name) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + + foreach (var parameter in parameters) + { + var parameterName = parameter.Name; + + if (string.IsNullOrEmpty(parameterName)) + { + continue; + } + + if (modelNames.Contains(parameterName)) + { + return parameter; + } + } + + return null; + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs index c235ae4c3688..fde149738053 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs @@ -1,4 +1,4 @@ -//HintName: OpenApiXmlCommentSupport.generated.cs +//HintName: OpenApiXmlCommentSupport.generated.cs //------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using System.Threading.Tasks; using Microsoft.AspNetCore.OpenApi; using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi; @@ -371,7 +372,12 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); + if (parameterInfo is null) + { + continue; + } + + var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); @@ -481,6 +487,40 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + var parameters = operation.Parameters; + if (parameters is null) + { + return null; + } + + var modelNames = parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name) + .Append(parameterInfo.Name) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + + foreach (var parameter in parameters) + { + var parameterName = parameter.Name; + + if (string.IsNullOrEmpty(parameterName)) + { + continue; + } + + if (modelNames.Contains(parameterName)) + { + return parameter; + } + } + + return null; + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs index 3affbf940068..7f0aba5896e5 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs @@ -1,4 +1,4 @@ -//HintName: OpenApiXmlCommentSupport.generated.cs +//HintName: OpenApiXmlCommentSupport.generated.cs //------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using System.Threading.Tasks; using Microsoft.AspNetCore.OpenApi; using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi; @@ -400,7 +401,12 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); + if (parameterInfo is null) + { + continue; + } + + var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); @@ -510,6 +516,40 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + var parameters = operation.Parameters; + if (parameters is null) + { + return null; + } + + var modelNames = parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name) + .Append(parameterInfo.Name) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + + foreach (var parameter in parameters) + { + var parameterName = parameter.Name; + + if (string.IsNullOrEmpty(parameterName)) + { + continue; + } + + if (modelNames.Contains(parameterName)) + { + return parameter; + } + } + + return null; + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 7bb176e85405..31b2d933f426 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -1,4 +1,4 @@ -//HintName: OpenApiXmlCommentSupport.generated.cs +//HintName: OpenApiXmlCommentSupport.generated.cs //------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using System.Threading.Tasks; using Microsoft.AspNetCore.OpenApi; using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi; @@ -498,7 +499,12 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); + if (parameterInfo is null) + { + continue; + } + + var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); @@ -608,6 +614,40 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + var parameters = operation.Parameters; + if (parameters is null) + { + return null; + } + + var modelNames = parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name) + .Append(parameterInfo.Name) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + + foreach (var parameter in parameters) + { + var parameterName = parameter.Name; + + if (string.IsNullOrEmpty(parameterName)) + { + continue; + } + + if (modelNames.Contains(parameterName)) + { + return parameter; + } + } + + return null; + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 63308d730037..845fcc208400 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -1,4 +1,4 @@ -//HintName: OpenApiXmlCommentSupport.generated.cs +//HintName: OpenApiXmlCommentSupport.generated.cs //------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using System.Threading.Tasks; using Microsoft.AspNetCore.OpenApi; using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi; @@ -372,7 +373,12 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); + if (parameterInfo is null) + { + continue; + } + + var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); @@ -482,6 +488,40 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + var parameters = operation.Parameters; + if (parameters is null) + { + return null; + } + + var modelNames = parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name) + .Append(parameterInfo.Name) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + + foreach (var parameter in parameters) + { + var parameterName = parameter.Name; + + if (string.IsNullOrEmpty(parameterName)) + { + continue; + } + + if (modelNames.Contains(parameterName)) + { + return parameter; + } + } + + return null; + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 034d851e2964..845fcc208400 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -490,33 +490,32 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) { - if (operation.Parameters is null) + var parameters = operation.Parameters; + if (parameters is null) { return null; } - var names = parameterInfo.GetCustomAttributes(inherit: false) + var modelNames = parameterInfo + .GetCustomAttributes(inherit: false) .OfType() - .Select(x => x.Name) + .Select(p => p.Name) .Append(parameterInfo.Name) + .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); - foreach (var operationParameter in operation.Parameters) + foreach (var parameter in parameters) { - // Optimize for the most common case - if (operationParameter.Name == parameterInfo.Name) + var parameterName = parameter.Name; + + if (string.IsNullOrEmpty(parameterName)) { - return operationParameter; + continue; } - // Check all attributes implementing IModelNameProvider for custom names - foreach (var modelNameProvider in parameterInfo.GetCustomAttributes(inherit: false).OfType()) + if (modelNames.Contains(parameterName)) { - var modelName = modelNameProvider.Name; - if (!string.IsNullOrEmpty(modelName) && operationParameter.Name == modelName) - { - return operationParameter; - } + return parameter; } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 753c09661ae8..c66d7d8baae7 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -1,4 +1,4 @@ -//HintName: OpenApiXmlCommentSupport.generated.cs +//HintName: OpenApiXmlCommentSupport.generated.cs //------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using System.Threading.Tasks; using Microsoft.AspNetCore.OpenApi; using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi; @@ -375,7 +376,12 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); + if (parameterInfo is null) + { + continue; + } + + var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); @@ -485,6 +491,40 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + var parameters = operation.Parameters; + if (parameters is null) + { + return null; + } + + var modelNames = parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name) + .Append(parameterInfo.Name) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + + foreach (var parameter in parameters) + { + var parameterName = parameter.Name; + + if (string.IsNullOrEmpty(parameterName)) + { + continue; + } + + if (modelNames.Contains(parameterName)) + { + return parameter; + } + } + + return null; + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index fe7be155d841..639cff0ca683 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -1,4 +1,4 @@ -//HintName: OpenApiXmlCommentSupport.generated.cs +//HintName: OpenApiXmlCommentSupport.generated.cs //------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using System.Threading.Tasks; using Microsoft.AspNetCore.OpenApi; using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi; @@ -419,7 +420,12 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); + if (parameterInfo is null) + { + continue; + } + + var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); @@ -529,6 +535,40 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + var parameters = operation.Parameters; + if (parameters is null) + { + return null; + } + + var modelNames = parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name) + .Append(parameterInfo.Name) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + + foreach (var parameter in parameters) + { + var parameterName = parameter.Name; + + if (string.IsNullOrEmpty(parameterName)) + { + continue; + } + + if (modelNames.Contains(parameterName)) + { + return parameter; + } + } + + return null; + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 9ae384e0e245..bcaa644d88eb 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -1,4 +1,4 @@ -//HintName: OpenApiXmlCommentSupport.generated.cs +//HintName: OpenApiXmlCommentSupport.generated.cs //------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using System.Threading.Tasks; using Microsoft.AspNetCore.OpenApi; using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi; @@ -401,7 +402,12 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); + if (parameterInfo is null) + { + continue; + } + + var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); @@ -511,6 +517,40 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + var parameters = operation.Parameters; + if (parameters is null) + { + return null; + } + + var modelNames = parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name) + .Append(parameterInfo.Name) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + + foreach (var parameter in parameters) + { + var parameterName = parameter.Name; + + if (string.IsNullOrEmpty(parameterName)) + { + continue; + } + + if (modelNames.Contains(parameterName)) + { + return parameter; + } + } + + return null; + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs index 86f754cd6cd6..611e1342bc0e 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs @@ -1,4 +1,4 @@ -//HintName: OpenApiXmlCommentSupport.generated.cs +//HintName: OpenApiXmlCommentSupport.generated.cs //------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using System.Threading.Tasks; using Microsoft.AspNetCore.OpenApi; using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi; @@ -380,7 +381,12 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); + if (parameterInfo is null) + { + continue; + } + + var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); @@ -490,6 +496,40 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + var parameters = operation.Parameters; + if (parameters is null) + { + return null; + } + + var modelNames = parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name) + .Append(parameterInfo.Name) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + + foreach (var parameter in parameters) + { + var parameterName = parameter.Name; + + if (string.IsNullOrEmpty(parameterName)) + { + continue; + } + + if (modelNames.Contains(parameterName)) + { + return parameter; + } + } + + return null; + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs index 44baeee7116b..aa2603dcc523 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs @@ -1,4 +1,4 @@ -//HintName: OpenApiXmlCommentSupport.generated.cs +//HintName: OpenApiXmlCommentSupport.generated.cs //------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated using System.Threading.Tasks; using Microsoft.AspNetCore.OpenApi; using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi; @@ -372,7 +373,12 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); + if (parameterInfo is null) + { + continue; + } + + var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); @@ -482,6 +488,40 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + var parameters = operation.Parameters; + if (parameters is null) + { + return null; + } + + var modelNames = parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name) + .Append(parameterInfo.Name) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + + foreach (var parameter in parameters) + { + var parameterName = parameter.Name; + + if (string.IsNullOrEmpty(parameterName)) + { + continue; + } + + if (modelNames.Contains(parameterName)) + { + return parameter; + } + } + + return null; + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) From ee1593bc982a4a75f941e7f41219a437ecef381b Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 26 Nov 2025 11:19:05 +0100 Subject: [PATCH 3/9] Extract helper method to get model names --- .../gen/XmlCommentGenerator.Emitter.cs | 19 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 19 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 19 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 19 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 19 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 19 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 19 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 19 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 19 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 19 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 19 ++++++++++++------- 11 files changed, 132 insertions(+), 77 deletions(-) diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs index 02f0968db047..b44adb391aaa 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs @@ -513,13 +513,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = parameterInfo - .GetCustomAttributes(inherit: false) - .OfType() - .Select(p => p.Name) - .Append(parameterInfo.Name) - .Where(n => !string.IsNullOrEmpty(n)) - .ToHashSet(); + var modelNames = GetModelNames(parameterInfo); foreach (var parameter in parameters) { @@ -539,6 +533,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } + private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + { + return parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name!) + .Append(parameterInfo.Name!) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs index fde149738053..d37929229181 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs @@ -495,13 +495,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = parameterInfo - .GetCustomAttributes(inherit: false) - .OfType() - .Select(p => p.Name) - .Append(parameterInfo.Name) - .Where(n => !string.IsNullOrEmpty(n)) - .ToHashSet(); + var modelNames = GetModelNames(parameterInfo); foreach (var parameter in parameters) { @@ -521,6 +515,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } + private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + { + return parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name!) + .Append(parameterInfo.Name!) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs index 7f0aba5896e5..1d5e784e779e 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs @@ -524,13 +524,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = parameterInfo - .GetCustomAttributes(inherit: false) - .OfType() - .Select(p => p.Name) - .Append(parameterInfo.Name) - .Where(n => !string.IsNullOrEmpty(n)) - .ToHashSet(); + var modelNames = GetModelNames(parameterInfo); foreach (var parameter in parameters) { @@ -550,6 +544,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } + private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + { + return parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name!) + .Append(parameterInfo.Name!) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 31b2d933f426..a2cdfb43f294 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -622,13 +622,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = parameterInfo - .GetCustomAttributes(inherit: false) - .OfType() - .Select(p => p.Name) - .Append(parameterInfo.Name) - .Where(n => !string.IsNullOrEmpty(n)) - .ToHashSet(); + var modelNames = GetModelNames(parameterInfo); foreach (var parameter in parameters) { @@ -648,6 +642,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } + private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + { + return parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name!) + .Append(parameterInfo.Name!) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 845fcc208400..08a86fb2fab4 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -496,13 +496,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = parameterInfo - .GetCustomAttributes(inherit: false) - .OfType() - .Select(p => p.Name) - .Append(parameterInfo.Name) - .Where(n => !string.IsNullOrEmpty(n)) - .ToHashSet(); + var modelNames = GetModelNames(parameterInfo); foreach (var parameter in parameters) { @@ -522,6 +516,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } + private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + { + return parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name!) + .Append(parameterInfo.Name!) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 845fcc208400..08a86fb2fab4 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -496,13 +496,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = parameterInfo - .GetCustomAttributes(inherit: false) - .OfType() - .Select(p => p.Name) - .Append(parameterInfo.Name) - .Where(n => !string.IsNullOrEmpty(n)) - .ToHashSet(); + var modelNames = GetModelNames(parameterInfo); foreach (var parameter in parameters) { @@ -522,6 +516,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } + private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + { + return parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name!) + .Append(parameterInfo.Name!) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index c66d7d8baae7..913cc0517b86 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -499,13 +499,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = parameterInfo - .GetCustomAttributes(inherit: false) - .OfType() - .Select(p => p.Name) - .Append(parameterInfo.Name) - .Where(n => !string.IsNullOrEmpty(n)) - .ToHashSet(); + var modelNames = GetModelNames(parameterInfo); foreach (var parameter in parameters) { @@ -525,6 +519,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } + private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + { + return parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name!) + .Append(parameterInfo.Name!) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index 639cff0ca683..11ddb52b4db5 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -543,13 +543,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = parameterInfo - .GetCustomAttributes(inherit: false) - .OfType() - .Select(p => p.Name) - .Append(parameterInfo.Name) - .Where(n => !string.IsNullOrEmpty(n)) - .ToHashSet(); + var modelNames = GetModelNames(parameterInfo); foreach (var parameter in parameters) { @@ -569,6 +563,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } + private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + { + return parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name!) + .Append(parameterInfo.Name!) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index bcaa644d88eb..a24ce6881ba2 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -525,13 +525,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = parameterInfo - .GetCustomAttributes(inherit: false) - .OfType() - .Select(p => p.Name) - .Append(parameterInfo.Name) - .Where(n => !string.IsNullOrEmpty(n)) - .ToHashSet(); + var modelNames = GetModelNames(parameterInfo); foreach (var parameter in parameters) { @@ -551,6 +545,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } + private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + { + return parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name!) + .Append(parameterInfo.Name!) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs index 611e1342bc0e..9eb35baad1ea 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs @@ -504,13 +504,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = parameterInfo - .GetCustomAttributes(inherit: false) - .OfType() - .Select(p => p.Name) - .Append(parameterInfo.Name) - .Where(n => !string.IsNullOrEmpty(n)) - .ToHashSet(); + var modelNames = GetModelNames(parameterInfo); foreach (var parameter in parameters) { @@ -530,6 +524,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } + private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + { + return parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name!) + .Append(parameterInfo.Name!) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs index aa2603dcc523..1847448a90fa 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs @@ -496,13 +496,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = parameterInfo - .GetCustomAttributes(inherit: false) - .OfType() - .Select(p => p.Name) - .Append(parameterInfo.Name) - .Where(n => !string.IsNullOrEmpty(n)) - .ToHashSet(); + var modelNames = GetModelNames(parameterInfo); foreach (var parameter in parameters) { @@ -522,6 +516,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } + private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + { + return parameterInfo + .GetCustomAttributes(inherit: false) + .OfType() + .Select(p => p.Name!) + .Append(parameterInfo.Name!) + .Where(n => !string.IsNullOrEmpty(n)) + .ToHashSet(); + } + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) { if (sourceParameter is OpenApiParameterReference parameterReference) From 17db1f2e404c4e1b6686b2a7bea023d3d402ac96 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 26 Nov 2025 12:06:36 +0100 Subject: [PATCH 4/9] Also respect IModelNameProvider when getting XML docs for properties --- .../gen/XmlCommentGenerator.Emitter.cs | 52 +++++------- .../OperationTests.Controllers.cs | 79 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 52 +++++------- ...ApiXmlCommentSupport.generated.verified.cs | 52 +++++------- ...ApiXmlCommentSupport.generated.verified.cs | 52 +++++------- ...piXmlCommentSupport.generated.verified.cs} | 53 ++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 52 +++++------- ...ApiXmlCommentSupport.generated.verified.cs | 52 +++++------- ...ApiXmlCommentSupport.generated.verified.cs | 52 +++++------- ...ApiXmlCommentSupport.generated.verified.cs | 52 +++++------- ...ApiXmlCommentSupport.generated.verified.cs | 52 +++++------- ...ApiXmlCommentSupport.generated.verified.cs | 52 +++++------- 12 files changed, 283 insertions(+), 369 deletions(-) rename src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/{OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs => OperationTests.SupportsParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs} (94%) diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs index b44adb391aaa..ca2ee12ebe66 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs @@ -154,30 +154,6 @@ public static string CreateDocumentationId(this PropertyInfo property) return sb.ToString(); } - /// - /// Generates a documentation comment ID for a property given its container type and property name. - /// Example: P:Namespace.ContainingType.PropertyName - /// - public static string CreateDocumentationId(Type containerType, string propertyName) - { - if (containerType == null) - { - throw new ArgumentNullException(nameof(containerType)); - } - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName)); - } - - var sb = new StringBuilder(); - sb.Append("P:"); - sb.Append(GetTypeDocId(containerType, includeGenericArguments: false, omitGenericArity: false)); - sb.Append('.'); - sb.Append(propertyName); - - return sb.ToString(); - } - /// /// Generates a documentation comment ID for a method (or constructor). /// For example: @@ -455,10 +431,14 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform && metadata.ContainerType is { } containerType && metadata.PropertyName is { } propertyName) { - var propertyDocId = DocumentationCommentIdHelper.CreateDocumentationId(containerType, propertyName); - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyDocId), out var propertyComment)) + var propertyInfo = containerType.GetProperty(propertyName); + if (propertyInfo is null) { - var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); + continue; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var parameter = GetOperationParameter(operation, propertyInfo); var description = propertyComment.Summary; if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) { @@ -505,7 +485,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) + { + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) { var parameters = operation.Parameters; if (parameters is null) @@ -513,7 +503,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = GetModelNames(parameterInfo); + var modelNames = GetModelNames(attributeProvider, name); foreach (var parameter in parameters) { @@ -533,13 +523,13 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) { - return parameterInfo + return attributeProvider .GetCustomAttributes(inherit: false) .OfType() .Select(p => p.Name!) - .Append(parameterInfo.Name!) + .Append(name) .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs index c70d4e7c1ccb..52e4aa826527 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs @@ -143,47 +143,70 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => } [Fact] - public async Task SupportsRouteParametersWithCustomNamesFromControllers() + public async Task SupportsParametersWithCustomNamesFromControllers() { - var source = """ - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Mvc; - using Microsoft.Extensions.DependencyInjection; + var source = +""" +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; - var builder = WebApplication.CreateBuilder(); +var builder = WebApplication.CreateBuilder(); - builder.Services - .AddControllers() - .AddApplicationPart(typeof(TestController).Assembly); - builder.Services.AddOpenApi(); +builder.Services + .AddControllers() + .AddApplicationPart(typeof(TestController).Assembly); +builder.Services.AddOpenApi(); - var app = builder.Build(); +var app = builder.Build(); + +app.MapControllers(); + +app.Run(); - app.MapControllers(); +[ApiController] +[Route("[controller]")] +public class TestController : ControllerBase +{ + /// The id of the user. + [HttpGet("{user_id}")] + public string Get([FromRoute(Name = "user_id")] int userId) + { + return "Hello, World!"; + } - app.Run(); + [HttpGet] + public IEnumerable Search(Query query) + { + return []; + } +} - [ApiController] - [Route("[controller]")] - public class TestController : ControllerBase - { - /// The id of the user. - [HttpGet("{user_id}")] - public string Get([FromRoute(Name = "user_id")] int userId) - { - return "Hello, World!"; - } - } +public partial class Program {} - public partial class Program {} +public record Person(int Id, string Name); - """; +public class Query +{ + /// + /// The full name of the person. + /// + [FromQuery(Name = "full_name")] + public string? Name { get; init; } +} +"""; var generator = new XmlCommentGenerator(); await SnapshotTestHelper.Verify(source, generator, out var compilation); await SnapshotTestHelper.VerifyOpenApi(compilation, document => { - var path = document.Paths["/Test/{user_id}"].Operations[HttpMethod.Get]; - Assert.Equal("The id of the user.", path.Parameters[0].Description); + var getOperation = document.Paths["/Test/{user_id}"].Operations[HttpMethod.Get]; + Assert.Equal("user_id", getOperation.Parameters[0].Name); + Assert.Equal("The id of the user.", getOperation.Parameters[0].Description); + + var searchOperation = document.Paths["/Test"].Operations[HttpMethod.Get]; + Assert.Equal("full_name", searchOperation.Parameters[0].Name); + Assert.Equal("The full name of the person.", searchOperation.Parameters[0].Description); }); } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs index d37929229181..6a968fb01fd7 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs @@ -136,30 +136,6 @@ public static string CreateDocumentationId(this PropertyInfo property) return sb.ToString(); } - /// - /// Generates a documentation comment ID for a property given its container type and property name. - /// Example: P:Namespace.ContainingType.PropertyName - /// - public static string CreateDocumentationId(Type containerType, string propertyName) - { - if (containerType == null) - { - throw new ArgumentNullException(nameof(containerType)); - } - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName)); - } - - var sb = new StringBuilder(); - sb.Append("P:"); - sb.Append(GetTypeDocId(containerType, includeGenericArguments: false, omitGenericArity: false)); - sb.Append('.'); - sb.Append(propertyName); - - return sb.ToString(); - } - /// /// Generates a documentation comment ID for a method (or constructor). /// For example: @@ -437,10 +413,14 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform && metadata.ContainerType is { } containerType && metadata.PropertyName is { } propertyName) { - var propertyDocId = DocumentationCommentIdHelper.CreateDocumentationId(containerType, propertyName); - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyDocId), out var propertyComment)) + var propertyInfo = containerType.GetProperty(propertyName); + if (propertyInfo is null) { - var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); + continue; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var parameter = GetOperationParameter(operation, propertyInfo); var description = propertyComment.Summary; if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) { @@ -487,7 +467,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) + { + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) { var parameters = operation.Parameters; if (parameters is null) @@ -495,7 +485,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = GetModelNames(parameterInfo); + var modelNames = GetModelNames(attributeProvider, name); foreach (var parameter in parameters) { @@ -515,13 +505,13 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) { - return parameterInfo + return attributeProvider .GetCustomAttributes(inherit: false) .OfType() .Select(p => p.Name!) - .Append(parameterInfo.Name!) + .Append(name) .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs index 1d5e784e779e..5d9dd1a044c7 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs @@ -165,30 +165,6 @@ public static string CreateDocumentationId(this PropertyInfo property) return sb.ToString(); } - /// - /// Generates a documentation comment ID for a property given its container type and property name. - /// Example: P:Namespace.ContainingType.PropertyName - /// - public static string CreateDocumentationId(Type containerType, string propertyName) - { - if (containerType == null) - { - throw new ArgumentNullException(nameof(containerType)); - } - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName)); - } - - var sb = new StringBuilder(); - sb.Append("P:"); - sb.Append(GetTypeDocId(containerType, includeGenericArguments: false, omitGenericArity: false)); - sb.Append('.'); - sb.Append(propertyName); - - return sb.ToString(); - } - /// /// Generates a documentation comment ID for a method (or constructor). /// For example: @@ -466,10 +442,14 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform && metadata.ContainerType is { } containerType && metadata.PropertyName is { } propertyName) { - var propertyDocId = DocumentationCommentIdHelper.CreateDocumentationId(containerType, propertyName); - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyDocId), out var propertyComment)) + var propertyInfo = containerType.GetProperty(propertyName); + if (propertyInfo is null) { - var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); + continue; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var parameter = GetOperationParameter(operation, propertyInfo); var description = propertyComment.Summary; if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) { @@ -516,7 +496,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) + { + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) { var parameters = operation.Parameters; if (parameters is null) @@ -524,7 +514,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = GetModelNames(parameterInfo); + var modelNames = GetModelNames(attributeProvider, name); foreach (var parameter in parameters) { @@ -544,13 +534,13 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) { - return parameterInfo + return attributeProvider .GetCustomAttributes(inherit: false) .OfType() .Select(p => p.Name!) - .Append(parameterInfo.Name!) + .Append(name) .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index a2cdfb43f294..9712adc9757d 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -263,30 +263,6 @@ public static string CreateDocumentationId(this PropertyInfo property) return sb.ToString(); } - /// - /// Generates a documentation comment ID for a property given its container type and property name. - /// Example: P:Namespace.ContainingType.PropertyName - /// - public static string CreateDocumentationId(Type containerType, string propertyName) - { - if (containerType == null) - { - throw new ArgumentNullException(nameof(containerType)); - } - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName)); - } - - var sb = new StringBuilder(); - sb.Append("P:"); - sb.Append(GetTypeDocId(containerType, includeGenericArguments: false, omitGenericArity: false)); - sb.Append('.'); - sb.Append(propertyName); - - return sb.ToString(); - } - /// /// Generates a documentation comment ID for a method (or constructor). /// For example: @@ -564,10 +540,14 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform && metadata.ContainerType is { } containerType && metadata.PropertyName is { } propertyName) { - var propertyDocId = DocumentationCommentIdHelper.CreateDocumentationId(containerType, propertyName); - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyDocId), out var propertyComment)) + var propertyInfo = containerType.GetProperty(propertyName); + if (propertyInfo is null) { - var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); + continue; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var parameter = GetOperationParameter(operation, propertyInfo); var description = propertyComment.Summary; if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) { @@ -614,7 +594,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) + { + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) { var parameters = operation.Parameters; if (parameters is null) @@ -622,7 +612,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = GetModelNames(parameterInfo); + var modelNames = GetModelNames(attributeProvider, name); foreach (var parameter in parameters) { @@ -642,13 +632,13 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) { - return parameterInfo + return attributeProvider .GetCustomAttributes(inherit: false) .OfType() .Select(p => p.Name!) - .Append(parameterInfo.Name!) + .Append(name) .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs similarity index 94% rename from src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs rename to src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 08a86fb2fab4..cceb01954e4e 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -72,6 +72,7 @@ private static Dictionary GenerateCacheEntries() { var cache = new Dictionary(); + cache.Add(@"P:Query.Name", new XmlComment(@"The full name of the person.", null, null, null, null, false, null, null, null)); cache.Add(@"M:TestController.Get(System.Int32)", new XmlComment(null, null, null, null, null, false, null, [new XmlParameterComment(@"userId", @"The id of the user.", null, false)], null)); return cache; @@ -137,30 +138,6 @@ public static string CreateDocumentationId(this PropertyInfo property) return sb.ToString(); } - /// - /// Generates a documentation comment ID for a property given its container type and property name. - /// Example: P:Namespace.ContainingType.PropertyName - /// - public static string CreateDocumentationId(Type containerType, string propertyName) - { - if (containerType == null) - { - throw new ArgumentNullException(nameof(containerType)); - } - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName)); - } - - var sb = new StringBuilder(); - sb.Append("P:"); - sb.Append(GetTypeDocId(containerType, includeGenericArguments: false, omitGenericArity: false)); - sb.Append('.'); - sb.Append(propertyName); - - return sb.ToString(); - } - /// /// Generates a documentation comment ID for a method (or constructor). /// For example: @@ -438,10 +415,14 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform && metadata.ContainerType is { } containerType && metadata.PropertyName is { } propertyName) { - var propertyDocId = DocumentationCommentIdHelper.CreateDocumentationId(containerType, propertyName); - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyDocId), out var propertyComment)) + var propertyInfo = containerType.GetProperty(propertyName); + if (propertyInfo is null) { - var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); + continue; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var parameter = GetOperationParameter(operation, propertyInfo); var description = propertyComment.Summary; if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) { @@ -488,7 +469,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) + { + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) { var parameters = operation.Parameters; if (parameters is null) @@ -496,7 +487,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = GetModelNames(parameterInfo); + var modelNames = GetModelNames(attributeProvider, name); foreach (var parameter in parameters) { @@ -516,13 +507,13 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) { - return parameterInfo + return attributeProvider .GetCustomAttributes(inherit: false) .OfType() .Select(p => p.Name!) - .Append(parameterInfo.Name!) + .Append(name) .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 08a86fb2fab4..e28e4b300b8b 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -137,30 +137,6 @@ public static string CreateDocumentationId(this PropertyInfo property) return sb.ToString(); } - /// - /// Generates a documentation comment ID for a property given its container type and property name. - /// Example: P:Namespace.ContainingType.PropertyName - /// - public static string CreateDocumentationId(Type containerType, string propertyName) - { - if (containerType == null) - { - throw new ArgumentNullException(nameof(containerType)); - } - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName)); - } - - var sb = new StringBuilder(); - sb.Append("P:"); - sb.Append(GetTypeDocId(containerType, includeGenericArguments: false, omitGenericArity: false)); - sb.Append('.'); - sb.Append(propertyName); - - return sb.ToString(); - } - /// /// Generates a documentation comment ID for a method (or constructor). /// For example: @@ -438,10 +414,14 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform && metadata.ContainerType is { } containerType && metadata.PropertyName is { } propertyName) { - var propertyDocId = DocumentationCommentIdHelper.CreateDocumentationId(containerType, propertyName); - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyDocId), out var propertyComment)) + var propertyInfo = containerType.GetProperty(propertyName); + if (propertyInfo is null) { - var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); + continue; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var parameter = GetOperationParameter(operation, propertyInfo); var description = propertyComment.Summary; if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) { @@ -488,7 +468,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) + { + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) { var parameters = operation.Parameters; if (parameters is null) @@ -496,7 +486,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = GetModelNames(parameterInfo); + var modelNames = GetModelNames(attributeProvider, name); foreach (var parameter in parameters) { @@ -516,13 +506,13 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) { - return parameterInfo + return attributeProvider .GetCustomAttributes(inherit: false) .OfType() .Select(p => p.Name!) - .Append(parameterInfo.Name!) + .Append(name) .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 913cc0517b86..60993f3d5732 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -140,30 +140,6 @@ public static string CreateDocumentationId(this PropertyInfo property) return sb.ToString(); } - /// - /// Generates a documentation comment ID for a property given its container type and property name. - /// Example: P:Namespace.ContainingType.PropertyName - /// - public static string CreateDocumentationId(Type containerType, string propertyName) - { - if (containerType == null) - { - throw new ArgumentNullException(nameof(containerType)); - } - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName)); - } - - var sb = new StringBuilder(); - sb.Append("P:"); - sb.Append(GetTypeDocId(containerType, includeGenericArguments: false, omitGenericArity: false)); - sb.Append('.'); - sb.Append(propertyName); - - return sb.ToString(); - } - /// /// Generates a documentation comment ID for a method (or constructor). /// For example: @@ -441,10 +417,14 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform && metadata.ContainerType is { } containerType && metadata.PropertyName is { } propertyName) { - var propertyDocId = DocumentationCommentIdHelper.CreateDocumentationId(containerType, propertyName); - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyDocId), out var propertyComment)) + var propertyInfo = containerType.GetProperty(propertyName); + if (propertyInfo is null) { - var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); + continue; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var parameter = GetOperationParameter(operation, propertyInfo); var description = propertyComment.Summary; if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) { @@ -491,7 +471,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) + { + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) { var parameters = operation.Parameters; if (parameters is null) @@ -499,7 +489,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = GetModelNames(parameterInfo); + var modelNames = GetModelNames(attributeProvider, name); foreach (var parameter in parameters) { @@ -519,13 +509,13 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) { - return parameterInfo + return attributeProvider .GetCustomAttributes(inherit: false) .OfType() .Select(p => p.Name!) - .Append(parameterInfo.Name!) + .Append(name) .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index 11ddb52b4db5..9c42e3edb6b5 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -184,30 +184,6 @@ public static string CreateDocumentationId(this PropertyInfo property) return sb.ToString(); } - /// - /// Generates a documentation comment ID for a property given its container type and property name. - /// Example: P:Namespace.ContainingType.PropertyName - /// - public static string CreateDocumentationId(Type containerType, string propertyName) - { - if (containerType == null) - { - throw new ArgumentNullException(nameof(containerType)); - } - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName)); - } - - var sb = new StringBuilder(); - sb.Append("P:"); - sb.Append(GetTypeDocId(containerType, includeGenericArguments: false, omitGenericArity: false)); - sb.Append('.'); - sb.Append(propertyName); - - return sb.ToString(); - } - /// /// Generates a documentation comment ID for a method (or constructor). /// For example: @@ -485,10 +461,14 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform && metadata.ContainerType is { } containerType && metadata.PropertyName is { } propertyName) { - var propertyDocId = DocumentationCommentIdHelper.CreateDocumentationId(containerType, propertyName); - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyDocId), out var propertyComment)) + var propertyInfo = containerType.GetProperty(propertyName); + if (propertyInfo is null) { - var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); + continue; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var parameter = GetOperationParameter(operation, propertyInfo); var description = propertyComment.Summary; if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) { @@ -535,7 +515,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) + { + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) { var parameters = operation.Parameters; if (parameters is null) @@ -543,7 +533,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = GetModelNames(parameterInfo); + var modelNames = GetModelNames(attributeProvider, name); foreach (var parameter in parameters) { @@ -563,13 +553,13 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) { - return parameterInfo + return attributeProvider .GetCustomAttributes(inherit: false) .OfType() .Select(p => p.Name!) - .Append(parameterInfo.Name!) + .Append(name) .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index a24ce6881ba2..f7f5f7c256fe 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -166,30 +166,6 @@ public static string CreateDocumentationId(this PropertyInfo property) return sb.ToString(); } - /// - /// Generates a documentation comment ID for a property given its container type and property name. - /// Example: P:Namespace.ContainingType.PropertyName - /// - public static string CreateDocumentationId(Type containerType, string propertyName) - { - if (containerType == null) - { - throw new ArgumentNullException(nameof(containerType)); - } - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName)); - } - - var sb = new StringBuilder(); - sb.Append("P:"); - sb.Append(GetTypeDocId(containerType, includeGenericArguments: false, omitGenericArity: false)); - sb.Append('.'); - sb.Append(propertyName); - - return sb.ToString(); - } - /// /// Generates a documentation comment ID for a method (or constructor). /// For example: @@ -467,10 +443,14 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform && metadata.ContainerType is { } containerType && metadata.PropertyName is { } propertyName) { - var propertyDocId = DocumentationCommentIdHelper.CreateDocumentationId(containerType, propertyName); - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyDocId), out var propertyComment)) + var propertyInfo = containerType.GetProperty(propertyName); + if (propertyInfo is null) { - var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); + continue; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var parameter = GetOperationParameter(operation, propertyInfo); var description = propertyComment.Summary; if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) { @@ -517,7 +497,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) + { + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) { var parameters = operation.Parameters; if (parameters is null) @@ -525,7 +515,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = GetModelNames(parameterInfo); + var modelNames = GetModelNames(attributeProvider, name); foreach (var parameter in parameters) { @@ -545,13 +535,13 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) { - return parameterInfo + return attributeProvider .GetCustomAttributes(inherit: false) .OfType() .Select(p => p.Name!) - .Append(parameterInfo.Name!) + .Append(name) .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs index 9eb35baad1ea..861fe791df70 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs @@ -145,30 +145,6 @@ public static string CreateDocumentationId(this PropertyInfo property) return sb.ToString(); } - /// - /// Generates a documentation comment ID for a property given its container type and property name. - /// Example: P:Namespace.ContainingType.PropertyName - /// - public static string CreateDocumentationId(Type containerType, string propertyName) - { - if (containerType == null) - { - throw new ArgumentNullException(nameof(containerType)); - } - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName)); - } - - var sb = new StringBuilder(); - sb.Append("P:"); - sb.Append(GetTypeDocId(containerType, includeGenericArguments: false, omitGenericArity: false)); - sb.Append('.'); - sb.Append(propertyName); - - return sb.ToString(); - } - /// /// Generates a documentation comment ID for a method (or constructor). /// For example: @@ -446,10 +422,14 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform && metadata.ContainerType is { } containerType && metadata.PropertyName is { } propertyName) { - var propertyDocId = DocumentationCommentIdHelper.CreateDocumentationId(containerType, propertyName); - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyDocId), out var propertyComment)) + var propertyInfo = containerType.GetProperty(propertyName); + if (propertyInfo is null) { - var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); + continue; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var parameter = GetOperationParameter(operation, propertyInfo); var description = propertyComment.Summary; if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) { @@ -496,7 +476,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) + { + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) { var parameters = operation.Parameters; if (parameters is null) @@ -504,7 +494,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = GetModelNames(parameterInfo); + var modelNames = GetModelNames(attributeProvider, name); foreach (var parameter in parameters) { @@ -524,13 +514,13 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) { - return parameterInfo + return attributeProvider .GetCustomAttributes(inherit: false) .OfType() .Select(p => p.Name!) - .Append(parameterInfo.Name!) + .Append(name) .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs index 1847448a90fa..463928213e9d 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs @@ -137,30 +137,6 @@ public static string CreateDocumentationId(this PropertyInfo property) return sb.ToString(); } - /// - /// Generates a documentation comment ID for a property given its container type and property name. - /// Example: P:Namespace.ContainingType.PropertyName - /// - public static string CreateDocumentationId(Type containerType, string propertyName) - { - if (containerType == null) - { - throw new ArgumentNullException(nameof(containerType)); - } - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName)); - } - - var sb = new StringBuilder(); - sb.Append("P:"); - sb.Append(GetTypeDocId(containerType, includeGenericArguments: false, omitGenericArity: false)); - sb.Append('.'); - sb.Append(propertyName); - - return sb.ToString(); - } - /// /// Generates a documentation comment ID for a method (or constructor). /// For example: @@ -438,10 +414,14 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform && metadata.ContainerType is { } containerType && metadata.PropertyName is { } propertyName) { - var propertyDocId = DocumentationCommentIdHelper.CreateDocumentationId(containerType, propertyName); - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyDocId), out var propertyComment)) + var propertyInfo = containerType.GetProperty(propertyName); + if (propertyInfo is null) { - var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); + continue; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var parameter = GetOperationParameter(operation, propertyInfo); var description = propertyComment.Summary; if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) { @@ -488,7 +468,17 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) + { + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + } + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) { var parameters = operation.Parameters; if (parameters is null) @@ -496,7 +486,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - var modelNames = GetModelNames(parameterInfo); + var modelNames = GetModelNames(attributeProvider, name); foreach (var parameter in parameters) { @@ -516,13 +506,13 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ParameterInfo parameterInfo) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) { - return parameterInfo + return attributeProvider .GetCustomAttributes(inherit: false) .OfType() .Select(p => p.Name!) - .Append(parameterInfo.Name!) + .Append(name) .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); } From fdfaa69d1acc1e7bcdd41c13259122943eb7b2d3 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 26 Nov 2025 12:40:10 +0100 Subject: [PATCH 5/9] Fix nullability warnings from Copilot --- .../gen/XmlCommentGenerator.Emitter.cs | 21 ++++++++++++------- .../OperationTests.Controllers.cs | 2 ++ ...ApiXmlCommentSupport.generated.verified.cs | 21 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 21 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 21 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 21 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 21 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 21 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 21 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 21 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 21 ++++++++++++------- ...ApiXmlCommentSupport.generated.verified.cs | 21 ++++++++++++------- 12 files changed, 145 insertions(+), 88 deletions(-) diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs index ca2ee12ebe66..eba629cc35df 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs @@ -370,7 +370,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform { continue; } - var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { @@ -487,15 +486,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) { - return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name); } private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) { - return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name); } - private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string? name) { var parameters = operation.Parameters; if (parameters is null) @@ -523,15 +522,21 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string? name) { - return attributeProvider + var modelNames = attributeProvider .GetCustomAttributes(inherit: false) .OfType() + .Where(p => !string.IsNullOrEmpty(p.Name)) .Select(p => p.Name!) - .Append(name) - .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); + + if (!string.IsNullOrEmpty(name)) + { + modelNames.Add(name); + } + + return modelNames; } private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs index 52e4aa826527..b21829b22194 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Net.Http; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests; diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs index 6a968fb01fd7..3c283349bd3a 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs @@ -352,7 +352,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform { continue; } - var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { @@ -469,15 +468,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) { - return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name); } private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) { - return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name); } - private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string? name) { var parameters = operation.Parameters; if (parameters is null) @@ -505,15 +504,21 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string? name) { - return attributeProvider + var modelNames = attributeProvider .GetCustomAttributes(inherit: false) .OfType() + .Where(p => !string.IsNullOrEmpty(p.Name)) .Select(p => p.Name!) - .Append(name) - .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); + + if (!string.IsNullOrEmpty(name)) + { + modelNames.Add(name); + } + + return modelNames; } private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs index 5d9dd1a044c7..cf166b710183 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs @@ -381,7 +381,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform { continue; } - var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { @@ -498,15 +497,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) { - return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name); } private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) { - return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name); } - private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string? name) { var parameters = operation.Parameters; if (parameters is null) @@ -534,15 +533,21 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string? name) { - return attributeProvider + var modelNames = attributeProvider .GetCustomAttributes(inherit: false) .OfType() + .Where(p => !string.IsNullOrEmpty(p.Name)) .Select(p => p.Name!) - .Append(name) - .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); + + if (!string.IsNullOrEmpty(name)) + { + modelNames.Add(name); + } + + return modelNames; } private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 9712adc9757d..32983388e5cc 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -479,7 +479,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform { continue; } - var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { @@ -596,15 +595,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) { - return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name); } private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) { - return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name); } - private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string? name) { var parameters = operation.Parameters; if (parameters is null) @@ -632,15 +631,21 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string? name) { - return attributeProvider + var modelNames = attributeProvider .GetCustomAttributes(inherit: false) .OfType() + .Where(p => !string.IsNullOrEmpty(p.Name)) .Select(p => p.Name!) - .Append(name) - .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); + + if (!string.IsNullOrEmpty(name)) + { + modelNames.Add(name); + } + + return modelNames; } private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index cceb01954e4e..ed98af100c8b 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -354,7 +354,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform { continue; } - var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { @@ -471,15 +470,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) { - return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name); } private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) { - return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name); } - private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string? name) { var parameters = operation.Parameters; if (parameters is null) @@ -507,15 +506,21 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string? name) { - return attributeProvider + var modelNames = attributeProvider .GetCustomAttributes(inherit: false) .OfType() + .Where(p => !string.IsNullOrEmpty(p.Name)) .Select(p => p.Name!) - .Append(name) - .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); + + if (!string.IsNullOrEmpty(name)) + { + modelNames.Add(name); + } + + return modelNames; } private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index e28e4b300b8b..c5d9ca7502b7 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -353,7 +353,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform { continue; } - var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { @@ -470,15 +469,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) { - return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name); } private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) { - return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name); } - private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string? name) { var parameters = operation.Parameters; if (parameters is null) @@ -506,15 +505,21 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string? name) { - return attributeProvider + var modelNames = attributeProvider .GetCustomAttributes(inherit: false) .OfType() + .Where(p => !string.IsNullOrEmpty(p.Name)) .Select(p => p.Name!) - .Append(name) - .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); + + if (!string.IsNullOrEmpty(name)) + { + modelNames.Add(name); + } + + return modelNames; } private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 60993f3d5732..b98f04bebf34 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -356,7 +356,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform { continue; } - var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { @@ -473,15 +472,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) { - return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name); } private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) { - return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name); } - private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string? name) { var parameters = operation.Parameters; if (parameters is null) @@ -509,15 +508,21 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string? name) { - return attributeProvider + var modelNames = attributeProvider .GetCustomAttributes(inherit: false) .OfType() + .Where(p => !string.IsNullOrEmpty(p.Name)) .Select(p => p.Name!) - .Append(name) - .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); + + if (!string.IsNullOrEmpty(name)) + { + modelNames.Add(name); + } + + return modelNames; } private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index 9c42e3edb6b5..9c2cf8a23e7a 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -400,7 +400,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform { continue; } - var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { @@ -517,15 +516,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) { - return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name); } private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) { - return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name); } - private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string? name) { var parameters = operation.Parameters; if (parameters is null) @@ -553,15 +552,21 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string? name) { - return attributeProvider + var modelNames = attributeProvider .GetCustomAttributes(inherit: false) .OfType() + .Where(p => !string.IsNullOrEmpty(p.Name)) .Select(p => p.Name!) - .Append(name) - .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); + + if (!string.IsNullOrEmpty(name)) + { + modelNames.Add(name); + } + + return modelNames; } private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index f7f5f7c256fe..9cf04c380acc 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -382,7 +382,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform { continue; } - var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { @@ -499,15 +498,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) { - return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name); } private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) { - return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name); } - private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string? name) { var parameters = operation.Parameters; if (parameters is null) @@ -535,15 +534,21 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string? name) { - return attributeProvider + var modelNames = attributeProvider .GetCustomAttributes(inherit: false) .OfType() + .Where(p => !string.IsNullOrEmpty(p.Name)) .Select(p => p.Name!) - .Append(name) - .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); + + if (!string.IsNullOrEmpty(name)) + { + modelNames.Add(name); + } + + return modelNames; } private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs index 861fe791df70..093f86424f05 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs @@ -361,7 +361,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform { continue; } - var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { @@ -478,15 +477,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) { - return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name); } private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) { - return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name); } - private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string? name) { var parameters = operation.Parameters; if (parameters is null) @@ -514,15 +513,21 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string? name) { - return attributeProvider + var modelNames = attributeProvider .GetCustomAttributes(inherit: false) .OfType() + .Where(p => !string.IsNullOrEmpty(p.Name)) .Select(p => p.Name!) - .Append(name) - .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); + + if (!string.IsNullOrEmpty(name)) + { + modelNames.Add(name); + } + + return modelNames; } private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs index 463928213e9d..5181bbfa0370 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs @@ -353,7 +353,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform { continue; } - var operationParameter = GetOperationParameter(operation, parameterInfo); if (operationParameter is not null) { @@ -470,15 +469,15 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) { - return GetOperationParameter(operation, propertyInfo, propertyInfo.Name!); + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name); } private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) { - return GetOperationParameter(operation, parameterInfo, parameterInfo.Name!); + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name); } - private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string name) + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string? name) { var parameters = operation.Parameters; if (parameters is null) @@ -506,15 +505,21 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return null; } - private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string name) + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string? name) { - return attributeProvider + var modelNames = attributeProvider .GetCustomAttributes(inherit: false) .OfType() + .Where(p => !string.IsNullOrEmpty(p.Name)) .Select(p => p.Name!) - .Append(name) - .Where(n => !string.IsNullOrEmpty(n)) .ToHashSet(); + + if (!string.IsNullOrEmpty(name)) + { + modelNames.Add(name); + } + + return modelNames; } private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) From be0a22fefe226470893d13df31c4e2a97ba7ca84 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 26 Nov 2025 12:54:22 +0100 Subject: [PATCH 6/9] Add test to verify cancellation token description --- .../OperationTests.Controllers.cs | 49 ++ ...ApiXmlCommentSupport.generated.verified.cs | 650 ++++++++++++++++++ 2 files changed, 699 insertions(+) create mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.ShouldNotApplyCancellationTokenDocumentationToRequestBody#OpenApiXmlCommentSupport.generated.verified.cs diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs index b21829b22194..5c2bdf39ef3e 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs @@ -211,4 +211,53 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => Assert.Equal("The full name of the person.", searchOperation.Parameters[0].Description); }); } + + [Fact] + public async Task ShouldNotApplyCancellationTokenDocumentationToRequestBody() + { + var source = +""" +using System.Collections.Generic; +using System.Threading; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; + +var builder = WebApplication.CreateBuilder(); + +builder.Services + .AddControllers() + .AddApplicationPart(typeof(TestController).Assembly); +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +app.MapControllers(); + +app.Run(); + +[ApiController] +[Route("[controller]")] +public class TestController : ControllerBase +{ + /// The cancellation token. + [HttpGet] + public ActionResult Create(Person person, CancellationToken cancellationToken) + { + return Created(); + } +} + +public partial class Program {} + +public record Person(int Id, string Name); +"""; + var generator = new XmlCommentGenerator(); + await SnapshotTestHelper.Verify(source, generator, out var compilation); + await SnapshotTestHelper.VerifyOpenApi(compilation, document => + { + var getOperation = document.Paths["/Test"].Operations[HttpMethod.Get]; + Assert.NotEqual("The cancellation token.", getOperation.RequestBody.Description); + }); + } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.ShouldNotApplyCancellationTokenDocumentationToRequestBody#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.ShouldNotApplyCancellationTokenDocumentationToRequestBody#OpenApiXmlCommentSupport.generated.verified.cs new file mode 100644 index 000000000000..cc830340b490 --- /dev/null +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.ShouldNotApplyCancellationTokenDocumentationToRequestBody#OpenApiXmlCommentSupport.generated.verified.cs @@ -0,0 +1,650 @@ +//HintName: OpenApiXmlCommentSupport.generated.cs +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable +// Suppress warnings about obsolete types and members +// in generated code +#pragma warning disable CS0612, CS0618 + +namespace System.Runtime.CompilerServices +{ + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : System.Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + } + } +} + +namespace Microsoft.AspNetCore.OpenApi.Generated +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Text.Json; + using System.Text.Json.Nodes; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.AspNetCore.OpenApi; + using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.AspNetCore.Mvc.ModelBinding; + using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.OpenApi; + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file record XmlComment( + string? Summary, + string? Description, + string? Remarks, + string? Returns, + string? Value, + bool Deprecated, + List? Examples, + List? Parameters, + List? Responses); + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file record XmlParameterComment(string? Name, string? Description, string? Example, bool Deprecated); + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file record XmlResponseComment(string Code, string? Description, string? Example); + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class XmlCommentCache + { + private static Dictionary? _cache; + public static Dictionary Cache => _cache ??= GenerateCacheEntries(); + + private static Dictionary GenerateCacheEntries() + { + var cache = new Dictionary(); + + cache.Add(@"M:TestController.Create(Person,System.Threading.CancellationToken)", new XmlComment(null, null, null, null, null, false, null, [new XmlParameterComment(@"cancellationToken", @"The cancellation token.", null, false)], null)); + + return cache; + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class DocumentationCommentIdHelper + { + /// + /// Generates a documentation comment ID for a type. + /// Example: T:Namespace.Outer+Inner`1 becomes T:Namespace.Outer.Inner`1 + /// + public static string CreateDocumentationId(this Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + return "T:" + GetTypeDocId(type, includeGenericArguments: false, omitGenericArity: false); + } + + /// + /// Generates a documentation comment ID for a property. + /// Example: P:Namespace.ContainingType.PropertyName or for an indexer P:Namespace.ContainingType.Item(System.Int32) + /// + public static string CreateDocumentationId(this PropertyInfo property) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + + var sb = new StringBuilder(); + sb.Append("P:"); + + if (property.DeclaringType != null) + { + sb.Append(GetTypeDocId(property.DeclaringType, includeGenericArguments: false, omitGenericArity: false)); + } + + sb.Append('.'); + sb.Append(property.Name); + + // For indexers, include the parameter list. + var indexParams = property.GetIndexParameters(); + if (indexParams.Length > 0) + { + sb.Append('('); + for (int i = 0; i < indexParams.Length; i++) + { + if (i > 0) + { + sb.Append(','); + } + + sb.Append(GetTypeDocId(indexParams[i].ParameterType, includeGenericArguments: true, omitGenericArity: false)); + } + sb.Append(')'); + } + + return sb.ToString(); + } + + /// + /// Generates a documentation comment ID for a method (or constructor). + /// For example: + /// M:Namespace.ContainingType.MethodName(ParamType1,ParamType2)~ReturnType + /// M:Namespace.ContainingType.#ctor(ParamType) + /// + public static string CreateDocumentationId(this MethodInfo method) + { + if (method == null) + { + throw new ArgumentNullException(nameof(method)); + } + + var sb = new StringBuilder(); + sb.Append("M:"); + + // Append the fully qualified name of the declaring type. + if (method.DeclaringType != null) + { + sb.Append(GetTypeDocId(method.DeclaringType, includeGenericArguments: false, omitGenericArity: false)); + } + + sb.Append('.'); + + // Append the method name, handling constructors specially. + if (method.IsConstructor) + { + sb.Append(method.IsStatic ? "#cctor" : "#ctor"); + } + else + { + sb.Append(method.Name); + if (method.IsGenericMethod) + { + sb.Append("``"); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}", method.GetGenericArguments().Length); + } + } + + // Append the parameter list, if any. + var parameters = method.GetParameters(); + if (parameters.Length > 0) + { + sb.Append('('); + for (int i = 0; i < parameters.Length; i++) + { + if (i > 0) + { + sb.Append(','); + } + + // Omit the generic arity for the parameter type. + sb.Append(GetTypeDocId(parameters[i].ParameterType, includeGenericArguments: true, omitGenericArity: true)); + } + sb.Append(')'); + } + + // Append the return type after a '~' (if the method returns a value). + if (method.ReturnType != typeof(void)) + { + sb.Append('~'); + // Omit the generic arity for the return type. + sb.Append(GetTypeDocId(method.ReturnType, includeGenericArguments: true, omitGenericArity: true)); + } + + return sb.ToString(); + } + + /// + /// Generates a documentation ID string for a type. + /// This method handles nested types (replacing '+' with '.'), + /// generic types, arrays, pointers, by-ref types, and generic parameters. + /// The flag controls whether + /// constructed generic type arguments are emitted, while + /// controls whether the generic arity marker (e.g. "`1") is appended. + /// + private static string GetTypeDocId(Type type, bool includeGenericArguments, bool omitGenericArity) + { + if (type.IsGenericParameter) + { + // Use `` for method-level generic parameters and ` for type-level. + if (type.DeclaringMethod != null) + { + return "``" + type.GenericParameterPosition; + } + else if (type.DeclaringType != null) + { + return "`" + type.GenericParameterPosition; + } + else + { + return type.Name; + } + } + + if (type.IsGenericType) + { + Type genericDef = type.GetGenericTypeDefinition(); + string fullName = genericDef.FullName ?? genericDef.Name; + + var sb = new StringBuilder(fullName.Length); + + // Replace '+' with '.' for nested types + for (var i = 0; i < fullName.Length; i++) + { + char c = fullName[i]; + if (c == '+') + { + sb.Append('.'); + } + else if (c == '`') + { + break; + } + else + { + sb.Append(c); + } + } + + if (!omitGenericArity) + { + int arity = genericDef.GetGenericArguments().Length; + sb.Append('`'); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}", arity); + } + + if (includeGenericArguments && !type.IsGenericTypeDefinition) + { + var typeArgs = type.GetGenericArguments(); + sb.Append('{'); + + for (int i = 0; i < typeArgs.Length; i++) + { + if (i > 0) + { + sb.Append(','); + } + + sb.Append(GetTypeDocId(typeArgs[i], includeGenericArguments, omitGenericArity)); + } + + sb.Append('}'); + } + + return sb.ToString(); + } + + // For non-generic types, use FullName (if available) and replace nested type separators. + return (type.FullName ?? type.Name).Replace('+', '.'); + } + + /// + /// Normalizes a documentation comment ID to match the compiler-style format. + /// Strips the return type suffix for ordinary methods but retains it for conversion operators. + /// + /// The documentation comment ID to normalize. + /// The normalized documentation comment ID. + public static string NormalizeDocId(string docId) + { + // Find the tilde character that indicates the return type suffix + var tildeIndex = docId.IndexOf('~'); + if (tildeIndex == -1) + { + // No return type suffix, return as-is + return docId; + } + + // Check if this is a conversion operator (op_Implicit or op_Explicit) + // For these operators, we need to keep the return type suffix + if (docId.Contains("op_Implicit") || docId.Contains("op_Explicit")) + { + return docId; + } + + // For ordinary methods, strip the return type suffix + return docId.Substring(0, tildeIndex); + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file class XmlCommentOperationTransformer : IOpenApiOperationTransformer + { + public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken) + { + var methodInfo = context.Description.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor + ? controllerActionDescriptor.MethodInfo + : context.Description.ActionDescriptor.EndpointMetadata.OfType().SingleOrDefault(); + + if (methodInfo is null) + { + return Task.CompletedTask; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(methodInfo.CreateDocumentationId()), out var methodComment)) + { + if (methodComment.Summary is { } summary) + { + operation.Summary = summary; + } + if (methodComment.Description is { } description) + { + operation.Description = description; + } + if (methodComment.Remarks is { } remarks) + { + operation.Description = remarks; + } + if (methodComment.Parameters is { Count: > 0}) + { + foreach (var parameterComment in methodComment.Parameters) + { + var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); + if (parameterInfo is null) + { + continue; + } + var operationParameter = GetOperationParameter(operation, parameterInfo); + if (operationParameter is not null) + { + var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); + targetOperationParameter.Description = parameterComment.Description; + if (parameterComment.Example is { } jsonString) + { + targetOperationParameter.Example = jsonString.Parse(); + } + targetOperationParameter.Deprecated = parameterComment.Deprecated; + } + else + { + var requestBody = operation.RequestBody; + if (requestBody is not null) + { + requestBody.Description = parameterComment.Description; + if (parameterComment.Example is { } jsonString) + { + var content = requestBody?.Content?.Values; + if (content is null) + { + continue; + } + foreach (var mediaType in content) + { + mediaType.Example = jsonString.Parse(); + } + } + } + } + } + } + // Applies `` on XML comments for operation with single response value. + if (methodComment.Returns is { } returns && operation.Responses is { Count: 1 }) + { + var response = operation.Responses.First(); + response.Value.Description = returns; + } + // Applies `` on XML comments for operation with multiple response values. + if (methodComment.Responses is { Count: > 0} && operation.Responses is { Count: > 0 }) + { + foreach (var response in operation.Responses) + { + var responseComment = methodComment.Responses.SingleOrDefault(xmlResponse => xmlResponse.Code == response.Key); + if (responseComment is not null) + { + response.Value.Description = responseComment.Description; + } + } + } + } + foreach (var parameterDescription in context.Description.ParameterDescriptions) + { + var metadata = parameterDescription.ModelMetadata; + if (metadata is not null + && metadata.MetadataKind == ModelMetadataKind.Property + && metadata.ContainerType is { } containerType + && metadata.PropertyName is { } propertyName) + { + var propertyInfo = containerType.GetProperty(propertyName); + if (propertyInfo is null) + { + continue; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var parameter = GetOperationParameter(operation, propertyInfo); + var description = propertyComment.Summary; + if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) + { + description = $"{description}\n{propertyComment.Value}"; + } + else if (string.IsNullOrEmpty(description)) + { + description = propertyComment.Value; + } + if (parameter is null) + { + if (operation.RequestBody is not null) + { + operation.RequestBody.Description = description; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + var content = operation.RequestBody.Content?.Values; + if (content is null) + { + continue; + } + var parsedExample = jsonString.Parse(); + foreach (var mediaType in content) + { + mediaType.Example = parsedExample; + } + } + } + continue; + } + var targetOperationParameter = UnwrapOpenApiParameter(parameter); + if (targetOperationParameter is not null) + { + targetOperationParameter.Description = description; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + targetOperationParameter.Example = jsonString.Parse(); + } + } + } + } + } + + return Task.CompletedTask; + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) + { + return GetOperationParameter(operation, propertyInfo, propertyInfo.Name); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo parameterInfo) + { + return GetOperationParameter(operation, parameterInfo, parameterInfo.Name); + } + + private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider attributeProvider, string? name) + { + var parameters = operation.Parameters; + if (parameters is null) + { + return null; + } + + var modelNames = GetModelNames(attributeProvider, name); + + foreach (var parameter in parameters) + { + var parameterName = parameter.Name; + + if (string.IsNullOrEmpty(parameterName)) + { + continue; + } + + if (modelNames.Contains(parameterName)) + { + return parameter; + } + } + + return null; + } + + private static IReadOnlySet GetModelNames(ICustomAttributeProvider attributeProvider, string? name) + { + var modelNames = attributeProvider + .GetCustomAttributes(inherit: false) + .OfType() + .Where(p => !string.IsNullOrEmpty(p.Name)) + .Select(p => p.Name!) + .ToHashSet(); + + if (!string.IsNullOrEmpty(name)) + { + modelNames.Add(name); + } + + return modelNames; + } + + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) + { + if (sourceParameter is OpenApiParameterReference parameterReference) + { + if (parameterReference.Target is OpenApiParameter target) + { + return target; + } + else + { + throw new InvalidOperationException($"The input schema must be an {nameof(OpenApiParameter)} or {nameof(OpenApiParameterReference)}."); + } + } + else if (sourceParameter is OpenApiParameter directParameter) + { + return directParameter; + } + else + { + throw new InvalidOperationException($"The input schema must be an {nameof(OpenApiParameter)} or {nameof(OpenApiParameterReference)}."); + } + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file class XmlCommentSchemaTransformer : IOpenApiSchemaTransformer + { + public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) + { + // Apply comments from the type + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) + { + schema.Description = typeComment.Summary; + if (typeComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + + if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + { + // Apply comments from the property + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + var description = propertyComment.Summary; + if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) + { + description = $"{description}\n{propertyComment.Value}"; + } + else if (string.IsNullOrEmpty(description)) + { + description = propertyComment.Value; + } + if (schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string)) + { + // Inlined schema + schema.Description = description; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + else + { + // Schema Reference + if (!string.IsNullOrEmpty(description)) + { + schema.Metadata["x-ref-description"] = description; + } + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Metadata["x-ref-example"] = jsonString.Parse()!; + } + } + } + } + return Task.CompletedTask; + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class JsonNodeExtensions + { + public static JsonNode? Parse(this string? json) + { + if (json is null) + { + return null; + } + + try + { + return JsonNode.Parse(json); + } + catch (JsonException) + { + try + { + // If parsing fails, try wrapping in quotes to make it a valid JSON string + return JsonNode.Parse($"\"{json.Replace("\"", "\\\"")}\""); + } + catch (JsonException) + { + return null; + } + } + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class GeneratedServiceCollectionExtensions + { + [InterceptsLocation] + public static IServiceCollection AddOpenApi(this IServiceCollection services) + { + return services.AddOpenApi("v1", options => + { + options.AddSchemaTransformer(new XmlCommentSchemaTransformer()); + options.AddOperationTransformer(new XmlCommentOperationTransformer()); + }); + } + + } +} From ab15003970d486a73588c3a76937835ca06498c4 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 26 Nov 2025 13:01:48 +0100 Subject: [PATCH 7/9] Don't include CancellationToken when applying XML doc description --- src/OpenApi/gen/XmlCommentGenerator.Emitter.cs | 2 +- ...eptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...ditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...TagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...ToRequestBody#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...omControllers#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...omControllers#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...omControllers#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...omMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...entsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...emaReferences#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...tionIdFormats#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs index eba629cc35df..400f3b768f8e 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs @@ -366,7 +366,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - if (parameterInfo is null) + if (parameterInfo is null || parameterInfo.ParameterType == typeof(CancellationToken)) { continue; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs index 3c283349bd3a..c79c73b66514 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs @@ -348,7 +348,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - if (parameterInfo is null) + if (parameterInfo is null || parameterInfo.ParameterType == typeof(CancellationToken)) { continue; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs index cf166b710183..b8a1172e40b9 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs @@ -377,7 +377,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - if (parameterInfo is null) + if (parameterInfo is null || parameterInfo.ParameterType == typeof(CancellationToken)) { continue; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 32983388e5cc..5319cc43fa94 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -475,7 +475,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - if (parameterInfo is null) + if (parameterInfo is null || parameterInfo.ParameterType == typeof(CancellationToken)) { continue; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.ShouldNotApplyCancellationTokenDocumentationToRequestBody#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.ShouldNotApplyCancellationTokenDocumentationToRequestBody#OpenApiXmlCommentSupport.generated.verified.cs index cc830340b490..1fa849e91a7c 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.ShouldNotApplyCancellationTokenDocumentationToRequestBody#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.ShouldNotApplyCancellationTokenDocumentationToRequestBody#OpenApiXmlCommentSupport.generated.verified.cs @@ -349,7 +349,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - if (parameterInfo is null) + if (parameterInfo is null || parameterInfo.ParameterType == typeof(CancellationToken)) { continue; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index ed98af100c8b..764f8dce398b 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsParametersWithCustomNamesFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -350,7 +350,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - if (parameterInfo is null) + if (parameterInfo is null || parameterInfo.ParameterType == typeof(CancellationToken)) { continue; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index c5d9ca7502b7..ba11f7a5f3b2 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsRouteParametersFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -349,7 +349,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - if (parameterInfo is null) + if (parameterInfo is null || parameterInfo.ParameterType == typeof(CancellationToken)) { continue; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index b98f04bebf34..ed75560d2bd1 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -352,7 +352,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - if (parameterInfo is null) + if (parameterInfo is null || parameterInfo.ParameterType == typeof(CancellationToken)) { continue; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index 9c2cf8a23e7a..f09fee2e1143 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -396,7 +396,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - if (parameterInfo is null) + if (parameterInfo is null || parameterInfo.ParameterType == typeof(CancellationToken)) { continue; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 9cf04c380acc..58e706997a2f 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -378,7 +378,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - if (parameterInfo is null) + if (parameterInfo is null || parameterInfo.ParameterType == typeof(CancellationToken)) { continue; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs index 093f86424f05..a37fd2a86179 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs @@ -357,7 +357,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - if (parameterInfo is null) + if (parameterInfo is null || parameterInfo.ParameterType == typeof(CancellationToken)) { continue; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs index 5181bbfa0370..210b515058ff 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs @@ -349,7 +349,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform foreach (var parameterComment in methodComment.Parameters) { var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); - if (parameterInfo is null) + if (parameterInfo is null || parameterInfo.ParameterType == typeof(CancellationToken)) { continue; } From a2efffff4a92668ca3750e337e21faf6ca7d9d78 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 26 Nov 2025 13:04:46 +0100 Subject: [PATCH 8/9] Remove unused usings --- .../OperationTests.Controllers.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs index 5c2bdf39ef3e..a85f0a6a172a 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Net.Http; -using System.Reflection; -using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests; From 185206081d951e585994ea5b2e922b542a93c685 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 26 Nov 2025 15:20:59 +0100 Subject: [PATCH 9/9] Make assert less specific to avoid regressions --- .../OperationTests.Controllers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs index a85f0a6a172a..19fa164e4230 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.Controllers.cs @@ -255,7 +255,7 @@ public record Person(int Id, string Name); await SnapshotTestHelper.VerifyOpenApi(compilation, document => { var getOperation = document.Paths["/Test"].Operations[HttpMethod.Get]; - Assert.NotEqual("The cancellation token.", getOperation.RequestBody.Description); + Assert.Null(getOperation.RequestBody.Description); }); } }