Verwenden von HttpContext in ASP.NET Core
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der Supportrichtlinie für .NET und .NET Core. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
HttpContext kapselt sämtliche Informationen zu einer einzelnen HTTP-Anforderung und -Antwort. Eine HttpContext
-Instanz wird initialisiert, wenn eine HTTP-Anforderung empfangen wird. Auf die HttpContext
-Instanz kann von Middleware und App-Frameworks wie Web-API-Controllern, Razor Pages, SignalR, gRPC und mehr zugegriffen werden.
Weitere Informationen zum Zugriff auf HttpContext
finden Sie unter Zugreifen auf HttpContext in ASP.NET Core.
HttpRequest
HttpContext.Request ermöglicht den Zugriff auf HttpRequest. HttpRequest
umfasst Informationen zur eingehenden HTTP-Anforderung und wird initialisiert, wenn eine HTTP-Anforderung vom Server empfangen wird. HttpRequest
ist nicht schreibgeschützt, und die Middleware kann Anforderungswerte in der Middlewarepipeline ändern.
Gängige Member von HttpRequest
sind unter anderem:
Eigenschaft | BESCHREIBUNG | Beispiel |
---|---|---|
HttpRequest.Path | Der Anforderungspfad. | /en/article/getstarted |
HttpRequest.Method | Der Anforderungsmethode. | GET |
HttpRequest.Headers | Eine Auflistung von Anforderungsheadern. | user-agent=Edge x-custom-header=MyValue |
HttpRequest.RouteValues | Eine Sammlung von Routenwerten. Die Sammlung wird eingerichtet, wenn die Anforderung mit einer Route abgeglichen wird. | language=en article=getstarted |
HttpRequest.Query | Eine Sammlung von Abfragewerten, die aus QueryString geparst wurden. | filter=hello page=1 |
HttpRequest.ReadFormAsync() | Eine Methode, die den Anforderungstext als Formular liest und eine Sammlung von Formularwerten zurückgibt. Informationen dazu, warum ReadFormAsync für den Zugriff auf Formulardaten verwendet werden sollte, finden Sie unter Bevorzugen von ReadFormAsync gegenüber Request.Form. |
email=user@contoso.com |
HttpRequest.Body | Ein Stream zum Lesen des Anforderungstexts. | UTF-8 JSON-Nutzlast |
Abrufen von Anforderungsheadern
HttpRequest.Headers bietet Zugriff auf die mit der HTTP-Anforderung gesendeten Anforderungsheader. Es gibt zwei Möglichkeiten, mithilfe dieser Sammlung auf Header zuzugreifen:
- Übergeben Sie den Headernamen an den Indexer für die Headersammlung. Beim Headernamen wird nicht zwischen Groß- und Kleinschreibung unterschieden. Der Indexer kann auf einen beliebigen Headerwert zugreifen.
- Die Headersammlung enthält außerdem Eigenschaften zum Abrufen und Festlegen häufig verwendeter HTTP-Header. Die Eigenschaften ermöglichen einen schnellen, IntelliSense-basierten Zugriff auf die Header.
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();
Informationen zur effizienten Behandlung von Kopfzeilen, die mehrmals angezeigt werden, finden Sie unter Ein kurzer Einblick in StringValues.
Lesen des Anforderungstexts
Eine HTTP-Anforderung kann einen Anforderungstext enthalten. Der Anfragetext besteht aus Daten, die mit der Anfrage verknüpft sind, wie z. B. der Inhalt eines HTML-Formulars, UTF-8-JSON-Nutzlast oder eine Datei.
HttpRequest.Body erlaubt das Lesen des Anforderungstexts mit 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
kann direkt gelesen oder mit anderen APIs verwendet werden, die „Stream“ akzeptieren.
Hinweis
Minimal-APIs unterstützen die direkte Bindung von HttpRequest.Body an einen Stream-Parameter.
Aktivieren der Anforderungstextpufferung
Der Anforderungstext kann nur einmal gelesen werden, von Anfang bis Ende. Durch das Vorwärtslesen des Anforderungstexts entfällt der Overhead, der durch das Zwischenspeichern des gesamten Anforderungstexts entsteht, sodass weniger Arbeitsspeicher benötigt wird. In einigen Szenarien ist es jedoch erforderlich, den Anforderungstext mehrmals zu lesen. Beispielsweise muss die Middleware den Anforderungstext lesen und dann auf den Anfang zurücksetzen, damit er für den Endpunkt verfügbar ist.
Die Erweiterungsmethode EnableBuffering ermöglicht die Pufferung des HTTP-Anforderungstexts und ist der empfohlene Weg, um mehrere Lesevorgänge zu ermöglichen. Da eine Anforderung eine beliebige Größe haben kann, unterstützt EnableBuffering
Optionen für die Pufferung großer Anforderungskörper auf dem Datenträger oder die Ablehnung von Anforderungen.
Die Middleware übernimmt im folgenden Beispiel diese Aufgaben:
- Sie aktiviert mehrere Lesevorgänge mit
EnableBuffering
. Der Aufruf muss vor dem Lesen des Anforderungstexts erfolgen. - Sie liest den Anforderungstext.
- Sie setzt den Anforderungstext auf den Anfang zurück, damit andere Middleware oder der Endpunkt ihn lesen können.
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
Eine alternative Möglichkeit zum Lesen des Anforderungstexts ist die Verwendung der Eigenschaft HttpRequest.BodyReader. Die Eigenschaft BodyReader
macht den Anforderungstext als PipeReader verfügbar. Diese API stammt aus System.IO.Pipelines, einer fortschrittlichen, leistungsstarken Methode zum Lesen des Anforderungstexts.
Der Reader greift direkt auf den Anforderungstext zu und verwaltet den Arbeitsspeicher im Namen des Aufrufers. Im Gegensatz zu HttpRequest.Body
kopiert der Reader die Anforderungsdaten nicht in einen Puffer. Ein Reader ist jedoch in der Anwendung komplexer als ein Stream und sollte mit Bedacht eingesetzt werden.
Informationen zum Lesen von Inhalten aus BodyReader
finden Sie unter E/A-Pipelines: PipeReader.
HttpResponse
HttpContext.Response ermöglicht den Zugriff auf HttpResponse. HttpResponse
wird verwendet, um Informationen für die an den Client zurückgesendete HTTP-Antwort festzulegen.
Gängige Member von HttpResponse
sind unter anderem:
Eigenschaft | BESCHREIBUNG | Beispiel |
---|---|---|
HttpResponse.StatusCode | Der Antwortcode. Muss vor dem Schreibvorgang in den Antworttext festgelegt werden. | 200 |
HttpResponse.ContentType | Der content-type -Header der Antwort. Muss vor dem Schreibvorgang in den Antworttext festgelegt werden. |
application/json |
HttpResponse.Headers | Die Sammlung von Antwortheadern. Muss vor dem Schreibvorgang in den Antworttext festgelegt werden. | server=Kestrel x-custom-header=MyValue |
HttpResponse.Body | Ein Stream zum Schreiben des Antworttexts. | Generierte Webseite |
Festlegen von Antwortheadern
HttpResponse.Headers bietet Zugriff auf die mit der HTTP-Antwort gesendeten Antwortheader. Es gibt zwei Möglichkeiten, mithilfe dieser Sammlung auf Header zuzugreifen:
- Übergeben Sie den Headernamen an den Indexer für die Headersammlung. Beim Headernamen wird nicht zwischen Groß- und Kleinschreibung unterschieden. Der Indexer kann auf einen beliebigen Headerwert zugreifen.
- Die Headersammlung enthält außerdem Eigenschaften zum Abrufen und Festlegen häufig verwendeter HTTP-Header. Die Eigenschaften ermöglichen einen schnellen, IntelliSense-basierten Zugriff auf die Header.
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();
Eine Anwendung kann die Header nach dem Start des Antwortvorgangs nicht mehr ändern. Sobald die Antwort gestartet wird, werden die Header an den Client gesendet. Ein Antwortvorgang wird durch das Leeren des Antworttexts oder durch den Aufruf von HttpResponse.StartAsync(CancellationToken) gestartet. Die Eigenschaft HttpResponse.HasStarted zeigt an, ob der Antwortvorgang gestartet wurde. Der Versuch, nach dem Start des Antwortvorgangs einen Header zu ändern, führt zu einem Fehler:
System.InvalidOperationException: Header sind schreibgeschützt, der Antwortvorgang wurde bereits gestartet.
Hinweis
Wenn die Antwortpufferung nicht aktiviert ist, leeren alle Schreibvorgänge (z. B. WriteAsync) den Antworttext intern, und markieren Sie die Antwort als gestartet. Die Antwortpufferung ist standardmäßig deaktiviert.
Schreiben des Antworttexts
Eine HTTP-Antwort kann einen Antworttext enthalten. Der Antworttext besteht aus Daten, die mit der Antwort verknüpft sind, wie z. B. generierte Webseiteninhalte, UTF-8-JSON-Nutzlast oder eine Datei.
HttpResponse.Body erlaubt das Schreiben des Antworttexts mit 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
kann direkt geschrieben oder mit anderen APIs verwendet werden, die in einen Stream schreiben.
BodyWriter
Eine alternative Möglichkeit zum Schreiben des Antworttexts ist die Verwendung der Eigenschaft HttpResponse.BodyWriter. Die Eigenschaft BodyWriter
macht den Antworttext als PipeWriter verfügbar. Diese API stammt aus System.IO.Pipelines, einer fortschrittlichen, leistungsstarken Methode zum Schreiben des Anforderungstexts.
Der Writer bietet direkten Zugriff auf den Antworttext und verwaltet den Arbeitsspeicher im Namen des Aufrufers. Im Gegensatz zu HttpResponse.Body
kopiert der Writer die Anforderungsdaten nicht in einen Puffer. Ein Writer ist jedoch in der Anwendung komplexer als ein Stream, und der Writercode sollte umfassend getestet werden.
Informationen zum Schreiben von Inhalten in BodyWriter
finden Sie unter E/A-Pipelines: PipeWriter.
Festlegen eines Antwortnachspanns
HTTP/2 und HTTP/3 bieten Unterstützung für einen Antwortnachspann. Ein Nachspann ist ein Header, der mit der Antwort gesendet wird, nachdem der Antworttext vollständig übermittelt wurde. Da ein Nachspann nach dem Antworttext gesendet wird, kann er jederzeit zur Antwort hinzugefügt werden.
Der folgende Code legt einen Nachspann mithilfe von AppendTrailer fest:
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
Mit dem Abbruchtoken HttpContext.RequestAborted können Sie mitteilen, dass die HTTP-Anforderung vom Client oder Server abgebrochen wurde. Das Abbruchtoken sollte an Aufgaben mit langer Ausführungsdauer übergeben werden, damit diese bei einer Stornierung der Anforderung abgebrochen werden können. Ein Beispiel ist der Abbruch einer Datenbankabfrage oder einer HTTP-Anforderung, um Daten für die Rückgabe in der Antwort zu erhalten.
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();
Das Abbruchtoken RequestAborted
muss nicht für Lesevorgänge des Anforderungstexts verwendet werden, da Lesevorgänge stets sofort abgebrochen werden, wenn die Anforderung storniert wird. Das Token RequestAborted
wird in der Regel auch beim Schreiben eines Antworttexts nicht benötigt, da Schreibvorgänge sofort abgebrochen werden, wenn die Anforderung storniert wird.
In einigen Fällen kann die Übergabe des Tokens RequestAborted
an Schreibvorgänge ein bequemer Weg sein, um eine Schreibschleife mit einem OperationCanceledException vorzeitig zu beenden. In der Regel ist es jedoch besser, das Token RequestAborted
an alle asynchronen Vorgänge zu übergeben, die für den Abruf des Antworttexts verantwortlich sind.
Hinweis
Minimal-APIs unterstützen die direkte Bindung von HttpContext.RequestAborted an einen CancellationToken-Parameter.
Abort()
Die Methode HttpContext.Abort() kann verwendet werden, um eine HTTP-Anforderung des Servers abzubrechen. Die Stornierung der HTTP-Anforderung löst sofort das Abbruchtoken HttpContext.RequestAborted aus, und der Client erhält eine Benachrichtigung, dass der Server die Anforderung abgebrochen hat.
Die Middleware übernimmt im folgenden Beispiel diese Aufgaben:
- Sie fügt eine benutzerdefinierte Prüfung auf schädliche Anforderungen hinzu.
- Sie bricht die HTTP-Anforderung ab, wenn es sich um eine schädliche Anforderung handelt.
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
Mithilfe der Eigenschaft HttpContext.User wird der durch ClaimsPrincipal repräsentierte Benutzer für die Anforderung abgerufen oder festgelegt. Der ClaimsPrincipal wird normalerweise über die ASP.NET Core-Authentifizierung festgelegt.
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();
Hinweis
Minimal-APIs unterstützen die direkte Bindung von HttpContext.User an einen ClaimsPrincipal-Parameter.
Features
Die Eigenschaft HttpContext.Features bietet Zugriff auf die Sammlung der Featureschnittstellen für die aktuelle Anforderung. Da die Featuresammlung auch im Kontext einer Anforderung änderbar ist, kann Middleware verwendet werden, um die Sammlung zu modifizieren und Unterstützung für zusätzliche Features hinzuzufügen. Einige erweiterte Features sind nur verfügbar, wenn Sie über die Featuresammlung auf die zugehörige Schnittstelle zugreifen.
Im Beispiel unten geschieht Folgendes:
- Ruft IHttpMinRequestBodyDataRateFeature aus der Featuresammlung ab.
- Legt MinDataRate auf Null fest. Dadurch wird die Mindestdatenrate für den Anforderungstext aufgehoben, die der Client für diese HTTP-Anforderung senden muss.
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();
Weitere Informationen zur Verwendung von Anforderungsfeatures und HttpContext
finden Sie unter Anforderungsfeatures in ASP.NET Core.
HttpContext ist nicht threadsicher
In diesem Artikel wird hauptsächlich die Verwendung von HttpContext
im Rahmen des Anforderungs- und Antwortflusses von Razor-Seiten, -Controllern, -Middleware usw. erläutert. Berücksichtigen Sie Folgendes, wenn Sie HttpContext
außerhalb des Anforderungs- und Antwortflusses verwenden:
HttpContext
ist NICHT threadsicher: der Zugriff darauf von mehreren Threads kann zu Ausnahmen, Datenbeschädigungen und im Allgemeinen unvorhersehbaren Ergebnissen führen.- Die IHttpContextAccessor-Schnittstelle sollte mit Vorsicht angewendet werden. Wie immer darf
HttpContext
nicht außerhalb des Anforderungsflusses erfasst werden.IHttpContextAccessor
:- Basiert auf AsyncLocal<T>, was negative Leistungseinbußen auf asynchrone Aufrufe als Auswirkung haben könnte.
- Erstellt eine Abhängigkeit vom „Umgebungszustand“, der das Testen erschweren kann.
- IHttpContextAccessor.HttpContext kann
null
sein, wenn darauf außerhalb des Anforderungsflusses zugegriffen wird. - Um auf Informationen auf
HttpContext
von außerhalb des Anforderungsflusses zuzugreifen, kopieren Sie die Informationen innerhalb des Anforderungsflusses. Achten Sie darauf, die tatsächlichen Daten und nicht nur Verweise zu kopieren. Anstatt beispielsweise einen Verweis auf einIHeaderDictionary
zu kopieren, kopieren Sie die entsprechenden Kopfzeilenwerte oder kopieren Sie das gesamte Wörterbuch Schlüssel für Schlüssel, bevor Sie den Anforderungsfluss verlassen. - Erfassen Sie
IHttpContextAccessor.HttpContext
nicht in einem Konstruktor.
Im folgenden Beispiel werden GitHub-Verzweigungen protokolliert, wenn sie vom /branch
-Endpunkt angefordert werden:
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();
Die GitHub-API erfordert zwei Kopfzeilen. Die User-Agent
-Kopfzeile wird dynamisch durch UserAgentHeaderHandler
hinzugefügt:
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();
Die 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);
}
}
Wenn HttpContext
im vorherigen Code null
ist, wird die Zeichenfolge userAgent
auf "Unknown"
festgelegt. Wenn möglich, sollte HttpContext
explizit an den Dienst übergeben werden. Explizite Übergabe von Daten in HttpContext
:
- Das macht die Dienst-API außerhalb des Anforderungsflusses benutzerfreundlicher.
- Das ist besser für die Leistung.
- Das macht den Code leichter verständlich und nachvollziehbar, als wenn man sich auf den Umgebungszustand verlässt.
Wenn der Dienst auf HttpContext
zugreifen muss, sollte er die Möglichkeit berücksichtigen, dass HttpContext
null
sein kann, wenn die Anforderung nicht von einem Anforderungsthread aufgerufen wird.
Die Anwendung umfasst auch PeriodicBranchesLoggerService
, das alle 30 Sekunden die offenen GitHub-Zweige des angegebenen Repositorys protokolliert:
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
ist ein gehosteter Dienst, der außerhalb des Anforderungs- und Antwortflusses ausgeführt wird. Die Protokollierung vom PeriodicBranchesLoggerService
hat ein Null-HttpContext
. PeriodicBranchesLoggerService
wurde so geschrieben, um nicht von HttpContext
abzuhängen.
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 =>
{
ASP.NET Core