Nguon: Microsoft Learn · .NET 8.0

Sử dụng HttpContext trong ASP.NET Core

Nguồn: Use HttpContext in ASP.NET Core

HttpContext đóng gói tất cả thông tin về một HTTP request và response riêng lẻ. Một instance HttpContext được khởi tạo khi nhận được HTTP request. Instance HttpContext có thể truy cập bởi middleware và các app framework như Blazor Web Apps, Web API controllers, Razor Pages, SignalR, gRPC, và nhiều hơn nữa.

HttpRequest

HttpContext.Request cung cấp quyền truy cập vào HttpRequest. HttpRequest có thông tin về incoming HTTP request, và nó được khởi tạo khi server nhận được HTTP request. HttpRequest không chỉ đọc, và middleware có thể thay đổi các giá trị request trong middleware pipeline.

Các thành viên thường được sử dụng trên HttpRequest bao gồm:

Thuộc tínhMô tảVí dụ
HttpRequest.PathĐường dẫn request./en/article/getstarted
HttpRequest.MethodPhương thức request.GET
HttpRequest.HeadersTập hợp các request header.user-agent=Edge, x-custom-header=MyValue
HttpRequest.RouteValuesTập hợp các route value. Tập hợp được đặt khi request được khớp với một route.language=en, article=getstarted
HttpRequest.QueryTập hợp các query value được phân tích từ QueryString.filter=hello, page=1
HttpRequest.ReadFormAsync()Phương thức đọc request body dưới dạng form và trả về tập hợp giá trị form.email=user@contoso.com
HttpRequest.BodyStream để đọc request body.UTF-8 JSON payload

Lấy request headers

HttpRequest.Headers cung cấp quyền truy cập vào request headers được gửi kèm với HTTP request. Có hai cách truy cập headers bằng collection này:

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

Đọc request body

Một HTTP request có thể bao gồm request body. Request body là dữ liệu liên quan đến request, chẳng hạn như nội dung của HTML form, UTF-8 JSON payload, hoặc file.

HttpRequest.Body cho phép đọc request body với Stream:

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

Kích hoạt request body buffering (đệm)

Request body chỉ có thể được đọc một lần, từ đầu đến cuối. Đọc chỉ tiến của request body tránh overhead của việc đệm toàn bộ request body và giảm sử dụng bộ nhớ. Tuy nhiên, trong một số trường hợp, cần phải đọc request body nhiều lần. Ví dụ, middleware có thể cần đọc request body và sau đó tua lại để endpoint có thể đọc.

Extension method EnableBuffering cho phép đệm HTTP request body và là cách được khuyến nghị để kích hoạt nhiều lần đọc. Vì request có thể có bất kỳ kích thước nào, EnableBuffering hỗ trợ các tùy chọn để đệm các request body lớn vào đĩa, hoặc từ chối hoàn toàn.

Middleware trong ví dụ sau:

csharp
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

Một cách thay thế để đọc request body là sử dụng thuộc tính HttpRequest.BodyReader. Thuộc tính BodyReader expose request body như một PipeReader. API này từ I/O pipelines, là cách tiên tiến, hiệu suất cao để đọc request body.

Reader truy cập trực tiếp vào request body và quản lý bộ nhớ thay cho người gọi. Không giống HttpRequest.Body, reader không sao chép dữ liệu request vào buffer. Tuy nhiên, reader phức tạp hơn để sử dụng so với stream và nên được sử dụng cẩn thận.

HttpResponse

HttpContext.Response cung cấp quyền truy cập vào HttpResponse. HttpResponse được sử dụng để đặt thông tin trên HTTP response được gửi lại cho client.

Các thành viên thường được sử dụng trên HttpResponse bao gồm:

Thuộc tínhMô tảVí dụ
HttpResponse.StatusCodeMã response. Phải được đặt trước khi ghi vào response body.200
HttpResponse.ContentTypeHeader content-type của response. Phải được đặt trước khi ghi vào response body.application/json
HttpResponse.HeadersTập hợp các response header. Phải được đặt trước khi ghi vào response body.server=Kestrel, x-custom-header=MyValue
HttpResponse.BodyStream để ghi response body.Trang web được tạo

Đặt response headers

HttpResponse.Headers cung cấp quyền truy cập vào response headers được gửi kèm với HTTP response. Có hai cách truy cập headers bằng collection này:

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

Ứng dụng không thể sửa đổi headers sau khi response đã bắt đầu. Sau khi response bắt đầu, headers được gửi cho client. Response được bắt đầu bằng cách flush response body hoặc gọi HttpResponse.StartAsync(CancellationToken). Thuộc tính HttpResponse.HasStarted cho biết response đã bắt đầu hay chưa. Lỗi được ném khi cố gắng sửa đổi headers sau khi response đã bắt đầu:

