Delen via


HttpContext gebruiken in ASP.NET Core

Notitie

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Belangrijk

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikelvoor de huidige release.

HttpContext bevat alle informatie over een afzonderlijke HTTP-aanvraag en -reactie. Een HttpContext-exemplaar wordt geïnitialiseerd wanneer een HTTP-aanvraag wordt ontvangen. Het HttpContext-exemplaar is toegankelijk via middleware en app-frameworks, zoals Blazor Web Apps, Web-API-controllers, Razor Pagina's, SignalR, gRPC en meer.

HttpRequest

HttpContext.Request biedt toegang tot HttpRequest. HttpRequest informatie heeft over de binnenkomende HTTP-aanvraag en deze wordt geïnitialiseerd wanneer een HTTP-aanvraag door de server wordt ontvangen. HttpRequest is niet alleen-lezen en middleware kan verzoekwaarden in de middleware-pijplijn wijzigen.

Veelgebruikte leden op HttpRequest zijn onder andere:

Eigenschap Beschrijving Voorbeeld
HttpRequest.Path Het aanvraagpad. /en/article/getstarted
HttpRequest.Method De aanvraagmethode. GET
HttpRequest.Headers Een verzameling verzoekheaders. user-agent=Edge
x-custom-header=MyValue
HttpRequest.RouteValues Een verzameling routewaarden. De verzameling wordt ingesteld wanneer de aanvraag overeenkomt met een route. language=en
article=getstarted
HttpRequest.Query Een verzameling querywaarden die worden geparseerd uit QueryString. filter=hello
page=1
httpRequest.ReadFormAsync() Een methode die de hoofdtekst van de aanvraag als formulier leest en een verzameling formulierwaarden retourneert. Zie Prefer ReadFormAsync over Request.Formvoor informatie over waarom ReadFormAsync moet worden gebruikt voor toegang tot formuliergegevens. email=user@contoso.com
HttpRequest.Body Een Stream voor het lezen van de aanvraagtekst. UTF-8 JSON-payload

Aanvraagheaders ophalen

HttpRequest.Headers biedt toegang tot de aanvraagheaders die worden verzonden met de HTTP-aanvraag. Er zijn twee manieren om toegang te krijgen tot headers met behulp van deze verzameling:

  • Geef de naam van de header op voor de indexeerfunctie in de headerverzameling. De naam van de header is niet hoofdlettergevoelig. De indexeerfunctie heeft toegang tot elke headerwaarde.
  • De headerverzameling bevat ook eigenschappen voor het ophalen en instellen van veelgebruikte HTTP-headers. De eigenschappen bieden een snelle, op IntelliSense gestuurde manier om toegang te krijgen tot headers.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", (HttpRequest request) =>
{
    var userAgent = request.Headers.UserAgent;
    var customHeader = request.Headers["x-custom-header"];

    return Results.Ok(new { userAgent = userAgent, customHeader = customHeader });
});

app.Run();

Zie Een kort overzicht van StringValuesvoor informatie over het efficiënt verwerken van headers die meer dan één keer worden weergegeven.

Hoofdtekst van aanvraag lezen

Een HTTP-aanvraag kan een aanvraagbody bevatten. De hoofdtekst van de aanvraag is data die is geassocieerd met de aanvraag, zoals de inhoud van een HTML-formulier, UTF-8 JSON-gegevens of een bestand.

HttpRequest.Body kan de aanvraagtekst worden gelezen met Stream:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/uploadstream", async (IConfiguration config, HttpContext context) =>
{
    var filePath = Path.Combine(config["StoredFilesPath"], Path.GetRandomFileName());

    await using var writeStream = File.Create(filePath);
    await context.Request.Body.CopyToAsync(writeStream);
});

app.Run();

HttpRequest.Body kan rechtstreeks worden gelezen of gebruikt met andere API's die stream accepteren.

Notitie

Minimale API's ondersteunt het rechtstreeks binden van HttpRequest.Body aan een Stream parameter.

Buffering van aanvraagbody inschakelen

