File: Extensions\OpenApiEndpointRouteBuilderExtensions.cs
Web Access
Project: src\src\OpenApi\src\Microsoft.AspNetCore.OpenApi.csproj (Microsoft.AspNetCore.OpenApi)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Writers;
 
namespace Microsoft.AspNetCore.Builder;
 
/// <summary>
/// OpenAPI-related methods for <see cref="IEndpointRouteBuilder"/>.
/// </summary>
public static class OpenApiEndpointRouteBuilderExtensions
{
    /// <summary>
    /// Register an endpoint onto the current application for resolving the OpenAPI document associated
    /// with the current application.
    /// </summary>
    /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
    /// <param name="pattern">The route to register the endpoint on. Must include the 'documentName' route parameter.</param>
    /// <returns>An <see cref="IEndpointRouteBuilder"/> that can be used to further customize the endpoint.</returns>
    public static IEndpointConventionBuilder MapOpenApi(this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern = OpenApiConstants.DefaultOpenApiRoute)
    {
        var options = endpoints.ServiceProvider.GetRequiredService<IOptionsMonitor<OpenApiOptions>>();
        return endpoints.MapGet(pattern, async (HttpContext context, string documentName = OpenApiConstants.DefaultDocumentName) =>
            {
                // We need to retrieve the document name in a case-insensitive manner
                // to support case-insensitive document name resolution.
                // Keyed Services are case-sensitive by default, which doesn't work well for document names in ASP.NET Core
                // as routing in ASP.NET Core is case-insensitive by default.
                var lowercasedDocumentName = documentName.ToLowerInvariant();
 
                // It would be ideal to use the `HttpResponseStreamWriter` to
                // asynchronously write to the response stream here but Microsoft.OpenApi
                // does not yet support async APIs on their writers.
                // See https://github.com/microsoft/OpenAPI.NET/issues/421 for more info.
                var documentService = context.RequestServices.GetKeyedService<OpenApiDocumentService>(lowercasedDocumentName);
                if (documentService is null)
                {
                    context.Response.StatusCode = StatusCodes.Status404NotFound;
                    context.Response.ContentType = "text/plain;charset=utf-8";
                    await context.Response.WriteAsync($"No OpenAPI document with the name '{lowercasedDocumentName}' was found.");
                }
                else
                {
                    var document = await documentService.GetOpenApiDocumentAsync(context.RequestServices, context.RequestAborted);
                    var documentOptions = options.Get(documentName);
                    using var output = MemoryBufferWriter.Get();
                    using var writer = Utf8BufferTextWriter.Get(output);
                    try
                    {
                        if (UseYaml(pattern))
                        {
                            document.Serialize(new OpenApiYamlWriter(writer), documentOptions.OpenApiVersion);
                            context.Response.ContentType = "text/plain+yaml;charset=utf-8";
                        }
                        else
                        {
                            document.Serialize(new OpenApiJsonWriter(writer), documentOptions.OpenApiVersion);
                            context.Response.ContentType = "application/json;charset=utf-8";
                        }
                        await context.Response.BodyWriter.WriteAsync(output.ToArray(), context.RequestAborted);
                        await context.Response.BodyWriter.FlushAsync(context.RequestAborted);
                    }
                    finally
                    {
                        MemoryBufferWriter.Return(output);
                        Utf8BufferTextWriter.Return(writer);
                    }
 
                }
            }).ExcludeFromDescription();
    }
 
    private static bool UseYaml(string pattern) =>
        pattern.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase) ||
        pattern.EndsWith(".yml", StringComparison.OrdinalIgnoreCase);
}