diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs
index 771f09d181c3..003df4f0689f 100644
--- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs
+++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs
@@ -423,6 +423,14 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
+ if (XmlCommentCache.Cache.TryGetValue(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 })
{
if (XmlCommentCache.Cache.TryGetValue(propertyInfo.CreateDocumentationId(), out var propertyComment))
@@ -434,14 +442,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext
}
}
}
- if (XmlCommentCache.Cache.TryGetValue(context.JsonTypeInfo.Type.CreateDocumentationId(), out var typeComment))
- {
- schema.Description = typeComment.Summary;
- if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
- {
- schema.Example = jsonString.Parse();
- }
- }
return Task.CompletedTask;
}
}
diff --git a/src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs b/src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs
index 777913ccc567..919675d580ab 100644
--- a/src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs
+++ b/src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs
@@ -97,8 +97,8 @@ internal static void ApplyValidationAttributes(this JsonNode schema, IEnumerable
? CultureInfo.InvariantCulture
: CultureInfo.CurrentCulture;
- var minString = rangeAttribute.Minimum.ToString();
- var maxString = rangeAttribute.Maximum.ToString();
+ var minString = string.Format(targetCulture, "{0}", rangeAttribute.Minimum);
+ var maxString = string.Format(targetCulture, "{0}", rangeAttribute.Maximum);
if (decimal.TryParse(minString, NumberStyles.Any, targetCulture, out var minDecimal))
{
diff --git a/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs b/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs
index cb3b6b26abfc..0a0ee73ca841 100644
--- a/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs
+++ b/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs
@@ -25,6 +25,9 @@ public static IOpenApiSchema AddOpenApiSchemaByReference(this OpenApiDocument do
document.Workspace ??= new();
var location = document.BaseUri + "/components/schemas/" + schemaId;
document.Workspace.RegisterComponentForDocument(document, schema, location);
- return new OpenApiSchemaReference(schemaId, document);
+ return new OpenApiSchemaReference(schemaId, document)
+ {
+ Description = schema.Description,
+ };
}
}
diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs
index 290100cdbfc3..1f8065c1a756 100644
--- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs
+++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Net.Http;
@@ -19,7 +19,10 @@ public async Task SupportsXmlCommentsOnSchemas()
var builder = WebApplication.CreateBuilder();
-builder.Services.AddOpenApi();
+builder.Services.AddOpenApi(options => {
+ var prevCreateSchemaReferenceId = options.CreateSchemaReferenceId;
+ options.CreateSchemaReferenceId = (x) => x.Type == typeof(AddressNested) ? null : prevCreateSchemaReferenceId(x);
+});
var app = builder.Build();
@@ -31,6 +34,7 @@ public async Task SupportsXmlCommentsOnSchemas()
app.MapPost("/todo-with-description", (TodoWithDescription todo) => { });
app.MapPost("/type-with-examples", (TypeWithExamples typeWithExamples) => { });
app.MapPost("/user", (User user) => { });
+app.MapPost("/company", (Company company) => { });
app.Run();
@@ -175,6 +179,60 @@ internal class User : IUser
///
public string Name { get; set; }
}
+
+///
+/// An address.
+///
+public class AddressWithSummary
+{
+ public string Street { get; set; }
+}
+
+public class AddressWithoutSummary
+{
+ public string Street { get; set; }
+}
+
+///
+/// An address.
+///
+public class AddressNested
+{
+ public string Street { get; set; }
+}
+
+public class Company
+{
+ ///
+ /// Billing address.
+ ///
+ public AddressWithSummary BillingAddressClassWithSummary { get; set; }
+
+ ///
+ /// Billing address.
+ ///
+ public AddressWithoutSummary BillingAddressClassWithoutSummary { get; set; }
+
+ ///
+ /// Billing address.
+ ///
+ public AddressNested BillingAddressNested { get; set; }
+
+ ///
+ /// Visiting address.
+ ///
+ public AddressWithSummary VisitingAddressClassWithSummary { get; set; }
+
+ ///
+ /// Visiting address.
+ ///
+ public AddressWithoutSummary VisitingAddressClassWithoutSummary { get; set; }
+
+ ///
+ /// Visiting address.
+ ///
+ public AddressNested VisitingAddressNested { get; set; }
+}
""";
var generator = new XmlCommentGenerator();
await SnapshotTestHelper.Verify(source, generator, out var compilation);
@@ -258,6 +316,21 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document =>
var user = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("The unique identifier for the user.", user.Properties["id"].Description);
Assert.Equal("The user's display name.", user.Properties["name"].Description);
+
+ path = document.Paths["/company"].Operations[HttpMethod.Post];
+ var company = path.RequestBody.Content["application/json"].Schema;
+ Assert.Equal("Billing address.", company.Properties["billingAddressClassWithSummary"].Description);
+ Assert.Equal("Billing address.", company.Properties["billingAddressClassWithoutSummary"].Description);
+ Assert.Equal("Billing address.", company.Properties["billingAddressNested"].Description);
+ Assert.Equal("Visiting address.", company.Properties["visitingAddressClassWithSummary"].Description);
+ Assert.Equal("Visiting address.", company.Properties["visitingAddressClassWithoutSummary"].Description);
+ Assert.Equal("Visiting address.", company.Properties["visitingAddressNested"].Description);
+
+ var addressWithSummary = document.Components.Schemas["AddressWithSummary"];
+ Assert.Equal("An address.", addressWithSummary.Description);
+
+ var addressWithoutSummary = document.Components.Schemas["AddressWithoutSummary"];
+ Assert.Null(addressWithSummary.Description);
});
}
}
diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SnapshotTestHelper.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SnapshotTestHelper.cs
index c2d09bdb5971..3f321dd217df 100644
--- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SnapshotTestHelper.cs
+++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SnapshotTestHelper.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
+using System.Globalization;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
@@ -16,6 +17,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Writers;
namespace Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests;
@@ -197,8 +199,7 @@ void OnEntryPointExit(Exception exception)
var service = services.GetService(serviceType) ?? throw new InvalidOperationException("Could not resolve IDocumentProvider service.");
using var stream = new MemoryStream();
- var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
- using var writer = new StreamWriter(stream, encoding, bufferSize: 1024, leaveOpen: true);
+ using var writer = new FormattingStreamWriter(stream, CultureInfo.InvariantCulture) { AutoFlush = true };
var targetMethod = serviceType.GetMethod("GenerateAsync", [typeof(string), typeof(TextWriter)]) ?? throw new InvalidOperationException("Could not resolve GenerateAsync method.");
targetMethod.Invoke(service, ["v1", writer]);
stream.Position = 0;
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 dd6515efdb21..43e6a5f68f57 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.
@@ -78,6 +78,8 @@ private static Dictionary GenerateCacheEntries()
cache.Add(@"T:ProjectBoard.ProtectedInternalElement", new XmlComment(@"Can find this XML comment.", null, null, null, null, false, null, null, null));
cache.Add(@"T:ProjectRecord", new XmlComment(@"The project that contains Todo items.", null, null, null, null, false, null, [new XmlParameterComment(@"Name", @"The name of the project.", null, false), new XmlParameterComment(@"Description", @"The description of the project.", null, false)], null));
cache.Add(@"T:User", new XmlComment(null, null, null, null, null, false, null, null, null));
+ cache.Add(@"T:AddressWithSummary", new XmlComment(@"An address.", null, null, null, null, false, null, null, null));
+ cache.Add(@"T:AddressNested", new XmlComment(@"An address.", null, null, null, null, false, null, null, null));
cache.Add(@"P:ProjectBoard.ProtectedInternalElement.Name", new XmlComment(@"The unique identifier for the element.", null, null, null, null, false, null, null, null));
cache.Add(@"P:ProjectRecord.Name", new XmlComment(@"The name of the project.", null, null, null, null, false, null, null, null));
cache.Add(@"P:ProjectRecord.Description", new XmlComment(@"The description of the project.", null, null, null, null, false, null, null, null));
@@ -102,6 +104,12 @@ private static Dictionary GenerateCacheEntries()
cache.Add(@"P:IUser.Name", new XmlComment(@"The user's display name.", null, null, null, null, false, null, null, null));
cache.Add(@"P:User.Id", new XmlComment(@"The unique identifier for the user.", null, null, null, null, false, null, null, null));
cache.Add(@"P:User.Name", new XmlComment(@"The user's display name.", null, null, null, null, false, null, null, null));
+ cache.Add(@"P:Company.BillingAddressClassWithSummary", new XmlComment(@"Billing address.", null, null, null, null, false, null, null, null));
+ cache.Add(@"P:Company.BillingAddressClassWithoutSummary", new XmlComment(@"Billing address.", null, null, null, null, false, null, null, null));
+ cache.Add(@"P:Company.BillingAddressNested", new XmlComment(@"Billing address.", null, null, null, null, false, null, null, null));
+ cache.Add(@"P:Company.VisitingAddressClassWithSummary", new XmlComment(@"Visiting address.", null, null, null, null, false, null, null, null));
+ cache.Add(@"P:Company.VisitingAddressClassWithoutSummary", new XmlComment(@"Visiting address.", null, null, null, null, false, null, null, null));
+ cache.Add(@"P:Company.VisitingAddressNested", new XmlComment(@"Visiting address.", null, null, null, null, false, null, null, null));
return cache;
}
@@ -435,6 +443,14 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
+ if (XmlCommentCache.Cache.TryGetValue(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 })
{
if (XmlCommentCache.Cache.TryGetValue(propertyInfo.CreateDocumentationId(), out var propertyComment))
@@ -446,14 +462,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext
}
}
}
- if (XmlCommentCache.Cache.TryGetValue(context.JsonTypeInfo.Type.CreateDocumentationId(), out var typeComment))
- {
- schema.Description = typeComment.Summary;
- if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
- {
- schema.Example = jsonString.Parse();
- }
- }
return Task.CompletedTask;
}
}
@@ -490,14 +498,15 @@ file static class JsonNodeExtensions
file static class GeneratedServiceCollectionExtensions
{
[InterceptsLocation]
- public static IServiceCollection AddOpenApi(this IServiceCollection services)
+ public static IServiceCollection AddOpenApi(this IServiceCollection services, Action configureOptions)
{
return services.AddOpenApi("v1", options =>
{
options.AddSchemaTransformer(new XmlCommentSchemaTransformer());
options.AddOperationTransformer(new XmlCommentOperationTransformer());
+ configureOptions(options);
});
}
}
-}
\ No newline at end of file
+}