De hoofdtekst van de aanvraag kan slechts eenmaal worden gelezen, van begin tot eind. Het uitsluitend voorwaarts lezen van de aanvraagbody voorkomt de last van het bufferen van de gehele aanvraagbody en vermindert het geheugengebruik. In sommige scenario's is het echter nodig om de aanvraagtekst meerdere keren te lezen. Middleware moet bijvoorbeeld de hoofdtekst van de aanvraag lezen en deze vervolgens terugspoelen, zodat deze beschikbaar is voor het eindpunt.

De EnableBuffering-extensiemethode maakt buffering van de HOOFDtekst van de HTTP-aanvraag mogelijk en is de aanbevolen manier om meerdere leesbewerkingen in te schakelen. Omdat een aanvraag elke grootte kan hebben, ondersteunt EnableBuffering opties voor het bufferen van grote aanvraagbody's op schijf of het volledig weigeren ervan.

De middleware in het volgende voorbeeld:

  • Hiermee worden meerdere leesbewerkingen met EnableBufferingingeschakeld. Deze moet worden aangeroepen voordat je de aanvraagtekst leest.
  • Leest de hoofdtekst van de aanvraag.
  • Hiermee wordt de hoofdtekst van de aanvraag teruggespoelen naar het begin, zodat andere middleware of het eindpunt deze kunnen lezen.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    context.Request.EnableBuffering();
    await ReadRequestBody(context.Request.Body);
    context.Request.Body.Position = 0;
    
    await next.Invoke();
});

app.Run();

BodyReader

Een alternatieve manier om de hoofdtekst van de aanvraag te lezen, is door de eigenschap HttpRequest.BodyReader te gebruiken. Met de eigenschap BodyReader wordt de request body weergegeven als een PipeReader. Deze API is afkomstig van I/O-pijplijnen, een geavanceerde, krachtige manier om de aanvraagbody te lezen.

De lezer heeft rechtstreeks toegang tot de hoofdtekst van de aanvraag en beheert het geheugen namens de beller. In tegenstelling tot HttpRequest.Bodykopieert de lezer geen aanvraaggegevens naar een buffer. Een lezer is echter ingewikkelder te gebruiken dan een stream en moet voorzichtig worden gebruikt.

Zie voor informatie over het lezen van inhoud uit , de I/O pijplijnen PipeReaderin .

HttpResponse

HttpContext.Response biedt toegang tot HttpResponse. HttpResponse wordt gebruikt om informatie in te stellen over het HTTP-antwoord dat naar de client wordt verzonden.

Veelgebruikte leden op HttpResponse zijn onder andere:

Eigenschap Beschrijving Voorbeeld
HttpResponse.StatusCode De antwoordcode. Moet worden ingesteld voordat u naar de hoofdtekst van het antwoord schrijft. 200
HttpResponse.ContentType Het antwoord content-type koptekst. Moet worden ingesteld voordat u naar de hoofdtekst van het antwoord schrijft. application/json
HttpResponse.Headers Een verzameling antwoordheaders. Moet worden ingesteld voordat u naar de hoofdtekst van het antwoord schrijft. server=Kestrel
x-custom-header=MyValue
HttpResponse.Body Een Stream voor het schrijven van de hoofdtekst van het antwoord. Gegenereerde webpagina

Antwoordheaders instellen

HttpResponse.Headers biedt toegang tot de antwoordheaders die worden verzonden met het HTTP-antwoord. Er zijn twee manieren om toegang te krijgen tot headers met behulp van deze verzameling:

  • Geef de naam van de header op voor de indexeerfunctie in de headerverzameling. De naam van de header is niet hoofdlettergevoelig. De indexeerfunctie heeft toegang tot elke headerwaarde.
  • Gebruik de eigenschappen van de headerverzameling voor het ophalen en instellen van veelgebruikte HTTP-headers. De eigenschappen bieden een snelle manier om, gestuurd door IntelliSense, toegang te krijgen tot headers.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", (HttpResponse response) =>
{
    response.Headers.CacheControl = "no-cache";
    response.Headers["x-custom-header"] = "Custom value";

    return Results.File(File.OpenRead("helloworld.txt"));
});

app.Run();

Een app kan headers niet wijzigen nadat het antwoord is gestart. Zodra het antwoord is gestart, worden de headers naar de client verzonden. Een antwoord wordt gestart door de hoofdtekst van het antwoord te leegmaken of HttpResponse.StartAsync(CancellationToken)aan te roepen. De eigenschap HttpResponse.HasStarted geeft aan of het antwoord is gestart. Er treedt een fout op bij het wijzigen van headers nadat het antwoord is gestart:

