Dela via


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.Bodykopierar 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 BodyReaderfinns 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.Bodykopierar 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 BodyWriterfinns 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 HttpContextfinns 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 HttpContextinte 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 en IHeaderDictionary, 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 HttpContextbö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 HttpContextfinns i Access HttpContext i ASP.NET Core.