System.InvalidOperationException: Headers are read-only, response has already started.

Ghi response body

Một HTTP response có thể bao gồm response body. Response body là dữ liệu liên quan đến response, chẳng hạn như nội dung trang web được tạo, UTF-8 JSON payload, hoặc file.

HttpResponse.Body cho phép ghi response body với Stream:

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

BodyWriter

Một cách thay thế để ghi response body là sử dụng thuộc tính HttpResponse.BodyWriter. Thuộc tính BodyWriter expose response body như một PipeWriter. API này từ I/O pipelines, và đây là cách tiên tiến, hiệu suất cao để ghi response.

Đặt response trailers

HTTP/2 và HTTP/3 hỗ trợ response trailers. Trailers là headers được gửi kèm với response sau khi response body hoàn tất. Vì trailers được gửi sau response body, trailers có thể được thêm vào response bất cứ lúc nào.

Code sau đây đặt trailers bằng cách sử dụng AppendTrailer:

csharp
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

Cancellation token HttpContext.RequestAborted có thể được sử dụng để thông báo rằng HTTP request đã bị hủy bởi client hoặc server. Cancellation token nên được truyền cho các task chạy lâu để chúng có thể bị hủy nếu request bị hủy. Ví dụ, hủy một database query hoặc HTTP request để lấy dữ liệu trả về trong response.

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

Cancellation token RequestAborted không cần được sử dụng cho các thao tác đọc request body vì các lần đọc luôn ném ngay lập tức khi request bị hủy. Token RequestAborted cũng thường không cần thiết khi ghi response body, vì các lần ghi ngay lập tức no-op khi request bị hủy.

Abort()

Phương thức HttpContext.Abort() có thể được sử dụng để hủy một HTTP request từ phía server. Hủy HTTP request ngay lập tức kích hoạt cancellation token HttpContext.RequestAborted và gửi thông báo cho client rằng server đã hủy request.

Middleware trong ví dụ sau:

csharp
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

Thuộc tính HttpContext.User được sử dụng để lấy hoặc đặt user, được biểu diễn bởi ClaimsPrincipal, cho request. ClaimsPrincipal thường được đặt bởi ASP.NET Core authentication (xác thực).

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

Features

Thuộc tính HttpContext.Features cung cấp quyền truy cập vào tập hợp các feature interface cho request hiện tại. Vì feature collection có thể thay đổi ngay cả trong context của một request, middleware có thể được sử dụng để sửa đổi collection và thêm hỗ trợ cho các tính năng bổ sung. Một số tính năng nâng cao chỉ có thể truy cập bằng cách sử dụng interface liên quan thông qua feature collection.

Ví dụ sau:

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

HttpContext không thread safe

Bài viết này chủ yếu thảo luận về việc sử dụng HttpContext trong request và response flow từ các component Blazor Web App, Razor Pages, controllers, middleware, v.v. Hãy xem xét điều sau khi sử dụng HttpContext bên ngoài request và response flow:

Ví dụ sau ghi log các GitHub branch khi được yêu cầu từ endpoint /branch:

csharp
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/");
    httpClient.DefaultRequestHeaders.Add(
        HeaderNames.Accept, "application/vnd.github.v3+json");
}).AddHttpMessageHandler<UserAgentHeaderHandler>();

builder.Services.AddTransient<UserAgentHeaderHandler>();

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/branches", async (IHttpClientFactory httpClientFactory,
                         HttpContext context, Logger<Program> logger) =>
{
    var httpClient = httpClientFactory.CreateClient("GitHub");
    var httpResponseMessage = await httpClient.GetAsync(
        "repos/dotnet/AspNetCore.Docs/branches");

    if (!httpResponseMessage.IsSuccessStatusCode) 
        return Results.BadRequest();

    await using var contentStream =
        await httpResponseMessage.Content.ReadAsStreamAsync();

    var response = await JsonSerializer.DeserializeAsync
        <IEnumerable<GitHubBranch>>(contentStream);

    app.Logger.LogInformation($"/branches request: " +
                              $"{JsonSerializer.Serialize(response)}");

    return Results.Ok(response);
});

app.Run();

UserAgentHeaderHandler thêm header User-Agent một cách động:

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

Trong code trên, khi HttpContextnull, chuỗi userAgent được đặt thành "Unknown". Nếu có thể, HttpContext nên được truyền tường minh vào service. Truyền tường minh dữ liệu HttpContext:

Ứng dụng cũng bao gồm PeriodicBranchesLoggerService, là hosted service (dịch vụ được host) chạy bên ngoài request và response flow. Logging từ PeriodicBranchesLoggerServiceHttpContext là null. PeriodicBranchesLoggerService được viết để không phụ thuộc vào HttpContext.