System.InvalidOperationException: Headers zijn alleen-lezen, antwoord is al gestart.

Notitie

Tenzij reactiebuffering is ingeschakeld, worden alle schrijfbewerkingen (bijvoorbeeld WriteAsync) de hoofdtekst van het antwoord intern leeggemaakt en wordt het antwoord gemarkeerd als gestart. Reactiebuffering is standaard uitgeschakeld.

Schrijf antwoordtekst

Een HTTP-antwoord kan een antwoordtekst bevatten. De hoofdtekst van het antwoord bevat gegevens die zijn gekoppeld aan het antwoord, zoals gegenereerde webpagina-inhoud, UTF-8 JSON-nettolading of een bestand.

De hoofdtekst van het antwoord kan met Streamworden geschreven door HttpResponse.Body.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/downloadfile", async (IConfiguration config, HttpContext context) =>
{
    var filePath = Path.Combine(config["StoredFilesPath"], "helloworld.txt");

    await using var fileStream = File.OpenRead(filePath);
    await fileStream.CopyToAsync(context.Response.Body);
});

app.Run();

HttpResponse.Body kan rechtstreeks worden geschreven of worden gebruikt met andere API's die naar een stream schrijven.

BodyWriter

U kunt de hoofdtekst van het antwoord ook schrijven door de eigenschap HttpResponse.BodyWriter te gebruiken. Met de eigenschap BodyWriter wordt de hoofdtekst van het antwoord weergegeven als een PipeWriter. Deze API is afkomstig van I/O-pijplijnenen het is een geavanceerde, krachtige manier om het antwoord te schrijven.

De schrijver biedt directe toegang tot de hoofdtekst van het antwoord en beheert het geheugen namens de beller. In tegenstelling tot HttpResponse.Body, kopieert de schrijfbewerking geen aanvraaggegevens naar een buffer. Een schrijver is echter ingewikkelder om te gebruiken dan een stream en schrijfcode moet grondig worden getest.

Zie voor informatie over het schrijven van inhoud naar BodyWriterin I/O-pijplijnen PipeWriter.

Antwoordtrailers instellen

HTTP/2 en HTTP/3 ondersteunen antwoordtrailers. Trailers zijn headers die met het antwoord worden verzonden nadat de hoofdtekst van het antwoord is voltooid. Omdat trailers na de antwoordbody worden verzonden, kunnen trailers op elk gewenst moment aan het antwoord worden toegevoegd.

De volgende code stelt trailers in met behulp van AppendTrailer:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", (HttpResponse response) =>
{
    // Write body
    response.WriteAsync("Hello world");

    if (response.SupportsTrailers())
    {
        response.AppendTrailer("trailername", "TrailerValue");
    }
});

app.Run();

RequestAborted

Het HttpContext.RequestAborted annuleringstoken kan worden gebruikt om te melden dat de HTTP-aanvraag is afgebroken door de client of server. Het annuleringstoken moet worden doorgegeven aan langlopende taken, zodat ze kunnen worden geannuleerd als de aanvraag wordt afgebroken. Bijvoorbeeld het afbreken van een databasequery of HTTP-aanvraag om gegevens op te halen die in het antwoord moeten worden geretourneerd.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var httpClient = new HttpClient();
app.MapPost("/books/{bookId}", async (int bookId, HttpContext context) =>
{
    var stream = await httpClient.GetStreamAsync(
        $"http://contoso/books/{bookId}.json", context.RequestAborted);

    // Proxy the response as JSON
    return Results.Stream(stream, "application/json");
});

app.Run();

Het RequestAborted annuleringstoken hoeft niet te worden gebruikt voor leesbewerkingen van de aanvraagbody, omdat leesbewerkingen altijd onmiddellijk worden gegenereerd wanneer de aanvraag wordt afgebroken. Het RequestAborted token is meestal ook niet nodig bij het schrijven van antwoordteksten, omdat schrijfbewerkingen onmiddellijk no-op wanneer de aanvraag wordt afgebroken.

