ASP.NET Core에서 HttpContext 사용
참고 항목
이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.
Important
이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.
현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.
HttpContext은 개별 HTTP 요청 및 응답에 대한 모든 정보를 캡슐화합니다. HttpContext
인스턴스는 HTTP 요청을 수신할 때 초기화됩니다. HttpContext
인스턴스는 Web API 컨트롤러, Razor Pages, SignalR, gRPC 등과 같은 미들웨어 및 앱 프레임워크에서 액세스할 수 있습니다.
HttpContext
에 액세스하는 방법에 대한 자세한 내용은 ASP.NET Core에서 HttpContext 액세스를 참조하세요.
HttpRequest
HttpContext.Request은 HttpRequest에 대한 액세스를 제공합니다. HttpRequest
에는 들어오는 HTTP 요청에 대한 정보가 있으며, 서버에서 HTTP 요청을 수신할 때 초기화됩니다. HttpRequest
는 읽기 전용이 아니며, 미들웨어는 미들웨어 파이프라인에서 요청 값을 변경할 수 있습니다.
HttpRequest
에서 일반적으로 사용되는 멤버는 다음과 같습니다.
속성 | Description | 예시 |
---|---|---|
HttpRequest.Path | 요청 경로입니다. | /en/article/getstarted |
HttpRequest.Method | 요청 방법입니다. | GET |
HttpRequest.Headers | 요청 헤더의 컬렉션입니다. | user-agent=Edge x-custom-header=MyValue |
HttpRequest.RouteValues | 경로 값의 컬렉션. 요청이 경로와 일치하면 컬렉션이 설정됩니다. | language=en article=getstarted |
HttpRequest.Query | QueryString에서 구문 분석된 쿼리 값의 컬렉션. | filter=hello page=1 |
HttpRequest.ReadFormAsync() | 요청 본문을 폼으로 읽고 양식 값 컬렉션을 반환하는 메서드. 양식 데이터에 액세스하는 데 ReadFormAsync 을 사용해야 하는 이유에 대한 자세한 내용은 Request.Form보다 ReadFormAsync 선호를 참조하세요. |
email=user@contoso.com |
HttpRequest.Body | 요청 본문을 읽기 위한 Stream. | UTF-8 JSON 페이로드 |
요청 헤더를 가져옵니다.
HttpRequest.Headers는 HTTP 요청과 함께 전송된 요청 헤더에 대한 액세스를 제공합니다. 이 컬렉션을 사용하여 헤더에 액세스하는 방법에는 두 가지가 있습니다.
- 헤더 컬렉션의 인덱서에 헤더 이름을 제공합니다. 헤더 이름은 대/소문자를 구분하지 않습니다. 인덱서는 모든 헤더 값에 액세스할 수 있습니다.
- 헤더 컬렉션에는 일반적으로 사용되는 HTTP 헤더를 가져오고 설정하는 속성도 있습니다. 속성은 헤더에 액세스하는 빠른 IntelliSense 기반 방법을 제공합니다.
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();
두 번 이상 표시되는 헤더를 효율적으로 처리하는 방법에 대한 자세한 내용은 StringValues를 간략하게 살펴보세요.
요청 본문 읽기
HTTP 요청에는 요청 본문이 포함될 수 있습니다. 요청 본문은 HTML 양식, UTF-8 JSON 페이로드 또는 파일의 콘텐츠와 같이 요청과 연결된 데이터입니다.
HttpRequest.Body를 사용하여 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();
스트림을 허용하는 다른 API와 함께 HttpRequest.Body
을 직접 읽거나 사용할 수 있습니다.
참고 항목
최소 API는 Stream 매개 변수에 직접 HttpRequest.Body을 바인딩하는 것을 지원합니다.
요청 본문 버퍼링 사용
요청 본문은 처음부터 끝까지 한 번만 읽을 수 있습니다. 요청 본문의 전달 전용 읽기는 전체 요청 본문을 버퍼링하는 오버헤드를 방지하고 메모리 사용량을 줄입니다. 그러나 일부 시나리오에서는 요청 본문을 여러 번 읽어야 합니다. 예를 들어, 미들웨어는 요청 본문을 읽은 다음, 엔드포인트에 사용할 수 있도록 되감아야 할 수 있습니다.
EnableBuffering 확장 메서드를 사용하면 HTTP 요청 본문을 버퍼링할 수 있으며 여러 번 읽기를 사용하도록 설정하는 것이 좋습니다. 요청은 모든 크기일 수 있으므로 EnableBuffering
는 큰 요청 본문을 디스크에 버퍼링하거나 완전히 거부하는 옵션을 지원합니다.
다음 예제의 미들웨어에는 다음이 적용됩니다.
EnableBuffering
를 사용하여 여러 번 읽기를 사용하도록 설정합니다. 요청 본문을 읽기 전에 호출해야 합니다.- 요청 본문을 읽습니다.
- 다른 미들웨어 또는 엔드포인트에서 읽을 수 있도록 요청 본문을 시작으로 되감습니다.
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
요청 본문을 읽는 다른 방법은 HttpRequest.BodyReader 속성을 사용하는 것입니다. BodyReader
속성은 요청 본문을 PipeReader로 노출합니다. 이 API는 요청 본문을 읽는 고급 고성능 방법인 I/O 파이프라인에서 제공됩니다.
판독기는 요청 본문에 직접 액세스하고 호출자를 대신하여 메모리를 관리합니다. HttpRequest.Body
과 달리, 판독기는 요청 데이터를 버퍼에 복사하지 않습니다. 그러나 판독기는 스트림보다 사용하기가 더 복잡하며 주의해서 사용해야 합니다.
BodyReader
에서 콘텐츠를 읽는 방법에 대한 자세한 내용은 I/O 파이프라인 PipeReader를 참조하세요.
HttpResponse
HttpContext.Response은 HttpResponse에 대한 액세스를 제공합니다. HttpResponse
는 클라이언트로 다시 전송된 HTTP 응답에 대한 정보를 설정하는 데 사용됩니다.
HttpResponse
에서 일반적으로 사용되는 멤버는 다음과 같습니다.
속성 | Description | 예시 |
---|---|---|
HttpResponse.StatusCode | 응답 코드. 응답 본문에 쓰기 전에 설정해야 합니다. | 200 |
HttpResponse.ContentType | 응답 content-type 헤더. 응답 본문에 쓰기 전에 설정해야 합니다. |
application/json |
HttpResponse.Headers | 응답 헤더의 컬렉션. 응답 본문에 쓰기 전에 설정해야 합니다. | server=Kestrel x-custom-header=MyValue |
HttpResponse.Body | 응답 본문을 작성하기 위한 Stream. | 생성된 웹 페이지 |
응답 헤더 설정
HttpResponse.Headers는 HTTP 응답과 함께 전송된 응답 헤더에 대한 액세스를 제공합니다. 이 컬렉션을 사용하여 헤더에 액세스하는 방법에는 두 가지가 있습니다.
- 헤더 컬렉션의 인덱서에 헤더 이름을 제공합니다. 헤더 이름은 대/소문자를 구분하지 않습니다. 인덱서는 모든 헤더 값에 액세스할 수 있습니다.
- 헤더 컬렉션에는 일반적으로 사용되는 HTTP 헤더를 가져오고 설정하는 속성도 있습니다. 속성은 헤더에 액세스하는 빠른 IntelliSense 기반 방법을 제공합니다.
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();
응답이 시작된 후에는 앱에서 헤더를 수정할 수 없습니다. 응답이 시작되면 헤더가 클라이언트로 전송됩니다. 응답 본문을 플러시하거나 HttpResponse.StartAsync(CancellationToken)를 호출하여 응답이 시작됩니다. HttpResponse.HasStarted 속성은 응답이 시작되었는지 여부를 나타냅니다. 응답이 시작된 후 헤더를 수정하려고 하면 오류가 발생합니다.
System.InvalidOperationException: 헤더는 읽기 전용이며 응답이 이미 시작되었습니다.
참고 항목
응답 버퍼링을 사용하지 않는 한 모든 쓰기 작업(예 WriteAsync: )은 응답 본문을 내부적으로 플러시하고 응답을 시작으로 표시합니다. 응답 버퍼링은 기본적으로 사용하지 않도록 설정됩니다.
응답 본문 작성
HTTP 응답에는 응답 본문이 포함될 수 있습니다. 응답 본문은 생성된 웹 페이지 콘텐츠, UTF-8 JSON 페이로드 또는 파일과 같은 응답과 연결된 데이터입니다.
HttpResponse.Body을 사용하여 응답 본문을 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
는 스트림에 쓰는 다른 API와 함께 직접 작성하거나 사용할 수 있습니다.
Bodywriter
응답 본문을 작성하는 다른 방법은 HttpResponse.BodyWriter 속성을 사용하는 것입니다. BodyWriter
속성은 응답 본문을 PipeWriter로 노출합니다. 이 API는 I/O 파이프라인에서 생성되며 응답을 작성하는 고급 고성능 방법입니다.
작성기는 응답 본문에 대한 직접 액세스를 제공하고 호출자를 대신하여 메모리를 관리합니다. HttpResponse.Body
과 달리, 작성기는 요청 데이터를 버퍼에 복사하지 않습니다. 그러나 작성기는 철저히 테스트해야 하는 스트림 및 기록기 코드보다 사용하기가 더 복잡합니다.
BodyWriter
에 콘텐츠를 쓰는 방법에 대한 자세한 내용은 I/O 파이프라인 PipeWriter를 참조하세요.
응답 트레일러 설정
HTTP/2 및 HTTP/3은 응답 트레일러를 지원합니다. 트레일러는 응답 본문이 완료된 후 응답과 함께 전송되는 헤더입니다. 트레일러는 응답 본문 이후에 전송되므로 언제든지 트레일러를 응답에 추가할 수 있습니다.
다음 코드는 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
HttpContext.RequestAborted 취소 토큰을 사용하여 클라이언트 또는 서버에서 HTTP 요청이 중단되었음을 알릴 수 있습니다. 요청이 중단된 경우 취소할 수 있도록 취소 토큰을 장기 실행 작업에 전달해야 합니다. 예를 들어, 응답에서 반환할 데이터를 가져오기 위해 데이터베이스 쿼리 또는 HTTP 요청을 중단합니다.
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();
요청이 중단될 때 읽기가 항상 즉시 throw되므로 RequestAborted
취소 토큰을 요청 본문 읽기 작업에 사용할 필요가 없습니다. 요청이 중단될 때 즉시 no-op을 쓰기 때문에, 응답 본문을 작성할 때 RequestAborted
토큰도 일반적으로 필요하지 않습니다.
경우에 따라 쓰기 작업에 RequestAborted
토큰을 전달하는 것이 쓰기 루프가 OperationCanceledException를 사용하여 일찍 종료되도록 강제하는 편리한 방법이 될 수 있습니다. 그러나 일반적으로 대신 응답 본문 콘텐츠를 검색하는 비동기 작업에 RequestAborted
토큰을 전달하는 것이 좋습니다.
참고 항목
최소 API는 CancellationToken 매개 변수에 직접 HttpContext.RequestAborted을 바인딩하는 것을 지원합니다.
Abort()
HttpContext.Abort() 메서드를 사용하여 서버에서 HTTP 요청을 중단할 수 있습니다. HTTP 요청을 중단하면 HttpContext.RequestAborted 취소 토큰이 즉시 트리거되고 서버가 요청을 중단했다는 알림을 클라이언트에 보냅니다.
다음 예제의 미들웨어에는 다음이 적용됩니다.
- 악의적인 요청에 대한 사용자 지정 검사를 추가합니다.
- 요청이 악의적인 경우 HTTP 요청을 중단합니다.
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
HttpContext.User 속성은 요청에 대해 ClaimsPrincipal로 표시되는 사용자를 가져오기 또는 설정하는 데 사용됩니다. ClaimsPrincipal은 일반적으로 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();
참고 항목
최소 API는 ClaimsPrincipal 매개 변수에 직접 HttpContext.User을 바인딩하는 것을 지원합니다.
Features
HttpContext.Features 속성은 현재 요청의 기능 인터페이스 컬렉션에 대한 액세스를 제공합니다. 기능 컬렉션은 요청 컨텍스트 내에서도 변경할 수 있기 때문에, 미들웨어를 사용하여 콜렉션을 수정하고 추가 기능에 대한 지원을 추가할 수 있습니다. 일부 고급 기능은 기능 컬렉션을 통해 연결된 인터페이스에 액세스해야 사용할 수 있습니다.
다음 예제를 참조하세요.
- 기능 컬렉션에서 IHttpMinRequestBodyDataRateFeature을 가져옵니다.
- MinDataRate을 null로 설정합니다. 이렇게 하면 이 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();
요청 기능 및 HttpContext
사용에 대한 자세한 내용은 ASP.NET Core에서 기능 요청을 참조하세요.
HttpContext가 스레드로부터 안전하지 않음
이 문서에서는 주로 Razor Pages, 컨트롤러, 미들웨어 등의 요청 및 응답 흐름에 HttpContext
을(를) 사용하는 방법에 대해 설명합니다. 요청 및 응답 흐름 외부에서 HttpContext
을(를) 사용할 때 다음 사항을 고려합니다.
HttpContext
은(는) 스레드가 안전하지 않으므로 여러 스레드에서 액세스하면 예외, 데이터 손상 및 일반적으로 예측할 수 없는 결과가 발생할 수 있습니다.- IHttpContextAccessor 인터페이스는 주의해서 사용해야 합니다. 항상 그렇듯이
HttpContext
은(는) 요청 흐름 외부에서 캡처해서는 안 됩니다.IHttpContextAccessor
:- 비동기 호출에 부정적인 성능 영향을 미칠 수 있는 AsyncLocal<T> 기능을 사용합니다.
- 테스트를 더 어렵게 만들 수 있는 "주변 상태"에 대한 종속성을 만듭니다.
- IHttpContextAccessor.HttpContext은(는) 요청 흐름 외부에서 액세스하는 경우
null
일 수 있습니다. - 요청 흐름 외부의
HttpContext
에서 정보에 액세스하려면 요청 흐름 내에서 정보를 복사합니다. 참조뿐만 아니라 실제 데이터를 주의해서 복사해야 합니다. 예를 들어 참조를IHeaderDictionary
(으)로 복사하는 대신, 관련 헤더 값을 복사하거나 요청 흐름을 벗어나기 전에 키별로 전체 사전 키를 복사합니다. - 생성자에서
IHttpContextAccessor.HttpContext
을(를) 캡처하지 마세요.
다음 샘플은 /branch
엔드포인트에서 요청된 경우 GitHub 분기를 기록합니다.
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에는 두 개의 헤더가 필요합니다. User-Agent
헤더는 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();
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);
}
}
앞의 코드에서 HttpContext
이(가) null
인 경우, userAgent
문자열이 "Unknown"
로 설정됩니다. 가능하면 HttpContext
은(는) 서비스에 명시적으로 전달되어야 합니다. HttpContext
데이터를 명시적으로 전달합니다.
- 요청 흐름 외부에서 서비스 API를 더 쉽게 사용할 수 있게 합니다.
- 성능에 더 좋습니다.
- 앰비언트 상태에 의존하는 것보다 코드를 더 쉽게 이해하고 추론할 수 있습니다.
서비스가 HttpContext
에 액세스 해야 하는 경우, 요청 스레드에서 호출되지 않으면, HttpContext
이(가) null
일 가능성을 고려해야 합니다.
또한 애플리케이션에는 지정된 리포지토리의 열린 GitHub 분기를 30초마다 기록하는 PeriodicBranchesLoggerService
도 포함됩니다.
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
은(는) 요청 및 응답 흐름 외부에서 실행되는 호스트된 서비스입니다. PeriodicBranchesLoggerService
에서 로깅에 null HttpContext
이(가) 있습니다. PeriodicBranchesLoggerService
은(는) 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 =>
{
ASP.NET Core