Używanie obiektu HttpContext w ASP.NET Core
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.
Ostrzeżenie
Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz .NET i .NET Core Support Policy (Zasady obsługi platformy .NET Core). Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.
HttpContext Hermetyzuje wszystkie informacje dotyczące pojedynczego żądania HTTP i odpowiedzi. Wystąpienie HttpContext
jest inicjowane po odebraniu żądania HTTP. Wystąpienie HttpContext
jest dostępne przez oprogramowanie pośredniczące i struktury aplikacji, takie jak kontrolery interfejsu API sieci Web, Razor strony, SignalR, gRPC i inne.
Aby uzyskać więcej informacji na temat uzyskiwania dostępu do obiektu , zobacz Access HttpContext in ASP.NET Core (Uzyskiwanie dostępu do obiektu HttpContext
HttpContext w programie ASP.NET Core).
HttpRequest
HttpContext.Request zapewnia dostęp do HttpRequestelementu . HttpRequest
zawiera informacje o przychodzącym żądaniu HTTP i jest inicjowany po odebraniu żądania HTTP przez serwer. HttpRequest
program nie jest tylko do odczytu, a oprogramowanie pośredniczące może zmieniać wartości żądań w potoku oprogramowania pośredniczącego.
Często używane elementy członkowskie HttpRequest
obejmują:
Właściwości | opis | Przykład |
---|---|---|
HttpRequest.Path | Ścieżka żądania. | /en/article/getstarted |
HttpRequest.Method | Metoda żądania. | GET |
HttpRequest.Headers | Kolekcja nagłówków żądań. | user-agent=Edge x-custom-header=MyValue |
HttpRequest.RouteValues | Kolekcja wartości tras. Kolekcja jest ustawiana po dopasowaniu żądania do trasy. | language=en article=getstarted |
HttpRequest.Query | Kolekcja wartości zapytania przeanalizowanych z elementu QueryString. | filter=hello page=1 |
HttpRequest.ReadFormAsync() | Metoda, która odczytuje treść żądania jako formularz i zwraca kolekcję wartości formularza. Aby uzyskać informacje o tym, dlaczego ReadFormAsync należy używać do uzyskiwania dostępu do danych formularza, zobacz Prefer ReadFormAsync over Request.Form (Preferuj funkcję ReadFormAsync za pośrednictwem pliku Request.Form). |
email=user@contoso.com |
HttpRequest.Body | Element Stream do odczytywania treści żądania. | Ładunek JSON UTF-8 |
Pobieranie nagłówków żądań
HttpRequest.Headers zapewnia dostęp do nagłówków żądań wysyłanych za pomocą żądania HTTP. Istnieją dwa sposoby uzyskiwania dostępu do nagłówków przy użyciu tej kolekcji:
- Podaj nazwę nagłówka indeksatorowi w kolekcji nagłówków. Nazwa nagłówka nie uwzględnia wielkości liter. Indeksator może uzyskać dostęp do dowolnej wartości nagłówka.
- Kolekcja nagłówków zawiera również właściwości pobierania i ustawiania często używanych nagłówków HTTP. Właściwości zapewniają szybki, oparty na funkcji IntelliSense sposób uzyskiwania dostępu do nagłówków.
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();
Aby uzyskać informacje na temat wydajnej obsługi nagłówków, które pojawiają się więcej niż raz, zobacz Krótkie spojrzenie na StringValues.
Treść żądania odczytu
Żądanie HTTP może zawierać treść żądania. Treść żądania to dane skojarzone z żądaniem, takie jak zawartość formularza HTML, ładunek JSON UTF-8 lub plik.
HttpRequest.Body umożliwia odczytanie treści żądania za pomocą polecenia 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
można odczytywać bezpośrednio lub używać z innymi interfejsami API, które akceptują strumień.
Uwaga
Minimalne interfejsy API obsługują wiązanie HttpRequest.Body bezpośrednio z parametrem Stream .
Włączanie buforowania treści żądania
Treść żądania może być odczytywana tylko raz, od początku do końca. Odczyt treści żądania tylko do przodu pozwala uniknąć obciążeń związanych z buforowaniem całej treści żądania i zmniejsza użycie pamięci. Jednak w niektórych scenariuszach istnieje potrzeba wielokrotnego odczytania treści żądania. Na przykład oprogramowanie pośredniczące może wymagać odczytania treści żądania, a następnie przewijenie go, aby było dostępne dla punktu końcowego.
EnableBuffering Metoda rozszerzenia umożliwia buforowanie treści żądania HTTP i jest zalecanym sposobem włączenia wielu operacji odczytu. Ponieważ żądanie może mieć dowolny rozmiar, EnableBuffering
obsługuje opcje buforowania dużych jednostek żądań na dysku lub ich całkowite odrzucenie.
Oprogramowanie pośredniczące w poniższym przykładzie:
- Włącza wiele odczytów za pomocą polecenia
EnableBuffering
. Przed odczytaniem treści żądania należy go wywołać. - Odczytuje treść żądania.
- Przewija treść żądania do początku, aby inne oprogramowanie pośredniczące lub punkt końcowy mógł go odczytać.
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
Alternatywnym sposobem odczytania treści żądania jest użycie HttpRequest.BodyReader właściwości . Właściwość BodyReader
uwidacznia treść żądania jako PipeReader. Ten interfejs API pochodzi z potoków we/wy, czyli zaawansowanego, wysokiej wydajności sposobu odczytywania treści żądania.
Czytelnik uzyskuje bezpośredni dostęp do treści żądania i zarządza pamięcią w imieniu wywołującego. W przeciwieństwie do HttpRequest.Body
elementu czytnik nie kopiuje danych żądania do buforu. Jednak czytelnik jest bardziej skomplikowany do użycia niż strumień i powinien być używany z ostrożnością.
Aby uzyskać informacje na temat odczytywania zawartości z BodyReader
usługi , zobacz Potoki we/wy PipeReader.
HttpResponse
HttpContext.Response zapewnia dostęp do HttpResponseelementu . HttpResponse
służy do ustawiania informacji na temat odpowiedzi HTTP wysyłanej z powrotem do klienta.
Często używane elementy członkowskie HttpResponse
obejmują:
Właściwości | opis | Przykład |
---|---|---|
HttpResponse.StatusCode | Kod odpowiedzi. Należy ustawić przed zapisaniem w treści odpowiedzi. | 200 |
HttpResponse.ContentType | Nagłówek odpowiedzi content-type . Należy ustawić przed zapisaniem w treści odpowiedzi. |
application/json |
HttpResponse.Headers | Kolekcja nagłówków odpowiedzi. Należy ustawić przed zapisaniem w treści odpowiedzi. | server=Kestrel x-custom-header=MyValue |
HttpResponse.Body | A Stream do pisania treści odpowiedzi. | Wygenerowana strona internetowa |
Ustawianie nagłówków odpowiedzi
HttpResponse.Headers zapewnia dostęp do nagłówków odpowiedzi wysyłanych za pomocą odpowiedzi HTTP. Istnieją dwa sposoby uzyskiwania dostępu do nagłówków przy użyciu tej kolekcji:
- Podaj nazwę nagłówka indeksatorowi w kolekcji nagłówków. Nazwa nagłówka nie uwzględnia wielkości liter. Indeksator może uzyskać dostęp do dowolnej wartości nagłówka.
- Kolekcja nagłówków zawiera również właściwości pobierania i ustawiania często używanych nagłówków HTTP. Właściwości zapewniają szybki, oparty na funkcji IntelliSense sposób uzyskiwania dostępu do nagłówków.
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();
Aplikacja nie może modyfikować nagłówków po uruchomieniu odpowiedzi. Po uruchomieniu odpowiedzi nagłówki są wysyłane do klienta. Odpowiedź jest uruchamiana przez opróżnienie treści odpowiedzi lub wywołanie metody HttpResponse.StartAsync(CancellationToken). Właściwość HttpResponse.HasStarted wskazuje, czy odpowiedź została uruchomiona. Podczas próby zmodyfikowania nagłówków po rozpoczęciu odpowiedzi jest zgłaszany błąd:
System.InvalidOperationException: Nagłówki są tylko do odczytu, odpowiedź została już uruchomiona.
Uwaga
Jeśli buforowanie odpowiedzi nie jest włączone, wszystkie operacje zapisu (na przykład WriteAsync) opróżnić treść odpowiedzi wewnętrznie i oznaczyć odpowiedź jako uruchomioną. Buforowanie odpowiedzi jest domyślnie wyłączone.
Treść odpowiedzi zapisu
Odpowiedź HTTP może zawierać treść odpowiedzi. Treść odpowiedzi to dane skojarzone z odpowiedzią, takie jak wygenerowana zawartość strony internetowej, ładunek JSON UTF-8 lub plik.
HttpResponse.Body umożliwia zapisanie treści odpowiedzi za pomocą polecenia Stream:
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
można zapisywać bezpośrednio lub używać z innymi interfejsami API, które zapisują w strumieniu.
BodyWriter
Alternatywnym sposobem na napisanie treści odpowiedzi jest użycie HttpResponse.BodyWriter właściwości . Właściwość BodyWriter
uwidacznia treść odpowiedzi jako PipeWriter. Ten interfejs API pochodzi z potoków we/wy i jest to zaawansowany, wysokowydajny sposób na napisanie odpowiedzi.
Składnik zapisywania zapewnia bezpośredni dostęp do treści odpowiedzi i zarządza pamięcią w imieniu wywołującego. W przeciwieństwie do HttpResponse.Body
elementu zapis nie kopiuje danych żądania do buforu. Jednak składnik zapisywania jest bardziej skomplikowany do użycia niż strumień, a kod modułu zapisywania powinien być dokładnie przetestowany.
Aby uzyskać informacje na temat pisania zawartości w usłudze BodyWriter
, zobacz Potoki we/wy PipeWriter.
Ustawianie zwiastunów odpowiedzi
Zwiastuny odpowiedzi http/2 i HTTP/3. Przyczepy są nagłówkami wysyłanymi z odpowiedzią po zakończeniu treści odpowiedzi. Ponieważ przyczepy są wysyłane po treści odpowiedzi, przyczepy można dodawać do odpowiedzi w dowolnym momencie.
Poniższy kod ustawia przyczepy przy użyciu polecenia 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
Token HttpContext.RequestAborted anulowania może służyć do powiadamiania o tym, że żądanie HTTP zostało przerwane przez klienta lub serwer. Token anulowania powinien zostać przekazany do długotrwałych zadań, aby można je było anulować, jeśli żądanie zostało przerwane. Na przykład przerwanie zapytania bazy danych lub żądania HTTP w celu pobrania danych do zwrócenia w odpowiedzi.
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();
Token RequestAborted
anulowania nie musi być używany dla operacji odczytu treści żądania, ponieważ operacje odczytu treści żądania są zawsze zgłaszane natychmiast po przerwaniu żądania. Token RequestAborted
jest również zwykle niepotrzebny podczas pisania treści odpowiedzi, ponieważ zapisuje natychmiast bez operacji po przerwaniu żądania.
W niektórych przypadkach przekazanie tokenu RequestAborted
do operacji zapisu może być wygodnym sposobem wymuszenia zamknięcia pętli zapisu na wczesnym etapie za pomocą polecenia OperationCanceledException. Jednak zazwyczaj lepiej jest przekazać RequestAborted
token do dowolnych operacji asynchronicznych odpowiedzialnych za pobieranie zawartości treści odpowiedzi.
Uwaga
Minimalne interfejsy API obsługują wiązanie HttpContext.RequestAborted bezpośrednio z parametrem CancellationToken .
Abort()
Metody HttpContext.Abort() można użyć do przerwania żądania HTTP z serwera. Przerwanie żądania HTTP natychmiast wyzwala HttpContext.RequestAborted token anulowania i wysyła powiadomienie do klienta, że serwer przerwał żądanie.
Oprogramowanie pośredniczące w poniższym przykładzie:
- Dodaje niestandardowe sprawdzanie pod kątem złośliwych żądań.
- Przerywa żądanie HTTP, jeśli żądanie jest złośliwe.
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
Właściwość służy do pobierania HttpContext.User lub ustawiania użytkownika reprezentowanego przez ClaimsPrincipalelement dla żądania . Element ClaimsPrincipal jest zwykle ustawiany przez uwierzytelnianie ASP.NET Core.
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();
Uwaga
Minimalne interfejsy API obsługują wiązanie HttpContext.User bezpośrednio z parametrem ClaimsPrincipal .
Features
Właściwość HttpContext.Features zapewnia dostęp do kolekcji interfejsów funkcji dla bieżącego żądania. Ponieważ kolekcja funkcji jest modyfikowalna nawet w kontekście żądania, oprogramowanie pośredniczące może służyć do modyfikowania kolekcji i dodawania obsługi dodatkowych funkcji. Niektóre zaawansowane funkcje są dostępne tylko przez uzyskanie dostępu do skojarzonego interfejsu za pośrednictwem kolekcji funkcji.
Poniższy przykład:
- Pobiera IHttpMinRequestBodyDataRateFeature z kolekcji funkcji.
- Ustawia MinDataRate wartość null. Spowoduje to usunięcie minimalnej szybkości danych, którą musi wysłać treść żądania przez klienta dla tego żądania HTTP.
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();
Aby uzyskać więcej informacji na temat korzystania z funkcji żądań i HttpContext
, zobacz Request Features in ASP.NET Core (Funkcje żądań w programie ASP.NET Core).
HttpContext nie jest bezpieczny wątkiem
W tym artykule omówiono przede wszystkim użycie HttpContext
przepływu żądań i odpowiedzi ze Razor stron, kontrolerów, oprogramowania pośredniczącego itp. Podczas korzystania z HttpContext
przepływu żądania i odpowiedzi należy wziąć pod uwagę następujące kwestie:
- Element
HttpContext
NIE jest bezpieczny wątkowo, dostęp do niego z wielu wątków może spowodować wyjątki, uszkodzenie danych i ogólnie nieprzewidywalne wyniki. - Interfejs IHttpContextAccessor powinien być używany z ostrożnością. Jak zawsze nie
HttpContext
można przechwycić elementu poza przepływem żądania.IHttpContextAccessor
:- Zależy od AsyncLocal<T> tego, co może mieć negatywny wpływ na wydajność wywołań asynchronicznych.
- Tworzy zależność od "stanu otoczenia", co może utrudnić testowanie.
- IHttpContextAccessor.HttpContext może być
null
używany poza przepływem żądań. - Aby uzyskać dostęp do informacji
HttpContext
spoza przepływu żądania, skopiuj informacje wewnątrz przepływu żądania. Zachowaj ostrożność, aby skopiować rzeczywiste dane, a nie tylko odwołania. Na przykład zamiast kopiować odwołanie doIHeaderDictionary
elementu , skopiuj odpowiednie wartości nagłówka lub skopiuj cały klucz słownika według klucza przed opuszczeniem przepływu żądania. - Nie przechwytuj
IHttpContextAccessor.HttpContext
w konstruktorze.
Następujące przykładowe gałęzie usługi GitHub są dzienniki po żądaniu z punktu końcowego /branch
:
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();
Interfejs API usługi GitHub wymaga dwóch nagłówków. Nagłówek User-Agent
jest dodawany dynamicznie przez element 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();
Pomocnik 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);
}
}
W poprzednim kodzie, gdy HttpContext
parametr to null
, userAgent
ciąg jest ustawiony na "Unknown"
wartość . Jeśli to możliwe, HttpContext
należy jawnie przekazać do usługi. Jawne przekazywanie HttpContext
danych:
- Sprawia, że interfejs API usługi jest bardziej możliwy do użycia poza przepływem żądań.
- Jest lepszy pod kątem wydajności.
- Ułatwia zrozumienie i uzasadnienie kodu niż poleganie na stanie otoczenia.
Gdy usługa musi uzyskać dostęp, HttpContext
powinna uwzględniać możliwość wywołania HttpContext
null
z wątku żądania.
Aplikacja zawiera PeriodicBranchesLoggerService
również element , który rejestruje otwarte gałęzie usługi GitHub określonego repozytorium co 30 sekund:
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
jest usługą hostowaną, która działa poza przepływem żądania i odpowiedzi. Rejestrowanie z obiektu PeriodicBranchesLoggerService
ma wartość null HttpContext
. Tekst PeriodicBranchesLoggerService
został zapisany, aby nie zależeć od .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 =>
{