ASP.NET Core'da HttpContext kullanma

HttpContext tek bir HTTP isteği ve yanıtı hakkındaki tüm bilgileri kapsüller. HTTP HttpContext isteği alındığında örnek başlatılır. Örneğe HttpContext Web API denetleyicileri, SayfalarSignalR, Razor gRPC ve daha fazlası gibi ara yazılım ve uygulama çerçeveleri tarafından erişilebilir.

öğesine erişme HttpContexthakkında daha fazla bilgi için bkz . ASP.NET Core'da HttpContext'e erişme.

HttpRequest

HttpContext.Request öğesine erişim HttpRequestsağlar. HttpRequest gelen HTTP isteği hakkında bilgi sahibidir ve sunucu tarafından bir HTTP isteği alındığında başlatılır. HttpRequest salt okunur değildir ve ara yazılım, ara yazılım işlem hattındaki istek değerlerini değiştirebilir.

Üzerinde HttpRequest yaygın olarak kullanılan üyeler şunlardır:

Özellik Açıklama Örnek
HttpRequest.Path İstek yolu. /en/article/getstarted
HttpRequest.Method İstek yöntemi. GET
HttpRequest.Headers İstek üst bilgileri koleksiyonu. user-agent=Edge
x-custom-header=MyValue
HttpRequest.RouteValues Yol değerleri koleksiyonu. İstek bir yolla eşleştirildiğinde koleksiyon ayarlanır. language=en
article=getstarted
HttpRequest.Query 'den QueryStringayrıştırılan sorgu değerleri koleksiyonu. filter=hello
page=1
HttpRequest.ReadFormAsync() İstek gövdesini form olarak okuyan ve form değerleri koleksiyonu döndüren bir yöntem. Form verilerine erişmek için neden ReadFormAsync kullanılması gerektiği hakkında bilgi için bkz . Request.Form yerine ReadFormAsync'i tercih etme. email=user@contoso.com
password=TNkt4taM
HttpRequest.Body İstek gövdesini okumak için A Stream . UTF-8 JSON yükü

İstek üst bilgilerini alma

HttpRequest.Headers HTTP isteğiyle gönderilen istek üst bilgilerine erişim sağlar. Bu koleksiyonu kullanarak üst bilgilere erişmenin iki yolu vardır:

  • Üst bilgi koleksiyonundaki dizin oluşturucuya üst bilgi adını sağlayın. Üst bilgi adı büyük/küçük harfe duyarlı değildir. Dizin oluşturucu herhangi bir üst bilgi değerine erişebilir.
  • Üst bilgi koleksiyonu, sık kullanılan HTTP üst bilgilerini alma ve ayarlama özelliklerine de sahiptir. Özellikler, üst bilgiye erişmek için hızlı, IntelliSense temelli bir yol sağlar.
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();

Birden çok kez görüntülenen üst bilgileri verimli bir şekilde işleme hakkında bilgi için bkz . StringValues'a kısa bir bakış.

Okuma isteği gövdesi

HTTP isteği bir istek gövdesi içerebilir. İstek gövdesi, bir HTML formunun içeriği, UTF-8 JSON yükü veya bir dosya gibi istekle ilişkili verilerdir.

HttpRequest.Body , istek gövdesinin ile Streamokunmasına izin verir:

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 doğrudan okunabilir veya akışı kabul eden diğer API'lerle birlikte kullanılabilir.

Dekont

Minimum API'ler doğrudan bir Stream parametreye bağlamayı HttpRequest.Body destekler.

İstek gövdesi arabelleği oluşturmayı etkinleştirme

İstek gövdesi baştan sona yalnızca bir kez okunabilir. İstek gövdesinin yalnızca ileriye doğru okunması, istek gövdesinin tamamını arabelleğe alma yükünü önler ve bellek kullanımını azaltır. Ancak bazı senaryolarda istek gövdesini birden çok kez okumanız gerekir. Örneğin ara yazılımların istek gövdesini okuması ve uç nokta için kullanılabilir olması için geri sarması gerekebilir.