In sommige gevallen kan het doorgeven van de RequestAborted-token aan schrijfbewerkingen een handige manier zijn om een schrijflus voortijdig af te sluiten met een OperationCanceledException. Het is echter gebruikelijker om het RequestAborted token door te geven aan asynchrone bewerkingen die verantwoordelijk zijn voor het ophalen van de inhoud van de antwoordtekst.

Notitie

Minimale API's ondersteunt het rechtstreeks binden van HttpContext.RequestAborted aan een CancellationToken parameter.

Abort()

De methode HttpContext.Abort() kan worden gebruikt om een HTTP-aanvraag van de server af te breken. Wanneer de HTTP-aanvraag wordt afgebroken, wordt het HttpContext.RequestAborted annuleringstoken geactiveerd en wordt er een melding verzonden naar de client dat de server de aanvraag heeft afgebroken.

De middleware in het volgende voorbeeld:

  • Voegt een aangepaste controle toe voor schadelijke aanvragen.
  • Hiermee wordt de HTTP-aanvraag afgebroken als de aanvraag schadelijk is.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    if (RequestAppearsMalicious(context.Request))
    {
        // Malicious requests don't even deserve an error response (e.g. 400).
        context.Abort();
        return;
    }

    await next.Invoke();
});

app.Run();

User

De eigenschap HttpContext.User wordt gebruikt om de gebruiker, vertegenwoordigd door ClaimsPrincipal, voor de aanvraag op te halen of in te stellen. De ClaimsPrincipal wordt doorgaans ingesteld door ASP.NET Core-verificatie.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/user/current", [Authorize] async (HttpContext context) =>
{
    var user = await GetUserAsync(context.User.Identity.Name);
    return Results.Ok(user);
});

app.Run();

Notitie

Minimale API's ondersteunt het rechtstreeks binden van HttpContext.User aan een ClaimsPrincipal parameter.

Features

De eigenschap HttpContext.Features biedt toegang tot de verzameling functie-interfaces voor de huidige aanvraag. Omdat de functieverzameling kan worden gedempt, zelfs binnen de context van een aanvraag, kan middleware worden gebruikt om de verzameling te wijzigen en ondersteuning toe te voegen voor aanvullende functies. Sommige geavanceerde functies zijn alleen beschikbaar door toegang te krijgen tot de bijbehorende interface via de functieverzameling.

Het volgende voorbeeld:

  • Hiermee haalt u IHttpMinRequestBodyDataRateFeature op uit de functiesverzameling.
  • Hiermee stelt u MinDataRate in op null. Hiermee verwijdert u de minimale gegevenssnelheid waarmee de aanvraagtekst door de client moet worden verzonden voor deze HTTP-aanvraag.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/long-running-stream", async (HttpContext context) =>
{
    var feature = context.Features.Get<IHttpMinRequestBodyDataRateFeature>();
    if (feature != null)
    {
        feature.MinDataRate = null;
    }

    // await and read long-running stream from request body.
    await Task.Yield();
});

app.Run();

Zie Aanvraagfuncties in ASP.NET Corevoor meer informatie over het gebruik van aanvraagfuncties en HttpContext.

HttpContext is geen thread veilig

In dit artikel wordt voornamelijk besproken hoe u HttpContext gebruikt in de aanvraag- en antwoordstroom van Blazor Web App onderdelen, Razor Pagina's, controllers, middleware, enzovoort. Houd rekening met het volgende wanneer u HttpContext buiten de aanvraag- en antwoordstroom gebruikt:

  • De HttpContext is NIET thread veilig. Als u deze opent vanuit meerdere threads, kan dit leiden tot onvoorspelbare resultaten, zoals uitzonderingen en beschadiging van gegevens.
  • De IHttpContextAccessor interface moet voorzichtig worden gebruikt. Zoals altijd moet de HttpContextniet buiten de aanvraagstroom worden vastgelegd. IHttpContextAccessor:
    • Is afhankelijk van AsyncLocal<T>, wat een negatieve invloed kan hebben op asynchrone aanroepen.
    • Hiermee maakt u een afhankelijkheid van de 'omgevingsstatus' die het testen moeilijker kan maken.
  • IHttpContextAccessor.HttpContext kan worden null als deze buiten de aanvraagstroom wordt geopend.
  • Als u toegang wilt krijgen tot gegevens uit HttpContext buiten de aanvraagstroom, kopieert u de informatie in de aanvraagstroom. Zorg ervoor dat u de werkelijke gegevens en niet alleen verwijzingen kopieert. In plaats van bijvoorbeeld een verwijzing naar een IHeaderDictionaryte kopiëren, kopieert u de relevante headerwaarden of kopieert u de hele woordenlijstsleutel per sleutel voordat u de aanvraagstroom verlaat.
  • Leg IHttpContextAccessor.HttpContext niet vast in een constructor.

