-
Notifications
You must be signed in to change notification settings - Fork 10.7k
Respect IModelNameProvider when matching OpenAPI parameters #64535
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d841c03
081f929
a01cb32
a0baeb0
64f6a13
2b8a5b7
e2e7ed5
8c957e3
e276952
2d1979f
0c41448
23c4a92
3f30094
d97acbe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -153,30 +154,6 @@ public static string CreateDocumentationId(this PropertyInfo property) | |
| return sb.ToString(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Generates a documentation comment ID for a property given its container type and property name. | ||
| /// Example: P:Namespace.ContainingType.PropertyName | ||
| /// </summary> | ||
| 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(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Generates a documentation comment ID for a method (or constructor). | ||
| /// For example: | ||
|
|
@@ -389,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); | ||
| var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); | ||
| var operationParameter = GetOperationParameter(operation, parameterInfo, parameterComment); | ||
| if (operationParameter is not null) | ||
| { | ||
| var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); | ||
|
|
@@ -400,7 +377,10 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform | |
| } | ||
| targetOperationParameter.Deprecated = parameterComment.Deprecated; | ||
| } | ||
| else | ||
| // Only fall back to the request body when the parameter is actually bound to it. | ||
| // This avoids applying documentation for parameters that aren't part of the | ||
| // OpenAPI surface (e.g. a `CancellationToken`) to the request body. | ||
| else if (IsRequestBodyParameter(context, parameterInfo, parameterComment)) | ||
| { | ||
| var requestBody = operation.RequestBody; | ||
| if (requestBody is not null) | ||
|
|
@@ -449,10 +429,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) | ||
| { | ||
| continue; | ||
| } | ||
| if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) | ||
| { | ||
| var parameter = operation.Parameters?.SingleOrDefault(p => p.Name == metadata.Name); | ||
| var parameter = GetOperationParameter(operation, propertyInfo); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to apply |
||
| var description = propertyComment.Summary; | ||
| if (!string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(propertyComment.Value)) | ||
| { | ||
|
|
@@ -499,6 +483,86 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform | |
| return Task.CompletedTask; | ||
| } | ||
|
|
||
| private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, PropertyInfo propertyInfo) | ||
| { | ||
| return GetOperationParameter(operation, propertyInfo, propertyInfo?.Name); | ||
| } | ||
|
khellang marked this conversation as resolved.
|
||
|
|
||
| private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ParameterInfo? parameterInfo, XmlParameterComment comment) | ||
| { | ||
| return GetOperationParameter(operation, parameterInfo, parameterInfo?.Name ?? comment.Name); | ||
| } | ||
|
|
||
| private static IOpenApiParameter? GetOperationParameter(OpenApiOperation operation, ICustomAttributeProvider? attributeProvider, string? name) | ||
| { | ||
| var parameters = operation.Parameters; | ||
| if (parameters is null || parameters.Count == 0) | ||
| { | ||
| 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 bool IsRequestBodyParameter(OpenApiOperationTransformerContext context, ParameterInfo? parameterInfo, XmlParameterComment comment) | ||
| { | ||
| var modelNames = GetModelNames(parameterInfo, parameterInfo?.Name ?? comment.Name); | ||
|
|
||
| foreach (var parameterDescription in context.Description.ParameterDescriptions) | ||
| { | ||
| if (parameterDescription.Source == BindingSource.Body | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This excludes
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But those are bound to the body, right?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, so if there is a description on the form param it should end up as the description in openapi? |
||
| && parameterDescription.Name is { } parameterName | ||
| && modelNames.Contains(parameterName)) | ||
| { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| private static IReadOnlySet<string> GetModelNames(ICustomAttributeProvider? attributeProvider, string? name) | ||
| { | ||
| var modelNames = new HashSet<string>(); | ||
|
|
||
| if (!string.IsNullOrEmpty(name)) | ||
| { | ||
| modelNames.Add(name); | ||
| } | ||
|
|
||
| if (attributeProvider is null) | ||
| { | ||
| return modelNames; | ||
| } | ||
|
|
||
| foreach (var attribute in attributeProvider.GetCustomAttributes(inherit: false)) | ||
| { | ||
| if (attribute is IModelNameProvider modelNameProvider && !string.IsNullOrEmpty(modelNameProvider.Name)) | ||
| { | ||
| modelNames.Add(modelNameProvider.Name); | ||
| } | ||
| } | ||
|
|
||
| return modelNames; | ||
| } | ||
|
|
||
| private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) | ||
| { | ||
| if (sourceParameter is OpenApiParameterReference parameterReference) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure we care too much, but if it's easy to fix could we avoid calling
GetModelNamesmultiple times per parameter?