dotnet / dotnet/aspnetcore

Make HttpContext available to OpenAPI transformers

Open
#56,189 3 comments 0 reactions 0 assignees View on GitHub
api-suggestion area-minimal area-mvc feature-openapi
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

## Background and Motivation

I was looking at how to wire up the `servers` property based on the current HttpContext [like NSwag does](https://github.com/RicoSuter/NSwag/blob/6b49eefa4048cecfd41ba0d7484bd1f25543d22d/src/NSwag.AspNetCore/Middlewares/OpenApiDocumentMiddleware.cs#L144-L147) after opening #56188, and noticed that the `HttpContext` isn't immediately available to any OpenAPI transformers.

It can be easily be made available via `IHttpContextAccessor`, but that's often frowned upon from a performance perspective, so I figured it would be worth raising the possibility of making it available from the request pipeline to avoid the need to do that.

The `HttpContext` could be passed through into `OpenApiDocumentService.GetOpenApiDocumentAsync()` here:

https://github.com/dotnet/aspnetcore/blob/2b5d2b36a04f3a4a9bb20bbca38617d1cd6a3a1a/src/OpenApi/src/Extensions/OpenApiEndpointRouteBuilderExtensions.cs#L46

Then it can be directly assigned into the various context objects passed to any transformers, as well as being available to the document service itself.

## Proposed API

```diff
namespace Microsoft.AspNetCore.OpenApi;

public sealed partial class OpenApiDocumentTransformerContext
{
+ ///
+ /// Gets the HTTP context associated with the current HTTP request.
+ ///
+ public required HttpContext { get; init }
}

public sealed partial class OpenApiOperationTransformerContext
{
+ ///
+ /// Gets the HTTP context associated with the current HTTP request.
+ ///
+ public required HttpContext { get; init }
}

public sealed partial class OpenApiSchemaTransformerContext
{
+ ///
+ /// Gets the HTTP context associated with the current HTTP request.
+ ///
+ public required HttpContext { get; init }
}
```

## Usage Examples

```csharp
internal sealed class AddServersTransformer : IOpenApiDocumentTransformer
{
///
public Task TransformAsync(
OpenApiDocument document,
OpenApiDocumentTransformerContext context,
CancellationToken cancellationToken)
{
document.Servers = [new() { Url = GetServerUrl(context) }];
return Task.CompletedTask;
}

private static string GetServerUrl(OpenApiDocumentTransformerContext context)
{
var request = context.HttpContext.Request;

var scheme = TryGetFirstHeader("X-Forwarded-Proto") ?? request.Scheme;
var host = TryGetFirstHeader("X-Forwarded-Host") ?? request.Host.ToString();

return new Uri($"{scheme}://{host}").ToString().TrimEnd('/');

string? TryGetFirstHeader(string name)
=> request.Headers.TryGetValue(name, out var values) ? values.FirstOrDefault() : null;
}
}
```

## Alternative Designs

None.

## Risks

None?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.