In het volgende voorbeeld worden GitHub-vertakkingen gelogd wanneer ze worden opgevraagd van het /branch-eindpunt:

using System.Text.Json;
using HttpContextInBackgroundThread;
using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpContextAccessor();
builder.Services.AddHostedService<PeriodicBranchesLoggerService>();

builder.Services.AddHttpClient("GitHub", httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com/");

    // The GitHub API requires two headers. The Use-Agent header is added
    // dynamically through UserAgentHeaderHandler
    httpClient.DefaultRequestHeaders.Add(
        HeaderNames.Accept, "application/vnd.github.v3+json");
}).AddHttpMessageHandler<UserAgentHeaderHandler>();

builder.Services.AddTransient<UserAgentHeaderHandler>();

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/branches", async (IHttpClientFactory httpClientFactory,
                         HttpContext context, Logger<Program> logger) =>
{
    var httpClient = httpClientFactory.CreateClient("GitHub");
    var httpResponseMessage = await httpClient.GetAsync(
        "repos/dotnet/AspNetCore.Docs/branches");

    if (!httpResponseMessage.IsSuccessStatusCode) 
        return Results.BadRequest();

    await using var contentStream =
        await httpResponseMessage.Content.ReadAsStreamAsync();

    var response = await JsonSerializer.DeserializeAsync
        <IEnumerable<GitHubBranch>>(contentStream);

    app.Logger.LogInformation($"/branches request: " +
                              $"{JsonSerializer.Serialize(response)}");

    return Results.Ok(response);
});

app.Run();

Voor de GitHub-API zijn twee headers vereist. De User-Agent koptekst wordt dynamisch toegevoegd door de UserAgentHeaderHandler:

using System.Text.Json;
using HttpContextInBackgroundThread;
using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpContextAccessor();
builder.Services.AddHostedService<PeriodicBranchesLoggerService>();

builder.Services.AddHttpClient("GitHub", httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com/");

    // The GitHub API requires two headers. The Use-Agent header is added
    // dynamically through UserAgentHeaderHandler
    httpClient.DefaultRequestHeaders.Add(
        HeaderNames.Accept, "application/vnd.github.v3+json");
}).AddHttpMessageHandler<UserAgentHeaderHandler>();

builder.Services.AddTransient<UserAgentHeaderHandler>();

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/branches", async (IHttpClientFactory httpClientFactory,
                         HttpContext context, Logger<Program> logger) =>
{
    var httpClient = httpClientFactory.CreateClient("GitHub");
    var httpResponseMessage = await httpClient.GetAsync(
        "repos/dotnet/AspNetCore.Docs/branches");

    if (!httpResponseMessage.IsSuccessStatusCode) 
        return Results.BadRequest();

    await using var contentStream =
        await httpResponseMessage.Content.ReadAsStreamAsync();

    var response = await JsonSerializer.DeserializeAsync
        <IEnumerable<GitHubBranch>>(contentStream);

    app.Logger.LogInformation($"/branches request: " +
                              $"{JsonSerializer.Serialize(response)}");

    return Results.Ok(response);
});

app.Run();

De UserAgentHeaderHandler:

using Microsoft.Net.Http.Headers;

namespace HttpContextInBackgroundThread;

public class UserAgentHeaderHandler : DelegatingHandler
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger _logger;

    public UserAgentHeaderHandler(IHttpContextAccessor httpContextAccessor,
                                  ILogger<UserAgentHeaderHandler> logger)
    {
        _httpContextAccessor = httpContextAccessor;
        _logger = logger;
    }

    protected override async Task<HttpResponseMessage> 
                                    SendAsync(HttpRequestMessage request, 
                                    CancellationToken cancellationToken)
    {
        var contextRequest = _httpContextAccessor.HttpContext?.Request;
        string? userAgentString = contextRequest?.Headers["user-agent"].ToString();
        
        if (string.IsNullOrEmpty(userAgentString))
        {
            userAgentString = "Unknown";
        }

        request.Headers.Add(HeaderNames.UserAgent, userAgentString);
        _logger.LogInformation($"User-Agent: {userAgentString}");

        return await base.SendAsync(request, cancellationToken);
    }
}