EnableBuffering Uzantı yöntemi, HTTP isteği gövdesinin arabelleğe alınmasını sağlar ve birden çok okumayı etkinleştirmenin önerilen yoludur. bir istek herhangi bir boyutta olabileceği için, EnableBuffering büyük istek gövdelerini diske arabelleğe alma veya tamamen reddetme seçeneklerini destekler.

Aşağıdaki örnekteki ara yazılım:

  • ile EnableBufferingbirden çok okumayı etkinleştirir. İstek gövdesi okunmadan önce çağrılmalıdır.
  • İstek gövdesini okur.
  • diğer ara yazılımların veya uç noktanın okuyabilmesi için istek gövdesini başlangıç ekranına geri sarar.
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

İstek gövdesini okumanın alternatif bir yolu özelliğini kullanmaktır HttpRequest.BodyReader . BodyReader özelliği istek gövdesini olarak PipeReaderkullanıma sunar. Bu API, istek gövdesini okumanın gelişmiş, yüksek performanslı bir yolu olan G/Ç işlem hatlarından alınmaktadır.

Okuyucu doğrudan istek gövdesine erişir ve arayan adına belleği yönetir. aksine HttpRequest.Body, okuyucu istek verilerini arabelleğe kopyalamaz. Ancak okuyucu kullanımı bir akıştan daha karmaşıktır ve dikkatli kullanılmalıdır.

'den BodyReaderiçerik okuma hakkında bilgi için bkz . G/Ç işlem hatları PipeReader.

HttpResponse

HttpContext.Response öğesine erişim HttpResponsesağlar. HttpResponse , istemciye geri gönderilen HTTP yanıtıyla ilgili bilgileri ayarlamak için kullanılır.

Üzerinde HttpResponse yaygın olarak kullanılan üyeler şunlardır:

Özellik Açıklama Örnek
HttpResponse.StatusCode Yanıt kodu. Yanıt gövdesine yazmadan önce ayarlanmalıdır. 200
HttpResponse.ContentType Yanıt content-type üst bilgisi. Yanıt gövdesine yazmadan önce ayarlanmalıdır. application/json
HttpResponse.Headers Yanıt üst bilgileri koleksiyonu. Yanıt gövdesine yazmadan önce ayarlanmalıdır. server=Kestrel
x-custom-header=MyValue
HttpResponse.Body Stream Yanıt gövdesini yazmak için A. Oluşturulan web sayfası

Yanıt üst bilgilerini ayarlama

HttpResponse.Headers HTTP yanıtıyla gönderilen yanıt üst bilgilerine erişim sağlar. Bu koleksiyonu kullanarak üst bilgilere erişmenin iki yolu vardır:

  • Üst bilgi koleksiyonundaki dizin oluşturucuya üst bilgi adını sağlayın. Üst bilgi adı büyük/küçük harfe duyarlı değildir. Dizin oluşturucu herhangi bir üst bilgi değerine erişebilir.
  • Üst bilgi koleksiyonu, sık kullanılan HTTP üst bilgilerini alma ve ayarlama özelliklerine de sahiptir. Özellikler, üst bilgiye erişmek için hızlı, IntelliSense temelli bir yol sağlar.
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();

Yanıt başladıktan sonra uygulama üst bilgileri değiştiremez. Yanıt başladıktan sonra üst bilgiler istemciye gönderilir. Yanıt, yanıt gövdesi temizlenerek veya çağrılarak HttpResponse.StartAsync(CancellationToken)başlatılır. HttpResponse.HasStarted özelliği yanıtın başlatılıp başlatılmadığını gösterir. Yanıt başlatıldıktan sonra üst bilgileri değiştirmeye çalışılırken bir hata oluşur:

System.InvalidOperationException: Üst bilgiler salt okunur, yanıt zaten başlatıldı.

Dekont

Yanıt arabelleğe alma etkinleştirilmediği sürece, tüm yazma işlemleri (örneğin, WriteAsync) yanıt gövdesini dahili olarak temizler ve yanıtı başlatıldı olarak işaretler. Yanıt arabelleği varsayılan olarak devre dışıdır.

