diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs
index 17a4723f..b6192e02 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs
@@ -18,9 +18,9 @@ namespace Asp.Versioning;
/// Represents API version parser from a type namespace.
///
///
-/// The namespace identifier can use 'v', 'V', or '_' as a prefix. The '_' prefix is useful when
-/// a folder starts with a number because Visual Studio automatically prefixes it with an underscore.
-/// For example, Contoso.Api._2018_04_01.Controllers is equivalent to Contoso.Api.v2018_04_01.Controllers.
+/// The namespace identifier can use 'v', 'V', or '_' as a prefix. The '_' prefix is useful
+/// when the source folder starts with a number and the editor automatically prefixes it with an underscore. As an
+/// example, Api._2018_04_01.Controllers is equivalent to Api.v2018_04_01.Controllers.
///
public class NamespaceParser
{
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs
index c31945a5..b1509b47 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs
@@ -58,12 +58,8 @@ private static void AddApiExplorerServices( IApiVersioningBuilder builder )
var services = builder.Services;
- // registers DefaultApiDescriptionProvider, which discovers controller-based endpoints
- services.AddMvcCore().AddApiExplorer();
-
- // registers EndpointMetadataApiDescriptionProvider, which discovers minimal API endpoints.
- // both providers are required so that OpenApiDocumentService sees all endpoints.
services.AddEndpointsApiExplorer();
+ services.AddMvcCore().AddApiExplorer();
services.TryAddSingleton, ApiExplorerOptionsFactory>();
services.TryAddTransient();
services.TryAddSingleton( static sp => sp.GetRequiredService().Create() );
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Asp.Versioning.OpenApi.csproj b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Asp.Versioning.OpenApi.csproj
index 1df0ce32..b2ad49ce 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Asp.Versioning.OpenApi.csproj
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Asp.Versioning.OpenApi.csproj
@@ -16,6 +16,7 @@
+
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/ApiExplorerTransformer.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/ApiExplorerTransformer.cs
index 84b1d7d3..905fe4c9 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/ApiExplorerTransformer.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/ApiExplorerTransformer.cs
@@ -301,8 +301,11 @@ private void AddLinkExtensions( OpenApiDocument document, ApiVersionDescription
if ( array.Count > 0 )
{
+ var obj = new JsonObject();
var extensions = document.Extensions ??= new Dictionary();
- extensions[ExtensionName] = new JsonNodeExtension( array );
+
+ obj["links"] = array;
+ extensions[ExtensionName] = new JsonNodeExtension( obj );
}
}
@@ -347,7 +350,7 @@ protected virtual JsonObject ToJson( LinkHeaderValue link )
if ( link.Languages.Count > 0 )
{
- obj["lang"] = new JsonArray( link.Languages.Select( l => JsonNode.Parse( l.ToString() ) ).ToArray() );
+ obj["lang"] = new JsonArray( [.. link.Languages.Select( l => JsonNode.Parse( l.ToString() ) )] );
}
foreach ( var (key, value) in link.Extensions )
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlComments.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlComments.cs
index d25a2703..59a5c710 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlComments.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlComments.cs
@@ -42,7 +42,7 @@ public class XmlComments
/// Gets the summary from the specified member, if any.
///
/// The member to get the summary from.
- /// The corresponding summary or an empty string.
+ /// The corresponding <summary> or an empty string.
public string GetSummary( MemberInfo member )
=> GetMember( member )?.Element( "summary" )?.Value.Trim() ?? string.Empty;
@@ -50,7 +50,7 @@ public string GetSummary( MemberInfo member )
/// Gets the description from the specified member, if any.
///
/// The member to get the description from.
- /// The corresponding description or an empty string.
+ /// The corresponding <description> or an empty string.
public string GetDescription( MemberInfo member )
=> GetMember( member )?.Element( "description" )?.Value.Trim() ?? string.Empty;
@@ -58,7 +58,7 @@ public string GetDescription( MemberInfo member )
/// Gets the remarks from the specified member, if any.
///
/// The member to get the remarks from.
- /// The corresponding remarks or an empty string.
+ /// The corresponding <remarks> or an empty string.
public string GetRemarks( MemberInfo member )
=> GetMember( member )?.Element( "remarks" )?.Value.Trim() ?? string.Empty;
@@ -66,16 +66,24 @@ public string GetRemarks( MemberInfo member )
/// Gets the returns from the specified member, if any.
///
/// The member to get the returns from.
- /// The corresponding returns or an empty string.
+ /// The corresponding <returns> or an empty string.
public string GetReturns( MemberInfo member )
=> GetMember( member )?.Element( "returns" )?.Value.Trim() ?? string.Empty;
+ ///
+ /// Gets the example from the specified member, if any.
+ ///
+ /// The member to get the example from.
+ /// The corresponding <example> or an empty string.
+ public string GetExample( MemberInfo member )
+ => GetMember( member )?.Element( "example" )?.Value.Trim() ?? string.Empty;
+
///
/// Gets the param description from the specified member, if any.
///
/// The member to get the parameter from.
/// The name of the parameter.
- /// The corresponding returns or an empty string.
+ /// The corresponding description or an empty string.
public string GetParameterDescription( MemberInfo member, string name )
{
if ( GetMember( member ) is { } element )
@@ -89,6 +97,50 @@ public string GetParameterDescription( MemberInfo member, string name )
return string.Empty;
}
+ ///
+ /// Gets the parameter example from the specified member, if any.
+ ///
+ /// The member to get the parameter from.
+ /// The name of the parameter.
+ /// The corresponding <example> or an empty string.
+ public string GetParameterExample( MemberInfo member, string name )
+ {
+ if ( GetMember( member ) is { } element )
+ {
+ return element.Elements( "param" )
+ .FirstOrDefault( x => x.Attribute( "name" )?.Value == name )?
+ .Attribute( "example" )?
+ .Value
+ .Trim() ?? string.Empty;
+ }
+
+ return string.Empty;
+ }
+
+ ///
+ /// Gets the deprecated attribute from the specified member, if any.
+ ///
+ /// The member to get the parameter from.
+ /// The name of the parameter.
+ /// true if the deprecated attribute is present with a value of "true";
+ /// otherwise false.
+ public bool IsParameterDeprecated( MemberInfo member, string name )
+ {
+ if ( GetMember( member ) is { } element )
+ {
+ var deprecated = element.Elements( "param" )
+ .FirstOrDefault( x => x.Attribute( "name" )?.Value == name )?
+ .Attribute( "deprecated" )?.Value;
+
+ if ( deprecated is { } value )
+ {
+ return StringComparer.OrdinalIgnoreCase.Equals( value, bool.TrueString );
+ }
+ }
+
+ return false;
+ }
+
///
/// Gets the response description from the specified member, if any.
///
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsFile.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsFile.cs
index 375deaf2..8a8393f6 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsFile.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsFile.cs
@@ -24,7 +24,7 @@ public XmlCommentsFile( Assembly[] assemblies, IHostEnvironment environment )
for ( var i = 0; i < assemblies.Length; i++ )
{
var assembly = assemblies[i];
- var fileName = FilePath.ChangeExtension( assembly.GetName().Name, ".xml" );
+ var fileName = assembly.GetName().Name + ".xml";
if ( string.IsNullOrEmpty( fileName ) )
{
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsTransformer.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsTransformer.cs
index 664551c6..eab3f198 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsTransformer.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsTransformer.cs
@@ -8,6 +8,8 @@ namespace Asp.Versioning.OpenApi.Transformers;
using Microsoft.OpenApi;
using System;
using System.Reflection;
+using System.Text.Json;
+using System.Text.Json.Nodes;
using System.Threading;
using static System.Reflection.BindingFlags;
@@ -45,24 +47,46 @@ public virtual Task TransformAsync(
ArgumentNullException.ThrowIfNull( schema );
ArgumentNullException.ThrowIfNull( context );
- if ( schema.Properties is not { } properties
- || context.JsonTypeInfo?.Type is not Type type )
+ if ( context.JsonTypeInfo?.Type is not Type type )
{
return Task.CompletedTask;
}
- if ( string.IsNullOrEmpty( schema.Description ) )
+ var description = schema.Description;
+
+ if ( string.IsNullOrEmpty( description )
+ && !string.IsNullOrEmpty( description = Documentation.GetSummary( type ) ) )
{
- schema.Description = Documentation.GetSummary( type );
+ schema.Description = description;
+ }
+
+ if ( schema.Example is null && ToJson( Documentation.GetExample( type ) ) is { } example )
+ {
+ schema.Example = example;
+ }
+
+ if ( schema.Properties is not { } properties )
+ {
+ return Task.CompletedTask;
}
foreach ( var (name, prop) in properties )
{
if ( prop is not null
- && string.IsNullOrEmpty( prop.Description )
- && type.GetProperty( name, IgnoreCase | Instance | Public ) is { } property )
+ && type.GetProperty( name, IgnoreCase | Instance | Public ) is { } property )
{
- prop.Description = Documentation.GetSummary( property );
+ if ( string.IsNullOrEmpty( prop.Description )
+ && !string.IsNullOrEmpty( description = Documentation.GetSummary( property ) ) )
+ {
+ prop.Description = description;
+ }
+
+ if ( prop.Example is null
+ && prop.Examples is not null
+ && ( example = ToJson( Documentation.GetExample( property ) ) ) is not null )
+ {
+ prop.Examples.Add( example );
+ }
}
}
@@ -88,16 +112,19 @@ public virtual Task TransformAsync(
operation.Summary = Documentation.GetSummary( method );
}
- if ( string.IsNullOrEmpty( operation.Description ) )
+ var description = operation.Description;
+
+ if ( string.IsNullOrEmpty( description )
+ && !string.IsNullOrEmpty( description = Documentation.GetDescription( method ) ) )
{
- operation.Description = Documentation.GetDescription( method );
+ operation.Description = description;
}
if ( operation.Responses is { } responses )
{
foreach ( var (statusCode, response) in responses )
{
- var description = Documentation.GetResponseDescription( method, statusCode );
+ description = Documentation.GetResponseDescription( method, statusCode );
if ( !string.IsNullOrEmpty( description ) )
{
@@ -118,18 +145,40 @@ public virtual Task TransformAsync(
{
var parameter = parameters[i];
- if ( !string.IsNullOrEmpty( parameter.Name ) && string.IsNullOrEmpty( parameter.Description ) )
+ if ( string.IsNullOrEmpty( parameter.Name ) )
{
- for ( var j = 0; j < args.Count; j++ )
+ continue;
+ }
+
+ for ( var j = 0; j < args.Count; j++ )
+ {
+ var arg = args[j];
+
+ if ( arg.Name != parameter.Name )
{
- var arg = args[i];
+ continue;
+ }
+
+ var name = arg.ParameterDescriptor.Name;
+
+ if ( string.IsNullOrEmpty( parameter.Description )
+ && !string.IsNullOrEmpty( description = Documentation.GetParameterDescription( method, name ) ) )
+ {
+ parameter.Description = description;
+ }
- if ( arg.Name == parameter.Name )
+ if ( parameter is OpenApiParameter param )
+ {
+ if ( param.Example is null
+ && ToJson( Documentation.GetParameterExample( method, name ) ) is { } example )
{
- var name = arg.ParameterDescriptor.Name;
- parameter.Description = Documentation.GetParameterDescription( method, name );
+ param.Example = example;
}
+
+ param.Deprecated |= Documentation.IsParameterDeprecated( method, name );
}
+
+ break;
}
}
@@ -159,4 +208,21 @@ private static bool TryResolveMethod( ActionDescriptor action, [MaybeNullWhen( f
method = default;
return false;
}
+
+ private static JsonNode? ToJson( string? example )
+ {
+ if ( string.IsNullOrEmpty( example ) )
+ {
+ return default;
+ }
+
+ try
+ {
+ return JsonNode.Parse( example );
+ }
+ catch ( JsonException )
+ {
+ return JsonNode.Parse( $"\"{example}\"" );
+ }
+ }
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Asp.Versioning.OpenApi.Tests.csproj b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Asp.Versioning.OpenApi.Tests.csproj
index 96d297f4..1712ddd6 100644
--- a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Asp.Versioning.OpenApi.Tests.csproj
+++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Asp.Versioning.OpenApi.Tests.csproj
@@ -14,13 +14,14 @@
+
-
+
-
+
@@ -32,10 +33,15 @@
+
+ true
+ false
+
+
-
+
diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/AssumeCultureAttribute.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/AssumeCultureAttribute.cs
deleted file mode 100644
index 395a9550..00000000
--- a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/AssumeCultureAttribute.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) .NET Foundation and contributors. All rights reserved.
-
-namespace Asp.Versioning;
-
-using System.Globalization;
-using System.Reflection;
-using static System.AttributeTargets;
-using static System.Threading.Thread;
-
-///
-/// Allows a test method to assume that it is running in a specific locale.
-///
-[AttributeUsage( Class | Method, AllowMultiple = false, Inherited = true )]
-internal sealed class AssumeCultureAttribute : BeforeAfterTestAttribute
-{
- private CultureInfo originalCulture;
- private CultureInfo originalUICulture;
-
- public AssumeCultureAttribute( string name ) => Name = name;
-
- public string Name { get; }
-
- public override void Before( MethodInfo methodUnderTest, IXunitTest test )
- {
- originalCulture = CurrentThread.CurrentCulture;
- originalUICulture = CurrentThread.CurrentUICulture;
-
- var culture = CultureInfo.CreateSpecificCulture( Name );
-
- CurrentThread.CurrentCulture = culture;
- CurrentThread.CurrentUICulture = culture;
- }
-
- public override void After( MethodInfo methodUnderTest, IXunitTest test )
- {
- CurrentThread.CurrentCulture = originalCulture;
- CurrentThread.CurrentUICulture = originalUICulture;
- }
-}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1-minimal.json b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1-minimal.json
index 3a29e0a2..8fb590a9 100644
--- a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1-minimal.json
+++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1-minimal.json
@@ -16,19 +16,20 @@
"tags": [
"Test"
],
- "summary": "",
- "description": "",
+ "summary": "Test",
+ "description": "A test API.",
"parameters": [
{
"name": "id",
"in": "path",
- "description": "",
+ "description": "A test parameter.",
"required": true,
"schema": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": "integer",
"format": "int32"
- }
+ },
+ "example": 42
},
{
"name": "api-version",
@@ -43,7 +44,7 @@
],
"responses": {
"200": {
- "description": "OK",
+ "description": "Pass",
"content": {
"application/json": {
"schema": {
@@ -58,7 +59,7 @@
}
},
"400": {
- "description": "Bad Request"
+ "description": "Fail"
}
}
}
@@ -69,18 +70,20 @@
"name": "Test"
}
],
- "x-api-versioning": [
- {
- "title": "Version Deprecation Policy",
- "type": "text/html",
- "rel": "deprecation",
- "url": "http://my.api.com/policies/versions/deprecated.html"
- },
- {
- "title": "Version Sunset Policy",
- "type": "text/html",
- "rel": "sunset",
- "url": "http://my.api.com/policies/versions/sunset.html"
- }
- ]
+ "x-api-versioning": {
+ "links": [
+ {
+ "title": "Version Deprecation Policy",
+ "type": "text/html",
+ "rel": "deprecation",
+ "url": "http://my.api.com/policies/versions/deprecated.html"
+ },
+ {
+ "title": "Version Sunset Policy",
+ "type": "text/html",
+ "rel": "sunset",
+ "url": "http://my.api.com/policies/versions/sunset.html"
+ }
+ ]
+ }
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1-mixed.json b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1-mixed.json
index 30b4afc6..d352c3d1 100644
--- a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1-mixed.json
+++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1-mixed.json
@@ -16,19 +16,20 @@
"tags": [
"Test"
],
- "summary": "",
- "description": "",
+ "summary": "Test",
+ "description": "A test API.",
"parameters": [
{
"name": "id",
"in": "path",
- "description": "",
+ "description": "A test parameter.",
"required": true,
"schema": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": "integer",
"format": "int32"
- }
+ },
+ "example": 42
},
{
"name": "api-version",
@@ -43,7 +44,7 @@
],
"responses": {
"200": {
- "description": "OK",
+ "description": "Pass",
"content": {
"application/json": {
"schema": {
@@ -58,7 +59,7 @@
}
},
"400": {
- "description": "Bad Request"
+ "description": "Fail"
}
}
}
@@ -68,13 +69,13 @@
"tags": [
"Test"
],
- "summary": "",
- "description": "",
+ "summary": "Test",
+ "description": "A test API.",
"parameters": [
{
"name": "id",
"in": "query",
- "description": "",
+ "description": "A test parameter.",
"schema": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": [
@@ -82,7 +83,8 @@
"string"
],
"format": "int32"
- }
+ },
+ "example": 42
},
{
"name": "api-version",
@@ -97,7 +99,7 @@
],
"responses": {
"200": {
- "description": "OK",
+ "description": "Pass",
"content": {
"text/plain": {
"schema": {
@@ -140,18 +142,20 @@
"name": "Test"
}
],
- "x-api-versioning": [
- {
- "title": "Version Deprecation Policy",
- "type": "text/html",
- "rel": "deprecation",
- "url": "http://my.api.com/policies/versions/deprecated.html"
- },
- {
- "title": "Version Sunset Policy",
- "type": "text/html",
- "rel": "sunset",
- "url": "http://my.api.com/policies/versions/sunset.html"
- }
- ]
+ "x-api-versioning": {
+ "links": [
+ {
+ "title": "Version Deprecation Policy",
+ "type": "text/html",
+ "rel": "deprecation",
+ "url": "http://my.api.com/policies/versions/deprecated.html"
+ },
+ {
+ "title": "Version Sunset Policy",
+ "type": "text/html",
+ "rel": "sunset",
+ "url": "http://my.api.com/policies/versions/sunset.html"
+ }
+ ]
+ }
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1.json b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1.json
index 6cb8098e..037036f4 100644
--- a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1.json
+++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1.json
@@ -16,13 +16,13 @@
"tags": [
"Test"
],
- "summary": "",
- "description": "",
+ "summary": "Test",
+ "description": "A test API.",
"parameters": [
{
"name": "id",
"in": "query",
- "description": "",
+ "description": "A test parameter.",
"schema": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": [
@@ -30,7 +30,8 @@
"string"
],
"format": "int32"
- }
+ },
+ "example": 42
},
{
"name": "api-version",
@@ -45,7 +46,7 @@
],
"responses": {
"200": {
- "description": "OK",
+ "description": "Pass",
"content": {
"text/plain": {
"schema": {
@@ -88,18 +89,20 @@
"name": "Test"
}
],
- "x-api-versioning": [
- {
- "title": "Version Deprecation Policy",
- "type": "text/html",
- "rel": "deprecation",
- "url": "http://my.api.com/policies/versions/deprecated.html"
- },
- {
- "title": "Version Sunset Policy",
- "type": "text/html",
- "rel": "sunset",
- "url": "http://my.api.com/policies/versions/sunset.html"
- }
- ]
+ "x-api-versioning": {
+ "links": [
+ {
+ "title": "Version Deprecation Policy",
+ "type": "text/html",
+ "rel": "deprecation",
+ "url": "http://my.api.com/policies/versions/deprecated.html"
+ },
+ {
+ "title": "Version Sunset Policy",
+ "type": "text/html",
+ "rel": "sunset",
+ "url": "http://my.api.com/policies/versions/sunset.html"
+ }
+ ]
+ }
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/MinimalApi.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/MinimalApi.cs
index 014dafb0..422cd388 100644
--- a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/MinimalApi.cs
+++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/MinimalApi.cs
@@ -10,7 +10,7 @@ public static class MinimalApi
/// Test
///
/// A test API.
- /// A test parameter.
+ /// A test parameter.
/// The original identifier.
/// Pass
/// Fail
diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/Model.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/Model.cs
new file mode 100644
index 00000000..d096b567
--- /dev/null
+++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/Model.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+#pragma warning disable SA1629
+
+namespace Asp.Versioning.OpenApi.Simulators;
+
+///
+/// Represents a model.
+///
+public class Model
+{
+ ///
+ /// Gets or sets the user associated with the model.
+ ///
+ ///
+ /// {
+ /// "userName": "John Doe",
+ /// "email": "john.doe@example.com"
+ /// }
+ ///
+ public User User { get; set; }
+}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/TestController.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/TestController.cs
index 363012e5..c86fca90 100644
--- a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/TestController.cs
+++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/TestController.cs
@@ -16,7 +16,7 @@ public class TestController : ControllerBase
/// Test
///
/// A test API.
- /// A test parameter.
+ /// A test parameter.
/// The original identifier.
/// Pass
/// Fail
diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/User.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/User.cs
new file mode 100644
index 00000000..6594f848
--- /dev/null
+++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/User.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+#pragma warning disable SA1629
+
+namespace Asp.Versioning.OpenApi.Simulators;
+
+///
+/// Represents a user.
+///
+public class User
+{
+ ///
+ /// Gets or sets the username associated with the account.
+ ///
+ /// John Doe
+ public string UserName { get; set; }
+
+ ///
+ /// Gets or sets the email address associated with the user.
+ ///
+ /// user@example.com
+ public string Email { get; set; }
+}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/AcceptanceTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/AcceptanceTest.cs
index 2e8652cc..f44a7f1b 100644
--- a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/AcceptanceTest.cs
+++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/AcceptanceTest.cs
@@ -13,13 +13,6 @@ namespace Asp.Versioning.OpenApi.Transformers;
public class AcceptanceTest
{
- ///
- /// Verifies that minimal API endpoints produce a non-empty OpenAPI document.
- /// AddApiExplorer internally calls AddMvcCore().AddApiExplorer(),
- /// which auto-discovers controllers from the test assembly. Application parts
- /// are cleared to isolate the test to minimal API endpoints only.
- ///
- /// A representing the asynchronous unit test.
[Fact]
[AssumeCulture( "en-US" )]
public async Task minimal_api_should_generate_expected_open_api_document()
@@ -31,9 +24,8 @@ public async Task minimal_api_should_generate_expected_open_api_document()
builder.Services.AddApiVersioning( options => AddPolicies( options ) )
.AddApiExplorer( options => options.GroupNameFormat = "'v'VVV" )
.AddOpenApi();
- builder.Services.AddMvcCore()
- .ConfigureApplicationPartManager(
- m => m.ApplicationParts.Clear() );
+
+ IsolateMinimalApis( builder.Services );
var app = builder.Build();
var api = app.NewVersionedApi( "Test" )
@@ -133,6 +125,9 @@ public async Task mixed_api_should_generate_expected_open_api_document()
JsonNode.DeepEquals( actual, expected ).Should().BeTrue();
}
+ private static void IsolateMinimalApis( IServiceCollection services ) =>
+ services.AddMvcCore().ConfigureApplicationPartManager( m => m.ApplicationParts.Clear() );
+
private static ApiVersioningOptions AddPolicies( ApiVersioningOptions options )
{
options.Policies.Deprecate( 1.0 )
diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/XmlCommentsTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/XmlCommentsTest.cs
index c0ae2c71..c7b6374d 100644
--- a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/XmlCommentsTest.cs
+++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/XmlCommentsTest.cs
@@ -3,6 +3,7 @@
namespace Asp.Versioning.OpenApi.Transformers;
using Asp.Versioning.OpenApi.Simulators;
+using System.Text.Json.Nodes;
public class XmlCommentsTest
{
@@ -62,6 +63,20 @@ public void response_description_should_be_retrieved_for_minimal_api()
description.Should().Be( "Pass" );
}
+ [Fact]
+ public void parameter_example_should_be_retrieved_for_minimal_api()
+ {
+ // arrange
+ var comments = XmlComments.FromFile( FilePath.XmlCommentFile );
+ var method = typeof( MinimalApi ).GetMethod( nameof( MinimalApi.Get ) );
+
+ // act
+ var example = comments.GetParameterExample( method, "id" );
+
+ // assert
+ example.Should().Be( "42" );
+ }
+
[Fact]
public void summary_should_be_retrieved_for_controller()
{
@@ -117,4 +132,47 @@ public void response_description_should_be_retrieved_for_controller()
// assert
description.Should().Be( "Fail" );
}
+
+ [Fact]
+ public void example_parameter_should_be_retrieved_for_controller()
+ {
+ // arrange
+ var comments = XmlComments.FromFile( FilePath.XmlCommentFile );
+ var method = typeof( TestController ).GetMethod( nameof( TestController.Get ) );
+
+ // act
+ var example = comments.GetParameterExample( method, "id" );
+
+ // assert
+ example.Should().Be( "42" );
+ }
+
+ [Fact]
+ public void example_property_should_be_retrieved_from_model()
+ {
+ // arrange
+ var comments = XmlComments.FromFile( FilePath.XmlCommentFile );
+ var property = typeof( Model ).GetProperty( nameof( Model.User ) );
+ var expected = JsonNode.Parse( """{"userName":"John Doe","email":"john.doe@example.com"}""" );
+
+ // act
+ var actual = JsonNode.Parse( comments.GetExample( property ) );
+
+ // assert
+ JsonNode.DeepEquals( expected, actual ).Should().BeTrue();
+ }
+
+ [Fact]
+ public void example_property_should_be_retrieved_from_nested_model()
+ {
+ // arrange
+ var comments = XmlComments.FromFile( FilePath.XmlCommentFile );
+ var property = typeof( User ).GetProperty( nameof( User.Email ) );
+
+ // act
+ var example = comments.GetExample( property );
+
+ // assert
+ example.Should().Be( "user@example.com" );
+ }
}
\ No newline at end of file