Wanneer de HttpContext in de voorgaande code is null, wordt de userAgent tekenreeks ingesteld op "Unknown". Indien mogelijk moet HttpContext expliciet worden doorgegeven aan de service. Expliciet HttpContext gegevens doorgeven:

  • Maakt de service-API beter bruikbaar buiten de aanvraagstroom.
  • Is beter voor prestaties.
  • Maakt de code gemakkelijker te begrijpen en te redeneren over dan afhankelijk te zijn van de omgevingsstatus.

Wanneer de service toegang moet krijgen tot HttpContext, moet deze rekening houden met de mogelijkheid dat HttpContext wordt null wanneer deze niet wordt aangeroepen vanuit een aanvraagthread.

De toepassing bevat ook PeriodicBranchesLoggerService, waarmee de geopende GitHub-vertakkingen van de opgegeven opslagplaats elke 30 seconden worden vastgelegd:

using System.Text.Json;

namespace HttpContextInBackgroundThread;

public class PeriodicBranchesLoggerService : BackgroundService
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly ILogger _logger;
    private readonly PeriodicTimer _timer;

    public PeriodicBranchesLoggerService(IHttpClientFactory httpClientFactory,
                                         ILogger<PeriodicBranchesLoggerService> logger)
    {
        _httpClientFactory = httpClientFactory;
        _logger = logger;
        _timer = new PeriodicTimer(TimeSpan.FromSeconds(30));
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (await _timer.WaitForNextTickAsync(stoppingToken))
        {
            try
            {
                // Cancel sending the request to sync branches if it takes too long
                // rather than miss sending the next request scheduled 30 seconds from now.
                // Having a single loop prevents this service from sending an unbounded
                // number of requests simultaneously.
                using var syncTokenSource = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
                syncTokenSource.CancelAfter(TimeSpan.FromSeconds(30));
                
                var httpClient = _httpClientFactory.CreateClient("GitHub");
                var httpResponseMessage = await httpClient.GetAsync("repos/dotnet/AspNetCore.Docs/branches",
                                                                    stoppingToken);

                if (httpResponseMessage.IsSuccessStatusCode)
                {
                    await using var contentStream =
                        await httpResponseMessage.Content.ReadAsStreamAsync(stoppingToken);

                    // Sync the response with preferred datastore.
                    var response = await JsonSerializer.DeserializeAsync<
                        IEnumerable<GitHubBranch>>(contentStream, cancellationToken: stoppingToken);

                    _logger.LogInformation(
                        $"Branch sync successful! Response: {JsonSerializer.Serialize(response)}");
                }
                else
                {
                    _logger.LogError(1, $"Branch sync failed! HTTP status code: {httpResponseMessage.StatusCode}");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(1, ex, "Branch sync failed!");
            }
        }
    }

    public override Task StopAsync(CancellationToken stoppingToken)
    {
        // This will cause any active call to WaitForNextTickAsync() to return false immediately.
        _timer.Dispose();
        // This will cancel the stoppingToken and await ExecuteAsync(stoppingToken).
        return base.StopAsync(stoppingToken);
    }
}

PeriodicBranchesLoggerService is een gehoste service, die buiten de aanvraag- en antwoordstroom wordt uitgevoerd. Logboekregistratie van de PeriodicBranchesLoggerService heeft een nulwaarde HttpContext. De PeriodicBranchesLoggerService was geschreven om niet afhankelijk te zijn van de HttpContext.

using System.Text.Json;
using HttpContextInBackgroundThread;
using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpContextAccessor();
builder.Services.AddHostedService<PeriodicBranchesLoggerService>();

builder.Services.AddHttpClient("GitHub", httpClient =>
{

Aanvullende informatiebronnen

Voor meer informatie over toegang tot HttpContext, zie Toegang tot HttpContext in ASP.NET Core.