diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs
index ecd59881e3f2..d83aa5028c18 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.Metadata;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi;
@@ -152,6 +153,30 @@ 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:
@@ -416,6 +441,50 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform
}
}
}
+ foreach (var parameterDescription in context.Description.ParameterDescriptions)
+ {
+ var metadata = parameterDescription.ModelMetadata;
+ if (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);
+ if (parameter is null)
+ {
+ if (operation.RequestBody is not null)
+ {
+ operation.RequestBody.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary;
+ 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 = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary;
+ if (propertyComment.Examples?.FirstOrDefault() is { } jsonString)
+ {
+ targetOperationParameter.Example = jsonString.Parse();
+ }
+ }
+ }
+ }
+ }
return Task.CompletedTask;
}
diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.MinimalApis.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.MinimalApis.cs
index 21213f5ac386..33544cd1a1af 100644
--- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.MinimalApis.cs
+++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/OperationTests.MinimalApis.cs
@@ -20,6 +20,7 @@ public async Task SupportsXmlCommentsOnOperationsFromMinimalApis()
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder();
@@ -44,6 +45,10 @@ public async Task SupportsXmlCommentsOnOperationsFromMinimalApis()
app.MapGet("/15", RouteHandlerExtensionMethods.Get15);
app.MapPost("/16", RouteHandlerExtensionMethods.Post16);
app.MapGet("/17", RouteHandlerExtensionMethods.Get17);
+app.MapPost("/18", RouteHandlerExtensionMethods.Post18);
+app.MapPost("/19", RouteHandlerExtensionMethods.Post19);
+app.MapGet("/20", RouteHandlerExtensionMethods.Get20);
+app.MapGet("/21", RouteHandlerExtensionMethods.Get21);
app.Run();
@@ -207,10 +212,81 @@ public static void Post16(Example example)
public static int[][] Get17(int[] args)
{
return [[1, 2, 3], [4, 5, 6], [7, 8, 9], args];
+ }
+ ///
+ /// A summary of Post18.
+ ///
+ public static int Post18([AsParameters] FirstParameters queryParameters, [AsParameters] SecondParameters bodyParameters)
+ {
+ return 0;
+ }
+
+ ///
+ /// Tests mixed regular and AsParameters with examples.
+ ///
+ /// A regular parameter with documentation.
+ /// Mixed parameter class with various types.
+ public static IResult Post19(string regularParam, [AsParameters] MixedParametersClass mixedParams)
+ {
+ return TypedResults.Ok($"Regular: {regularParam}, Email: {mixedParams.Email}");
+ }
+
+ ///
+ /// Tests AsParameters with different binding sources.
+ ///
+ /// Parameters from different sources.
+ public static IResult Get20([AsParameters] BindingSourceParametersClass bindingParams)
+ {
+ return TypedResults.Ok($"Query: {bindingParams.QueryParam}, Header: {bindingParams.HeaderParam}");
+ }
+
+ ///
+ /// Tests XML documentation priority order (value > returns > summary).
+ ///
+ /// Parameters demonstrating XML doc priority.
+ public static IResult Get21([AsParameters] XmlDocPriorityParametersClass priorityParams)
+ {
+ return TypedResults.Ok($"Processed parameters");
}
}
+public class FirstParameters
+{
+ ///
+ /// The name of the person.
+ ///
+ public string? Name { get; set; }
+ ///
+ /// The age of the person.
+ ///
+ /// 30
+ public int? Age { get; set; }
+ ///
+ /// The user information.
+ ///
+ ///
+ /// {
+ /// "username": "johndoe",
+ /// "email": "johndoe@example.com"
+ /// }
+ ///
+ public User? User { get; set; }
+}
+
+public class SecondParameters
+{
+ ///
+ /// The description of the project.
+ ///
+ public string? Description { get; set; }
+ ///
+ /// The service used for testing.
+ ///
+ [FromServices]
+ public Example Service { get; set; }
+}
+
public class User
{
public string Username { get; set; } = string.Empty;
@@ -232,6 +308,69 @@ public Example(Func