Använda HttpContext i ASP.NET Core
Notera
Det här är inte den senaste versionen av den här artikeln. Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.
Varning
Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. För den nuvarande versionen, se .NET 9-versionen av denna artikel .
Viktig
Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.
Den aktuella versionen finns i -versionen av .NET 9 av den här artikeln.
HttpContext kapslar in all information om en enskild HTTP-begäran och ett enskilt svar. En HttpContext
instans initieras när en HTTP-begäran tas emot. Den HttpContext
instansen är tillgänglig via mellanprogram och appramverk som Blazor Web Apps, webb-API-styrenheter, Razor Pages, SignalR, gRPC med mera.
HttpRequest
HttpContext.Request ger åtkomst till HttpRequest.
HttpRequest
har information om den inkommande HTTP-begäran och initieras när en HTTP-begäran tas emot av servern.
HttpRequest
är inte skrivskyddad och mellanprogram kan ändra begärandevärden i pipelinen för mellanprogram.
Vanliga medlemmar på HttpRequest
är:
Egenskap | Beskrivning | Exempel |
---|---|---|
HttpRequest.Path | Sökvägen för begäran. | /en/article/getstarted |
HttpRequest.Method | Begärandemetoden. | GET |
HttpRequest.Headers | En samling förfrågningshuvuden. | user-agent=Edge x-custom-header=MyValue |
HttpRequest.RouteValues | En samling ruttvärden. Samlingen anges när begäran matchas till en rutt. | language=en article=getstarted |
HttpRequest.Query | En samling frågevärden som parsas från QueryString. | filter=hello page=1 |
HttpRequest.ReadFormAsync() | En metod som läser begärandetexten som ett formulär och returnerar en samling med formulärvärden. Information om varför ReadFormAsync ska användas för att komma åt formulärdata finns i Föredrar ReadFormAsync framför Request.Form. |
email=user@contoso.com |
HttpRequest.Body | En Stream för att läsa begärandetexten. | UTF-8 JSON-payload |
Hämta begärandehuvuden
HttpRequest.Headers ger åtkomst till de begärandehuvuden som skickas med HTTP-begäran. Det finns två sätt att komma åt rubriker med den här samlingen:
- Ange rubriknamnet för indexeraren i rubriksamlingen. Rubriknamnet är inte skiftlägeskänsligt. Indexeraren kan komma åt valfritt rubrikvärde.
- Huvudsamlingen har också egenskaper för att hämta och ställa in vanliga HTTP-huvuden. Egenskaperna ger ett snabbt, IntelliSense-drivet sätt att komma åt rubriker.
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();
Information om effektiv hantering av rubriker som visas mer än en gång finns i En kort titt på StringValues.
Läs begäranens innehåll
En HTTP-begäran kan innehålla en begärandetext. Begärandetexten är data som är associerade med begäran, till exempel innehållet i ett HTML-formulär, UTF-8 JSON-nyttolast eller en fil.
HttpRequest.Body gör att begärandetexten kan läsas med 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 läsas direkt eller användas med andra API:er som accepterar ström.
Anteckning
minimala API:er stöder bindning HttpRequest.Body direkt till en Stream parameter.
Aktivera buffert för begärandetext
Begärandetexten kan bara läsas en gång, från början till slut. Genom att endast läsa begärandetexten i en framåtgående riktning undviks buffring av hela begärandetexten och minnesanvändningen minskar. I vissa fall finns det dock ett behov av att läsa begärandetexten flera gånger. Till exempel kan mellanprogram behöva läsa begärandetexten och sedan spola tillbaka den så att den är tillgänglig för slutpunkten.
Metoden EnableBuffering tillägg möjliggör buffring av HTTP-begärandetexten och är det rekommenderade sättet att aktivera flera läsningar. Eftersom en begäran kan vara i vilken storlek som helst stöder EnableBuffering
alternativ för att buffra stora begärandekroppar till disken eller avvisa dem helt.
Mellanprogrammet i följande exempel:
- Möjliggör flera läsningar med
EnableBuffering
. Det måste anropas innan du läser begärandetexten. - Läser begärandeinnehållet.
- Spolar tillbaka begärandetexten till början så att andra mellanprogram eller slutpunkten kan läsa den.
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
Ett annat sätt att läsa begärandetexten är att använda egenskapen HttpRequest.BodyReader. Egenskapen BodyReader
exponerar begärandetexten som en PipeReader. Det här API:et kommer från I/O-pipelines, ett avancerat och högpresterande sätt att läsa begärandetexten.
Läsaren kommer direkt åt begärandetexten och hanterar minne åt anroparen. Till skillnad från HttpRequest.Body
kopierar läsaren inte begärandedata till en buffert. En läsare är dock mer komplicerad att använda än en ström och bör hanteras med försiktighet.
Information om hur du läser innehåll från BodyReader
finns i I/O-pipelines PipeReader.
HttpResponse
HttpContext.Response ger åtkomst till HttpResponse.
HttpResponse
används för att ange information om HTTP-svaret som skickas tillbaka till klienten.
Vanliga medlemmar på HttpResponse
är:
Egenskap | Beskrivning | Exempel |
---|---|---|
HttpResponse.StatusCode | Svarskoden. Måste anges innan du skriver till svarstexten. | 200 |
HttpResponse.ContentType | Svaret content-type sidhuvud. Måste anges innan du skriver till svarstexten. |
application/json |
HttpResponse.Headers | En samling svarshuvuden. Måste anges innan du skriver till svarstexten. | server=Kestrel x-custom-header=MyValue |
HttpResponse.Body | En Stream för att skriva svarstexten. | Skapad webbsida |
Ange svarshuvuden
HttpResponse.Headers ger åtkomst till svarshuvuden som skickas med HTTP-svaret. Det finns två sätt att komma åt rubriker med den här samlingen:
- Ange rubriknamnet till indexeraren i rubriksamlingen. Rubriknamnet är inte skiftlägeskänsligt. Indexeraren kan komma åt valfritt rubrikvärde.
- Använd egenskaperna för rubriksamlingen för att hämta och ange vanliga HTTP-huvuden. Egenskaperna ger ett snabbt, IntelliSense-drivet sätt att komma åt rubriker.
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();
En app kan inte ändra rubriker när svaret har startats. När svaret startar skickas rubrikerna till klienten. Ett svar initieras genom att spola ut responsens innehåll eller anropa HttpResponse.StartAsync(CancellationToken). Egenskapen HttpResponse.HasStarted indikerar huruvida svaret har påbörjats. Ett fel utlöses när du försöker ändra rubriker när svaret har startats:
System.InvalidOperationException: Headers är skrivskyddade, svaret har redan startats.
Not
Såvida inte buffring av svar är aktiverad, skriver alla skrivåtgärder (till exempel WriteAsync) ut svarstexten internt och markerar svaret som startat. Svarsbuffertning är inaktiverad som standard.
Skriv svarstext
Ett HTTP-svar kan innehålla en svarstext. Svarstexten är data som är associerade med svaret, till exempel genererat webbsideinnehåll, UTF-8 JSON-nyttolast eller en fil.
HttpResponse.Body gör att svarstexten kan skrivas med 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
kan skrivas direkt eller användas med andra API:er som skriver till en dataström.
BodyWriter
Ett annat sätt att skriva svarstexten är att använda egenskapen HttpResponse.BodyWriter. Egenskapen BodyWriter
exponerar svarstexten som en PipeWriter. Det här API:et kommer från I/O-pipelinesoch det är ett avancerat och högpresterande sätt att skriva svaret.
Skrivaren ger direkt åtkomst till svarstexten och hanterar minne åt anroparen. Till skillnad från HttpResponse.Body
kopierar inte skrivningen begärandedata till en buffert. En skrivare är dock mer komplicerad att använda än en ström och skrivarkod bör testas noggrant.
Information om hur du skriver innehåll till BodyWriter
finns i I/O-pipelines PipeWriter.
Ange svarstrailer
HTTP/2 och HTTP/3 stöder svarstrailrar. Trailers är rubriker som skickas med svaret när svarstexten har slutförts. Eftersom släpvagnar skickas efter svarstexten kan släpvagnar läggas till i svaret när som helst.
Följande kod anger trailers med hjälp av 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
Den HttpContext.RequestAborted annulleringstoken kan användas för att meddela att HTTP-begäran har avbrutits av klienten eller servern. Annulleringstoken ska skickas till långvariga uppgifter så att de kan avbrytas om begäran avbryts. Till exempel avbryta en databasfråga eller HTTP-begäran för att hämta data som ska returneras i svaret.
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();
RequestAborted
-annulleringstoken behöver inte användas för att läsa begärandetexten eftersom läsningar alltid avbryts omedelbart när begäran stoppas. Den RequestAborted
token är också vanligtvis onödig när du skriver svarskroppar, eftersom skrivningar omedelbart no-op när begäran avbryts.
Att i vissa fall passera RequestAborted
-token till skrivoperationer kan vara ett praktiskt sätt att tvinga en skrivloop att avslutas tidigt med en OperationCanceledException. Det är dock vanligtvis bättre att skicka RequestAborted
-token till alla asynkrona åtgärder som ansvarar för att hämta innehållet i svarstexten i stället.
Not
minimala API:er stödjer bindning HttpContext.RequestAborted direkt till en CancellationToken parameter.
Abort()
Metoden HttpContext.Abort() kan användas för att avbryta en HTTP-begäran från servern. Om DU avbryter HTTP-begäran utlöses omedelbart HttpContext.RequestAborted annulleringstoken och skickar ett meddelande till klienten om att servern har avbrutit begäran.
Mellanprogrammet i följande exempel:
- Lägger till en anpassad kontroll för skadliga begäranden.
- Avbryter HTTP-begäran om begäran är skadlig.
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
Egenskapen HttpContext.User används för att hämta eller ange användaren, som representeras av ClaimsPrincipal, för begäran. ClaimsPrincipal anges vanligtvis av ASP.NET Core-autentisering.
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();
Anteckning
minimala API:er stöder bindning HttpContext.User direkt till en ClaimsPrincipal parameter.
Features
Egenskapen HttpContext.Features ger åtkomst till samlingen med funktionsgränssnitt för den aktuella begäran. Eftersom funktionssamlingen kan ändras även inom ramen för en begäran kan mellanprogram användas för att ändra samlingen och lägga till stöd för ytterligare funktioner. Vissa avancerade funktioner är bara tillgängliga genom att komma åt det associerade gränssnittet via funktionssamlingen.
Följande exempel:
- Hämtar IHttpMinRequestBodyDataRateFeature från funktionssamlingen.
- Ställer in MinDataRate till nullvärde. Detta tar bort den lägsta överföringshastighet vid vilken klienten måste skicka begärandetexten för den här HTTP-begäran.
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();
Mer information om hur du använder begärandefunktioner och HttpContext
finns i Request Features in ASP.NET Core.
HttpContext är inte trådsäkert
I den här artikeln beskrivs främst hur du använder HttpContext
i begärande- och svarsflödet från Blazor Web App komponenter, Razor sidor, kontrollanter, mellanprogram och så vidare. Tänk på följande när du använder HttpContext
utanför flödet för begäran och svar:
-
HttpContext
är INTE trådsäker. Åtkomst till den från flera trådar kan resultera i oförutsägbara resultat, till exempel undantag och skadade data. - Det IHttpContextAccessor gränssnittet bör användas med försiktighet. Som alltid måste
HttpContext
inte samlas in utanför begärandeflödet.IHttpContextAccessor
:- Förlitar sig på AsyncLocal<T>, som kan ha en negativ prestandapåverkan på asynkrona anrop.
- Skapar ett beroende av "omgivande tillstånd" som kan göra testningen svårare.
-
IHttpContextAccessor.HttpContext kan vara
null
om de nås utanför begärandeflödet. - Om du vill komma åt information från
HttpContext
utanför begärandeflödet kopierar du informationen i begärandeflödet. Var noga med att kopiera faktiska data och inte bara referenser. I stället för att till exempel kopiera en referens till enIHeaderDictionary
, kopiera de relevanta rubrikvärdena eller kopiera hela ordlistan nyckel för nyckel innan begärandeflödet avslutas. - Använd inte
IHttpContextAccessor.HttpContext
i en konstruktor.
Följande exempel loggar GitHub-grenar när de begärs från /branch
slutpunkten:
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:et kräver två huvuden. Rubriken User-Agent
läggs till dynamiskt av 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();
Den 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);
}
}
I föregående kod, när HttpContext
är null
, anges strängen userAgent
till "Unknown"
. Om möjligt bör HttpContext
uttryckligen skickas till tjänsten. Uttryckligen skicka in data för HttpContext
.
- Gör tjänst-API:et mer användbart utanför begärandeflödet.
- Är bättre för prestanda.
- Gör koden enklare att förstå och resonera om än att förlita sig på omgivande tillstånd.
När tjänsten måste komma åt HttpContext
bör den ta hänsyn till möjligheten att HttpContext
är null
när den inte anropas från en begärandetråd.
Programmet innehåller även PeriodicBranchesLoggerService
, som loggar de öppna GitHub-grenarna på den angivna lagringsplatsen var 30:e 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
är en värdbaserad tjänst, som körs utanför flödet för begäran och svar. Loggning från PeriodicBranchesLoggerService
har ett nullvärde HttpContext
.
PeriodicBranchesLoggerService
skrevs att inte bero på 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 =>
{
Ytterligare resurser
Mer information om hur du kommer åt HttpContext
finns i Access HttpContext i ASP.NET Core.
ASP.NET Core