Yanıt gövdesi yazma

HTTP yanıtı bir yanıt gövdesi içerebilir. Yanıt gövdesi, oluşturulan web sayfası içeriği, UTF-8 JSON yükü veya bir dosya gibi yanıtla ilişkili verilerdir.

HttpResponse.Body yanıt gövdesinin ile Streamyazılmasına izin verir:

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 doğrudan yazılabilir veya bir akışa yazan diğer API'lerle birlikte kullanılabilir.

Bodywriter

Yanıt gövdesini yazmanın alternatif bir yolu özelliğini kullanmaktır HttpResponse.BodyWriter . BodyWriter özelliği, yanıt gövdesini olarak PipeWriterkullanıma sunar. Bu API G/Ç işlem hatlarından ve yanıtı yazmanın gelişmiş, yüksek performanslı bir yoludur.

Yazıcı, yanıt gövdesine doğrudan erişim sağlar ve arayan adına belleği yönetir. 'nin aksine HttpResponse.Body, yazma işlemi istek verilerini arabelleğe kopyalamaz. Ancak, bir yazıcının kullanımı, bir akış ve yazıcı kodunun kapsamlı bir şekilde test edilmesinden daha karmaşıktır.

öğesine içerik BodyWriteryazma hakkında bilgi için bkz . G/Ç işlem hatları PipeWriter.

Yanıt fragmanlarını ayarlama

HTTP/2 ve HTTP/3, yanıt fragmanlarını destekler. Römorklar, yanıt gövdesi tamamlandıktan sonra yanıtla birlikte gönderilen üst bilgilerdir. Römorklar yanıt gövdesinden sonra gönderildiğinden, römorklar istediğiniz zaman yanıta eklenebilir.

Aşağıdaki kod, kullanarak AppendTrailerfragmanları ayarlar:

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

İptal HttpContext.RequestAborted belirteci, HTTP isteğinin istemci veya sunucu tarafından durdurulduğunu bildirmek için kullanılabilir. İstek durdurulduysa iptal edilebilmeleri için iptal belirteci uzun süre çalışan görevlere geçirilmelidir. Örneğin, yanıtta döndürülecek verileri almak için bir veritabanı sorgusunu veya HTTP isteğini iptal etme.

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();

İstek RequestAborted durdurulduğunda okuma işlemleri her zaman hemen oluştuğundan iptal belirtecinin istek gövdesi okuma işlemleri için kullanılması gerekmez. RequestAborted İstek durdurulduğunda hemen işlem yapılmadan yazdığından, yanıt gövdeleri yazılırken belirteç de genellikle gereksizdir.

Bazı durumlarda, belirteci yazma işlemlerine geçirmek RequestAborted , bir yazma döngüsünü ile erken çıkmaya zorlamanın kullanışlı bir OperationCanceledExceptionyolu olabilir. Ancak, genellikle belirteci yanıt gövdesi içeriğini almaktan sorumlu zaman uyumsuz işlemlere geçirmek RequestAborted daha iyidir.

Dekont

Minimum API'ler doğrudan bir CancellationToken parametreye bağlamayı HttpContext.RequestAborted destekler.

Abort()

yöntemi, HttpContext.Abort() sunucudan gelen bir HTTP isteğini durdurmak için kullanılabilir. HTTP isteğinin iptali hemen iptal belirtecini HttpContext.RequestAborted tetikler ve istemciye sunucunun isteği durdurduğunu belirten bir bildirim gönderir.

Aşağıdaki örnekteki ara yazılım:

  • Kötü amaçlı istekler için özel bir denetim ekler.
  • İstek kötü amaçlıysa HTTP isteğini durdurur.
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

HttpContext.User özelliği, istek için tarafından temsil edilen ClaimsPrincipalkullanıcıyı almak veya ayarlamak için kullanılır. ClaimsPrincipal genellikle ASP.NET Core kimlik doğrulaması tarafından ayarlanır.

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();

Dekont

Minimum API'ler doğrudan bir ClaimsPrincipal parametreye bağlamayı HttpContext.User destekler.

Features

özelliği, HttpContext.Features geçerli istek için özellik arabirimleri koleksiyonuna erişim sağlar. Özellik koleksiyonu bir istek bağlamında bile değişebilir olduğundan, ara yazılım koleksiyonu değiştirmek ve ek özellikler için destek eklemek için kullanılabilir. Bazı gelişmiş özellikler yalnızca özellik koleksiyonu aracılığıyla ilişkili arabirime erişilerek kullanılabilir.

Aşağıdaki örnek:

  • Özellikler koleksiyonundan alır IHttpMinRequestBodyDataRateFeature .
  • Null olarak ayarlanır MinDataRate . Bu, istek gövdesinin bu HTTP isteği için istemci tarafından gönderilmesi gereken en düşük veri hızını kaldırır.
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();

ve HttpContextistek özelliklerini kullanma hakkında daha fazla bilgi için bkz . ASP.NET Core'da İstek Özellikleri.

HttpContext iş parçacığı güvenli değil

Bu makalede öncelikle Sayfalar, denetleyiciler, ara yazılım vb. istek ve yanıt akışında Razor kullanımı HttpContext ele alınmaktadır. İstek ve yanıt akışının dışında kullanırken HttpContext aşağıdakileri göz önünde bulundurun:

  • HttpContext İş parçacığı güvenli DEĞİlDİ, birden çok iş parçacığından erişildiğinde özel durumlara, veri bozulmasına ve genel olarak öngörülemeyen sonuçlara neden olabilir.
  • Arabirim IHttpContextAccessor dikkatli kullanılmalıdır. Her zaman olduğu gibi, HttpContext istek akışının dışında yakalanmamalıdır. IHttpContextAccessor:
    • Zaman uyumsuz çağrılar AsyncLocal<T> üzerinde olumsuz bir performans etkisine sahip olabilecek değerlerdir.
    • Testi daha zor hale getirebilecek bir "ortam durumu" bağımlılığı oluşturur.
  • IHttpContextAccessor.HttpContext , istek akışının dışından erişilirse olabilir null .
  • İstek akışının dışından HttpContext bilgilere erişmek için, bilgileri istek akışının içine kopyalayın. Yalnızca başvuruları değil gerçek verileri kopyalamaya dikkat edin. Örneğin, başvuruyu bir IHeaderDictionaryöğesine kopyalamak yerine ilgili üst bilgi değerlerini kopyalayın veya istek akışından çıkmadan önce anahtara göre sözlük anahtarının tamamını kopyalayın.
  • Oluşturucuda yakalama IHttpContextAccessor.HttpContext .

Aşağıdaki örnek, uç noktadan istendiğinde GitHub dallarını günlüğe /branch kaydeder:

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();

GitHub API'sinde iki üst bilgi gerekir. User-Agent Üst bilgi, tarafından UserAgentHeaderHandlerdinamik olarak eklenir:

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();

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);
    }
}

Yukarıdaki kodda, HttpContext olduğunda nulluserAgent dize olarak "Unknown"ayarlanır. Mümkünse, HttpContext hizmete açıkça geçirilmelidir. Verileri açıkça geçirme HttpContext :

  • Hizmet API'sini istek akışının dışında daha kullanılabilir hale getirir.
  • Performans için daha iyidir.
  • Kodun anlaşılmasını ve anlaşılmasını, ortam durumuna bağlı olmaktan daha kolay hale getirir.

Hizmetin erişmesi HttpContextgerektiğinde, istek iş parçacığından çağrılmama olasılığını HttpContextnull hesaba eklemelidir.

Uygulama, belirtilen deponun açık GitHub dallarını 30 saniyede bir günlüğe kaydeden öğesini de içerir PeriodicBranchesLoggerService:

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 , istek ve yanıt akışının dışında çalışan barındırılan bir hizmettir. 'den günlüğe PeriodicBranchesLoggerService kaydetmenin null HttpContextdeğeri vardır. , PeriodicBranchesLoggerService öğesine bağlı HttpContextdeğil olarak yazılmıştır.

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 =>
{