Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Note
Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikel voor de huidige release.
Warning
Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie het .NET- en .NET Core-ondersteuningsbeleid voor meer informatie. Zie de .NET 9-versie van dit artikel voor de huidige release.
Important
Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.
Zie de .NET 9-versie van dit artikel voor de huidige release.
Door Kirk Larkin, Steve Gordon, Glenn Condron en Ryan Nowak.
Een IHttpClientFactory kan worden geregistreerd en gebruikt voor het configureren en maken van HttpClient exemplaren in een app.
IHttpClientFactory biedt de volgende voordelen:
- Biedt een centrale locatie voor het benoemen en configureren van logische
HttpClientinstanties. Een client met de naam github kan bijvoorbeeld worden geregistreerd en geconfigureerd voor toegang tot GitHub. Een standaardclient kan worden geregistreerd voor algemene toegang. - Codifeert het concept van uitgaande middleware via het delegeren van handlers in
HttpClient. Biedt uitbreidingen voor op Polly gebaseerde middleware om te profiteren van het delegeren van handlers inHttpClient. - Beheert de pooling en levensduur van onderliggende
HttpClientMessageHandlerexemplaren. Automatisch beheer voorkomt veelvoorkomende DNS-problemen (Domain Name System) die optreden bij het handmatig beheren vanHttpClientlevensduur. - Voegt een configureerbare logboekregistratie-ervaring (via
ILogger) toe voor alle aanvragen die worden verzonden via clients die door de fabriek zijn gemaakt.
De voorbeeldcode in deze onderwerpversie gebruikt System.Text.Json voor het deserialiseren van JSON-inhoud die wordt geretourneerd in HTTP-antwoorden. Voor voorbeelden die gebruikmaken van Json.NET en ReadAsAsync<T>, gebruik de versiekiezer om een 2.x-versie van dit onderwerp te selecteren.
Consumptiepatronen
Er zijn verschillende manieren IHttpClientFactory om in een app te gebruiken:
De beste aanpak is afhankelijk van de vereisten van de app.
Basaal gebruik
Registreer IHttpClientFactory door te bellen naar AddHttpClient in Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();
Een IHttpClientFactory kan worden aangevraagd met behulp van afhankelijkheidsinjectie (DI). De volgende code gebruikt IHttpClientFactory om een HttpClient exemplaar te maken:
public class BasicModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public BasicModel(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ HeaderNames.Accept, "application/vnd.github.v3+json" },
{ HeaderNames.UserAgent, "HttpRequestsSample" }
}
};
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
Het gebruik van IHttpClientFactory zoals in het voorgaande voorbeeld is een goede manier om een bestaande app te herstructureren. Het heeft geen invloed op hoe HttpClient wordt gebruikt. Op plaatsen waar HttpClient exemplaren worden gemaakt in een bestaande app, vervangt u deze exemplaren door aanroepen naar CreateClient.
Benoemde clients
Benoemde clients zijn een goede keuze wanneer:
- Voor de app zijn veel verschillende toepassingen van
HttpClientvereist. - Veel
HttpClients hebben een andere configuratie.
Geef de configuratie op voor een naam HttpClient tijdens de registratie in Program.cs.
builder.Services.AddHttpClient("GitHub", httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});
In de voorgaande code is de client geconfigureerd met:
- Het basisadres
https://api.github.com/. - Er zijn twee headers vereist om te werken met de GitHub-API.
CreateClient
Elke keer CreateClient wordt het volgende aangeroepen:
- Er wordt een nieuw exemplaar gemaakt
HttpClient. - De configuratieactie wordt aangeroepen.
Als u een benoemde client wilt maken, geeft u de naam door in CreateClient:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public NamedClientModel(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpClient = _httpClientFactory.CreateClient("GitHub");
var httpResponseMessage = await httpClient.GetAsync(
"repos/dotnet/AspNetCore.Docs/branches");
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
In de voorgaande code hoeft de aanvraag geen hostnaam op te geven. De code kan alleen het pad doorgeven, omdat het basisadres dat is geconfigureerd voor de client wordt gebruikt.
Getypte clients
Getypte clients:
- Bied dezelfde mogelijkheden als benoemde clients zonder dat u tekenreeksen als sleutels hoeft te gebruiken.
- Biedt IntelliSense en compilerhulp voor het gebruik van cliënten.
- Geef één locatie op om een bepaalde
HttpClientlocatie te configureren en ermee te communiceren. Er kan bijvoorbeeld één getypeerde client worden gebruikt:- Voor één back-endeindpunt.
- Als u alle logica voor het eindpunt wilt inkapselen.
- Werk met DI en kan waar nodig worden geïnjecteerd in de app.
Een getypte client accepteert een HttpClient parameter in de constructor:
public class GitHubService
{
private readonly HttpClient _httpClient;
public GitHubService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
}
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync() =>
await _httpClient.GetFromJsonAsync<IEnumerable<GitHubBranch>>(
"repos/dotnet/AspNetCore.Docs/branches");
}
In de voorgaande code:
- De configuratie wordt verplaatst naar de getypte client.
- Het opgegeven
HttpClientexemplaar wordt opgeslagen als een privéveld.
API-specifieke methoden kunnen worden gemaakt die functionaliteit beschikbaar HttpClient maken. Met de GetAspNetCoreDocsBranches-methode wordt bijvoorbeeld code ingekapseld om documenten van GitHub-vertakkingen op te halen.
De volgende code roept AddHttpClient aan in Program.cs om de GitHubService getypte clientklasse te registreren:
builder.Services.AddHttpClient<GitHubService>();
De getypte client wordt geregistreerd als tijdelijk bij DI. In de voorgaande code AddHttpClient wordt geregistreerd GitHubService als een tijdelijke service. Deze registratie maakt gebruik van een factory-methode voor het volgende:
- Maak een exemplaar van
HttpClient. - Maak een exemplaar van
GitHubService, door te geven in het exemplaar vanHttpClientde constructor.
De getypte client kan worden geïnjecteerd en direct gebruikt.
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public TypedClientModel(GitHubService gitHubService) =>
_gitHubService = gitHubService;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
GitHubBranches = await _gitHubService.GetAspNetCoreDocsBranchesAsync();
}
catch (HttpRequestException)
{
// ...
}
}
}
De configuratie voor een getypte client kan ook worden opgegeven tijdens de registratie in Program.cs, in plaats van in de constructor van de getypte client:
builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// ...
});
Gegenereerde clients
IHttpClientFactory kan worden gebruikt in combinatie met bibliotheken van derden, zoals Refit. Refit is een REST bibliotheek voor .NET. Het converteert REST API's naar live-interfaces. Aanroep AddRefitClient voor het genereren van een dynamische implementatie van een interface, die wordt gebruikt HttpClient om de externe HTTP-aanroepen uit te voeren.
Een aangepaste interface vertegenwoordigt de externe API:
public interface IGitHubClient
{
[Get("/repos/dotnet/AspNetCore.Docs/branches")]
Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}
Aanroep AddRefitClient om de dynamische implementatie te genereren en vervolgens aan te roepen ConfigureHttpClient om de onderliggende HttpClientimplementatie te configureren:
builder.Services.AddRefitClient<IGitHubClient>()
.ConfigureHttpClient(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});
Gebruik DI om toegang te krijgen tot de dynamische implementatie van IGitHubClient:
public class RefitModel : PageModel
{
private readonly IGitHubClient _gitHubClient;
public RefitModel(IGitHubClient gitHubClient) =>
_gitHubClient = gitHubClient;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
GitHubBranches = await _gitHubClient.GetAspNetCoreDocsBranchesAsync();
}
catch (ApiException)
{
// ...
}
}
}
POST-, PUT- en DELETE-aanvragen maken
In de voorgaande voorbeelden gebruiken alle HTTP-aanvragen het GET HTTP-werkwoord.
HttpClient ondersteunt ook andere HTTP-woorden, waaronder:
- POST
- PUT
- DELETE
- PATCH
Zie voor een volledige lijst met ondersteunde HTTP-woorden HttpMethod.
In het volgende voorbeeld ziet u hoe u een HTTP POST-aanvraag maakt:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json); // using static System.Net.Mime.MediaTypeNames;
using var httpResponseMessage =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}
In de voorgaande code is de CreateItemAsync methode:
- Serialiseert de
TodoItemparameter naar JSON met behulp vanSystem.Text.Json. - Hiermee maakt u een exemplaar van StringContent het pakket van de geserialiseerde JSON voor verzending in de hoofdtekst van de HTTP-aanvraag.
- Aanroepen PostAsync om de JSON-inhoud naar de opgegeven URL te verzenden. Dit is een relatieve URL die wordt toegevoegd aan het HttpClient.BaseAddress.
- Aanroepen EnsureSuccessStatusCode om een uitzondering te genereren als de antwoordstatuscode geen succes aangeeft.
HttpClient ondersteunt ook andere typen inhoud. Bijvoorbeeld MultipartContent en StreamContent. Zie voor een volledige lijst met ondersteunde inhoud HttpContent.
In het volgende voorbeeld ziet u een HTTP PUT-aanvraag:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json);
using var httpResponseMessage =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}
De voorgaande code is vergelijkbaar met het POST-voorbeeld. De SaveItemAsync methode roept PutAsync in plaats van PostAsync.
In het volgende voorbeeld ziet u een HTTP DELETE-aanvraag:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponseMessage =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponseMessage.EnsureSuccessStatusCode();
}
In de voorgaande code roept DeleteItemAsyncde methode aanDeleteAsync. Omdat HTTP DELETE-aanvragen doorgaans geen hoofdtekst bevatten, biedt de DeleteAsync methode geen overbelasting die een exemplaar accepteert.HttpContent
Zie HttpClientvoor meer informatie over het gebruik van verschillende HTTP-woorden met HttpClient.
Middleware voor uitgaande aanvragen
HttpClient heeft het concept van het delegeren van handlers die kunnen worden gekoppeld voor uitgaande HTTP-aanvragen.
IHttpClientFactory:
- Vereenvoudigt het definiëren van de handlers die voor elke benoemde client moeten worden toegepast.
- Ondersteunt registratie en ketening van meerdere handlers voor het bouwen van een middleware-pijplijn voor uitgaande aanvragen. Elk van deze handlers kan werk uitvoeren voor en na de uitgaande aanvraag. Dit patroon:
- Is vergelijkbaar met de binnenkomende middleware-pijplijn in ASP.NET Core.
- Biedt een mechanisme voor het beheren van kruislingse problemen rond HTTP-aanvragen, zoals:
- caching
- error handling
- serialization
- logging
Een delegerende handler maken:
- Afgeleid van DelegatingHandler.
- Overschrijf SendAsync. Voer code uit voordat u de aanvraag doorgeeft aan de volgende handler in de pijplijn:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"The API key header X-API-KEY is required.")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Met de voorgaande code wordt gecontroleerd of de X-API-KEY header zich in de aanvraag bevindt. Als X-API-KEY ontbreekt, wordt BadRequest geretourneerd.
Meer dan één handler kan worden toegevoegd aan de configuratie voor een HttpClient met Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
builder.Services.AddTransient<ValidateHeaderHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<ValidateHeaderHandler>();
In de voorgaande code wordt de ValidateHeaderHandler code geregistreerd bij DI. Zodra geregistreerd, kan AddHttpMessageHandler worden aangeroepen, waarbij het type voor de handler wordt doorgegeven.
Meerdere handlers kunnen worden geregistreerd in de volgorde waarin ze moeten worden uitgevoerd. Elke handler verpakt de volgende handler totdat de laatste HttpClientHandler de aanvraag uitvoert:
builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();
builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
.AddHttpMessageHandler<SampleHandler1>()
.AddHttpMessageHandler<SampleHandler2>();
In de voorgaande code wordt SampleHandler1 eerst uitgevoerd, vóór SampleHandler2.
DI gebruiken in middleware voor uitgaande aanvragen
Wanneer IHttpClientFactory er een nieuwe delegeringshandler wordt gemaakt, wordt DI gebruikt om te voldoen aan de constructorparameters van de handler.
IHttpClientFactory maakt een afzonderlijk DI-bereik voor elke handler, wat kan leiden tot verrassend gedrag wanneer een handler een scoped service verbruikt.
Denk bijvoorbeeld aan de volgende interface en de implementatie, die een taak vertegenwoordigt als een bewerking met een id: OperationId
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Zoals de naam al aangeeft, wordt IOperationScoped geregistreerd bij DI met behulp van een scoped levensduur:
builder.Services.AddScoped<IOperationScoped, OperationScoped>();
De volgende delegerende handler verbruikt en gebruikt IOperationScoped om de X-OPERATION-ID header voor de uitgaande aanvraag in te stellen:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationScoped;
public OperationHandler(IOperationScoped operationScoped) =>
_operationScoped = operationScoped;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationScoped.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
Navigeer in het HttpRequestsSample downloadbestand naar /Operation en vernieuw de pagina. De waarde van het aanvraagbereik wordt voor elke aanvraag gewijzigd, maar de scopewaarde van de handler wordt slechts elke 5 seconden gewijzigd.
Handlers kunnen gebruikmaken van services van enige omvang. Services waarop handlers afhankelijk zijn, worden verwijderd wanneer de handler wordt verwijderd.
Gebruik een van de volgende methoden om de status per aanvraag te delen met berichthandlers:
- Geef gegevens door aan de handler met behulp van HttpRequestMessage.Options.
- Gebruiken IHttpContextAccessor om toegang te krijgen tot de huidige aanvraag.
- Maak een aangepast AsyncLocal<T> opslagobject om de gegevens door te geven.
Op Polly gebaseerde handlers gebruiken
IHttpClientFactory kan worden geïntegreerd met de bibliotheek van derden Polly. Polly is een uitgebreide bibliotheek voor tolerantie en tijdelijke foutafhandeling voor .NET. Hiermee kunnen ontwikkelaars beleid uitdrukken, zoals Retry, Circuit Breaker, Timeout, Bulkhead Isolation en Fallback op een vloeiende en thread-veilige manier.
Extensiemethoden worden geboden om het gebruik van Polly-beleidsregels met geconfigureerde HttpClient exemplaren in te schakelen. De Polly-extensies ondersteunen het toevoegen van op Polly gebaseerde handlers aan clients. Polly vereist het Microsoft.Extensions.Http.Polly NuGet-pakket.
Tijdelijke fouten afhandelen
Fouten treden meestal op wanneer externe HTTP-aanroepen tijdelijk zijn.
AddTransientHttpErrorPolicy hiermee kan een beleid worden gedefinieerd voor het afhandelen van tijdelijke fouten. Beleidsregels die zijn geconfigureerd met AddTransientHttpErrorPolicy de volgende antwoorden verwerken:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy biedt toegang tot een PolicyBuilder object dat is geconfigureerd voor het afhandelen van fouten die een mogelijke tijdelijke fout vertegenwoordigen:
builder.Services.AddHttpClient("PollyWaitAndRetry")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3, retryNumber => TimeSpan.FromMilliseconds(600)));
In de voorgaande code wordt een WaitAndRetryAsync beleid gedefinieerd. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd met een vertraging van 600 ms tussen pogingen.
Dynamisch beleid selecteren
Extensiemethoden worden geleverd om bijvoorbeeld AddPolicyHandlerop Polly gebaseerde handlers toe te voegen. De volgende AddPolicyHandler overload inspecteert het verzoek om te bepalen welk beleid moet worden toegepast.
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
builder.Services.AddHttpClient("PollyDynamic")
.AddPolicyHandler(httpRequestMessage =>
httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy : longTimeoutPolicy);
Als de uitgaande aanvraag een HTTP GET is, wordt in de voorgaande code een time-out van 10 seconden toegepast. Voor elke andere HTTP-methode wordt een time-out van 30 seconden gebruikt.
Meerdere Polly-handlers toevoegen
Het is gebruikelijk om Polly-beleid te nesten:
builder.Services.AddHttpClient("PollyMultiple")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.RetryAsync(3))
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
In het voorgaande voorbeeld:
- Er worden twee handlers toegevoegd.
- De eerste handler gebruikt AddTransientHttpErrorPolicy om een beleid voor opnieuw proberen toe te voegen. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd.
- Met de tweede
AddTransientHttpErrorPolicyaanroep wordt een beleid voor circuitonderbrekers toegevoegd. Verdere externe aanvragen worden 30 seconden geblokkeerd als 5 mislukte pogingen opeenvolgend plaatsvinden. Circuitonderbreker beleid is toestandsafhankelijk. Alle aanroepen via deze client delen dezelfde circuitstatus.
Beleid toevoegen uit het Polly-register
Een benadering voor het beheren van regelmatig gebruikte beleidsregels is om ze eenmaal te definiëren en te registreren bij een PolicyRegistry. Voorbeeld:
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var policyRegistry = builder.Services.AddPolicyRegistry();
policyRegistry.Add("Regular", timeoutPolicy);
policyRegistry.Add("Long", longTimeoutPolicy);
builder.Services.AddHttpClient("PollyRegistryRegular")
.AddPolicyHandlerFromRegistry("Regular");
builder.Services.AddHttpClient("PollyRegistryLong")
.AddPolicyHandlerFromRegistry("Long");
In de voorgaande code:
- Er worden twee beleidsregels
RegularLongtoegevoegd aan het Polly-register. - AddPolicyHandlerFromRegistry configureert individuele benoemde clients om deze beleidsregels uit het Polly-register te gebruiken.
Zie de IHttpClientFactory voor meer informatie over en Polly-integraties.
HttpClient en levensduurbeheer
Er wordt telkens een nieuw HttpClient exemplaar geretourneerd wanneer CreateClient deze IHttpClientFactorywordt aangeroepen. Een HttpMessageHandler wordt gecreëerd per benoemde cliënt. De fabriek beheert de levensduur van de HttpMessageHandler exemplaren.
IHttpClientFactory voegt de instanties van HttpMessageHandler die door de fabriek zijn gecreëerd samen om het gebruik van middelen te verminderen. Een HttpMessageHandler exemplaar kan opnieuw worden gebruikt vanuit de pool bij het maken van een nieuw HttpClient exemplaar als de levensduur ervan niet is verlopen.
Pooling van handlers is wenselijk omdat elke handler doorgaans zijn eigen onderliggende HTTP-verbindingen beheert. Het maken van meer handlers dan nodig is, kan leiden tot verbindingsvertragingen. Sommige handlers houden verbindingen ook voor onbepaalde tijd open, waardoor de handler niet kan reageren op DNS-wijzigingen (Domain Name System).
De standaardlevensduur van de handler is twee minuten. De standaardwaarde kan per benoemde client worden overschreven:
builder.Services.AddHttpClient("HandlerLifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
HttpClient exemplaren kunnen over het algemeen worden behandeld als .NET-objecten die geen verwijdering vereisen. Opruimen annuleert uitgaande aanvragen en garandeert dat de gegeven HttpClient instantie na gebruik van Dispose niet meer kan worden gebruikt.
IHttpClientFactory houdt de resources bij en verwijdert deze die door exemplaren van HttpClient worden gebruikt.
Het levend houden van één HttpClient exemplaar voor een lange duur is een veelgebruikte methode voordat de opkomst van IHttpClientFactory. Dit patroon wordt overbodig na de migratie naar IHttpClientFactory.
Alternatieven voor IHttpClientFactory
Door gebruik te maken van IHttpClientFactory in een DI-ingeschakelde applicatie worden de volgende zaken vermeden:
- Problemen met uitputting van middelen door het groeperen van
HttpMessageHandlerinstanties. - Verouderde DNS-problemen oplossen door
HttpMessageHandlerinstanties regelmatig te rouleren.
Er zijn alternatieve manieren om de voorgaande problemen op te lossen met behulp van een SocketsHttpHandler-exemplaar met een lange levensduur.
- Maak een exemplaar van
SocketsHttpHandlerwanneer de app wordt gestart en gebruik deze voor de levensduur van de app. - Configureer PooledConnectionLifetime naar een geschikte waarde op basis van DNS-vernieuwingstijden.
- Maak
HttpClient-instanties zo nodig metnew HttpClient(handler, disposeHandler: false).
Met de voorgaande benaderingen worden de problemen met resourcebeheer op een vergelijkbare manier opgelost als IHttpClientFactory.
- De
SocketsHttpHandlerdeelt verbindingen tussenHttpClientexemplaren. Dit delen voorkomt uitputting van sockets. - De
SocketsHttpHandlercyclet verbindingen volgensPooledConnectionLifetimeom verouderde DNS-problemen te voorkomen.
Logging
Clients die zijn gemaakt via IHttpClientFactory leggen logboekberichten vast voor alle verzoeken. Schakel het juiste informatieniveau in de logboekconfiguratie in om de standaardlogboekberichten te zien. Aanvullende logboeken, zoals de logboeken van aanvraagheaders, worden alleen opgenomen op trace-niveau.
De logboekcategorie die voor elke client wordt gebruikt, bevat de naam van de client. Een client met de naam MyNamedClient registreert bijvoorbeeld berichten met een categorie System.Net.Http.HttpClient. MyNamedClient. LogicalHandler". Berichten met het achtervoegsel LogicalHandler komen voor buiten de pijplijn van de aanvraaghandler. Op de aanvraag worden berichten geregistreerd voordat andere handlers in de pijplijn deze hebben verwerkt. Bij het antwoord worden berichten geregistreerd nadat andere pipeline-handlers het antwoord hebben ontvangen.
Logboekregistratie vindt ook plaats in de pijplijn van de aanvraaghandler. In het voorbeeld van MyNamedClient worden deze berichten vastgelegd met de logboekcategorie System.Net.Http.HttpClient. MyNamedClient. ClientHandler". Voor de aanvraag gebeurt dit nadat alle andere handlers zijn uitgevoerd en direct voordat de aanvraag wordt verzonden. In het antwoord bevat deze logboekregistratie de status van het antwoord voordat deze wordt doorgestuurd via de handler-pijplijn.
Door logboekregistratie buiten en binnen de pijplijn in te schakelen, kunnen de wijzigingen die door de andere pijplijnhandlers zijn aangebracht, worden gecontroleerd. Dit kunnen wijzigingen in aanvraagheaders of de antwoordstatuscode zijn.
Als u de naam van de client in de logboekcategorie opvoegt, kunt u logboekfiltering voor specifieke benoemde clients inschakelen.
HttpMessageHandler configureren
Het kan nodig zijn om de configuratie van de binnenste HttpMessageHandler te beheren die door een client wordt gebruikt.
Er IHttpClientBuilder wordt een geretourneerd bij het toevoegen van benoemde of getypte clients. De ConfigurePrimaryHttpMessageHandler extensiemethode kan worden gebruikt om een gemachtigde te definiëren. De gemachtigde wordt gebruikt voor het maken en configureren van de primaire die HttpMessageHandler door die client wordt gebruikt:
builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
AllowAutoRedirect = true,
UseDefaultCredentials = true
});
Cookies
De gegroepeerde HttpMessageHandler exemplaren resulteren in CookieContainer gedeelde objecten.
CookieContainer Onverwacht object delen leidt vaak tot onjuiste code. Voor apps waarvoor cookies zijn vereist, kunt u het volgende overwegen:
- cookie Automatische verwerking uitschakelen
- Het vermijden van
IHttpClientFactory
Aanroep ConfigurePrimaryHttpMessageHandler om automatische cookie verwerking uit te schakelen:
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});
IHttpClientFactory gebruiken in een console-app
Voeg in een console-app de volgende pakketverwijzingen toe aan het project:
In het volgende voorbeeld:
-
IHttpClientFactory en
GitHubServicezijn geregistreerd in de servicecontainer van de Generic Host . -
GitHubServicewordt aangevraagd bij DI, die op zijn beurt een instantie vanIHttpClientFactoryaanvraagt. -
GitHubServicegebruiktIHttpClientFactoryvoor het maken van een instantie vanHttpClient, die wordt gebruikt om Docs GitHub-vertakkingen op te halen.
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var host = new HostBuilder()
.ConfigureServices(services =>
{
services.AddHttpClient();
services.AddTransient<GitHubService>();
})
.Build();
try
{
var gitHubService = host.Services.GetRequiredService<GitHubService>();
var gitHubBranches = await gitHubService.GetAspNetCoreDocsBranchesAsync();
Console.WriteLine($"{gitHubBranches?.Count() ?? 0} GitHub Branches");
if (gitHubBranches is not null)
{
foreach (var gitHubBranch in gitHubBranches)
{
Console.WriteLine($"- {gitHubBranch.Name}");
}
}
}
catch (Exception ex)
{
host.Services.GetRequiredService<ILogger<Program>>()
.LogError(ex, "Unable to load branches from GitHub.");
}
public class GitHubService
{
private readonly IHttpClientFactory _httpClientFactory;
public GitHubService(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ "Accept", "application/vnd.github.v3+json" },
{ "User-Agent", "HttpRequestsConsoleSample" }
}
};
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
httpResponseMessage.EnsureSuccessStatusCode();
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
public record GitHubBranch(
[property: JsonPropertyName("name")] string Name);
Middleware voor headerdoorgifte
Headerdoorgifte is een ASP.NET Core-middleware voor het doorgeven van HTTP-headers van de binnenkomende aanvraag naar de uitgaande HttpClient verzoeken. Koptekst propagatie gebruiken:
Installeer het Microsoft.AspNetCore.HeaderPropagation-pakket .
Configureer de
HttpClienten middleware pijplijn inProgram.cs:// Add services to the container. builder.Services.AddControllers(); builder.Services.AddHttpClient("PropagateHeaders") .AddHeaderPropagation(); builder.Services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.MapControllers();Maak uitgaande aanvragen met behulp van het geconfigureerde
HttpClientexemplaar, dat de toegevoegde headers bevat.
Aanvullende bronnen
- Voorbeeldcode bekijken of downloaden (hoe download je)
- HttpClientFactory gebruiken om tolerante HTTP-aanvragen te implementeren
- HTTP-aanroepherhaling implementeren met exponentieel uitstel met HttpClientFactory- en Polly-beleid
- De Circuit Breaker-patroon implementeren
- JSON serialiseren en deserialiseren in .NET
Door Kirk Larkin, Steve Gordon, Glenn Condron en Ryan Nowak.
Een IHttpClientFactory kan worden geregistreerd en gebruikt voor het configureren en maken van HttpClient exemplaren in een app.
IHttpClientFactory biedt de volgende voordelen:
- Biedt een centrale locatie voor het benoemen en configureren van logische
HttpClientinstanties. Een client met de naam github kan bijvoorbeeld worden geregistreerd en geconfigureerd voor toegang tot GitHub. Een standaardclient kan worden geregistreerd voor algemene toegang. - Codifeert het concept van uitgaande middleware via het delegeren van handlers in
HttpClient. Biedt uitbreidingen voor op Polly gebaseerde middleware om te profiteren van het delegeren van handlers inHttpClient. - Beheert de pooling en levensduur van onderliggende
HttpClientMessageHandlerexemplaren. Automatisch beheer voorkomt veelvoorkomende DNS-problemen (Domain Name System) die optreden bij het handmatig beheren vanHttpClientlevensduur. - Voegt een configureerbare logboekregistratie-ervaring (via
ILogger) toe voor alle aanvragen die worden verzonden via clients die door de fabriek zijn gemaakt.
Voorbeeldcode bekijken of downloaden (hoe u kunt downloaden).
De voorbeeldcode in deze onderwerpversie gebruikt System.Text.Json voor het deserialiseren van JSON-inhoud die wordt geretourneerd in HTTP-antwoorden. Voor voorbeelden die gebruikmaken van Json.NET en ReadAsAsync<T>, gebruik de versiekiezer om een 2.x-versie van dit onderwerp te selecteren.
Consumptiepatronen
Er zijn verschillende manieren IHttpClientFactory om in een app te gebruiken:
De beste aanpak is afhankelijk van de vereisten van de app.
Basaal gebruik
IHttpClientFactory kan worden geregistreerd door te bellen AddHttpClient:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// Remaining code deleted for brevity.
Een IHttpClientFactory kan worden aangevraagd met behulp van afhankelijkheidsinjectie (DI). De volgende code gebruikt IHttpClientFactory om een HttpClient exemplaar te maken:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
Branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Het gebruik van IHttpClientFactory zoals in het voorgaande voorbeeld is een goede manier om een bestaande app te herstructureren. Het heeft geen invloed op hoe HttpClient wordt gebruikt. Op plaatsen waar HttpClient exemplaren worden gemaakt in een bestaande app, vervangt u deze exemplaren door aanroepen naar CreateClient.
Benoemde clients
Benoemde clients zijn een goede keuze wanneer:
- Voor de app zijn veel verschillende toepassingen van
HttpClientvereist. - Veel
HttpClients hebben een andere configuratie.
Configuratie voor een genummerde HttpClient kan tijdens de registratie in Startup.ConfigureServices worden opgegeven.
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
In de voorgaande code is de client geconfigureerd met:
- Het basisadres
https://api.github.com/. - Er zijn twee headers vereist om te werken met de GitHub-API.
CreateClient
Elke keer CreateClient wordt het volgende aangeroepen:
- Er wordt een nieuw exemplaar gemaakt
HttpClient. - De configuratieactie wordt aangeroepen.
Als u een benoemde client wilt maken, geeft u de naam door in CreateClient:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
In de voorgaande code hoeft de aanvraag geen hostnaam op te geven. De code kan alleen het pad doorgeven, omdat het basisadres dat is geconfigureerd voor de client wordt gebruikt.
Getypte clients
Getypte clients:
- Bied dezelfde mogelijkheden als benoemde clients zonder dat u tekenreeksen als sleutels hoeft te gebruiken.
- Biedt IntelliSense en compilerhulp voor het gebruik van cliënten.
- Geef één locatie op om een bepaalde
HttpClientlocatie te configureren en ermee te communiceren. Er kan bijvoorbeeld één getypeerde client worden gebruikt:- Voor één back-endeindpunt.
- Als u alle logica voor het eindpunt wilt inkapselen.
- Werk met DI en kan waar nodig worden geïnjecteerd in de app.
Een getypte client accepteert een HttpClient parameter in de constructor:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
return await Client.GetFromJsonAsync<IEnumerable<GitHubIssue>>(
"/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
}
}
In de voorgaande code:
- De configuratie wordt verplaatst naar de getypte client.
- Het
HttpClientobject wordt weergegeven als een openbare eigenschap.
API-specifieke methoden kunnen worden gemaakt die functionaliteit beschikbaar HttpClient maken. Met de GetAspNetDocsIssues methode wordt bijvoorbeeld code ingekapseld om openstaande problemen op te halen.
De volgende code roept AddHttpClient aan binnen Startup.ConfigureServices om een getypte clientklasse te registreren.
services.AddHttpClient<GitHubService>();
De getypte client wordt geregistreerd als tijdelijk bij DI. In de voorgaande code AddHttpClient wordt geregistreerd GitHubService als een tijdelijke service. Deze registratie maakt gebruik van een factory-methode voor het volgende:
- Maak een exemplaar van
HttpClient. - Maak een exemplaar van
GitHubService, door te geven in het exemplaar vanHttpClientde constructor.
De getypte client kan worden geïnjecteerd en direct gebruikt.
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
De configuratie voor een getypte client kan worden opgegeven tijdens de registratie in Startup.ConfigureServices, in plaats van in de constructor van de getypte client:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
De HttpClient kan worden ingekapseld in een getypte client. In plaats van deze als eigenschap weer te geven, definieert u een methode waarmee het HttpClient exemplaar intern wordt aangeroepen:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<string>>(responseStream);
}
}
In de voorgaande code wordt de HttpClient code opgeslagen in een privéveld. Toegang tot de HttpClient is via de openbare GetRepos methode.
Gegenereerde clients
IHttpClientFactory kan worden gebruikt in combinatie met bibliotheken van derden, zoals Refit. Refit is een REST bibliotheek voor .NET. Het converteert REST API's naar live-interfaces. Er wordt dynamisch een implementatie van de interface gegenereerd door de RestService, waarbij HttpClient de externe HTTP-aanroepen worden uitgevoerd.
Een interface en een antwoord worden gedefinieerd om de externe API en het bijbehorende antwoord weer te geven:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Een getypte client kan worden toegevoegd met Behulp van Refit om de implementatie te genereren:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddControllers();
}
De gedefinieerde interface kan waar nodig worden gebruikt, waarbij DI en Refit de implementatie leveren.
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
POST-, PUT- en DELETE-aanvragen maken
In de voorgaande voorbeelden gebruiken alle HTTP-aanvragen het GET HTTP-werkwoord.
HttpClient ondersteunt ook andere HTTP-woorden, waaronder:
- POST
- PUT
- DELETE
- PATCH
Zie voor een volledige lijst met ondersteunde HTTP-woorden HttpMethod.
In het volgende voorbeeld ziet u hoe u een HTTP POST-aanvraag maakt:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
In de voorgaande code is de CreateItemAsync methode:
- Serialiseert de
TodoItemparameter naar JSON met behulp vanSystem.Text.Json. Dit maakt gebruik van een exemplaar van JsonSerializerOptions het serialisatieproces te configureren. - Hiermee maakt u een exemplaar van StringContent het pakket van de geserialiseerde JSON voor verzending in de hoofdtekst van de HTTP-aanvraag.
- Aanroepen PostAsync om de JSON-inhoud naar de opgegeven URL te verzenden. Dit is een relatieve URL die wordt toegevoegd aan het HttpClient.BaseAddress.
- Aanroepen EnsureSuccessStatusCode om een uitzondering te genereren als de antwoordstatuscode niet aangeeft dat deze is geslaagd.
HttpClient ondersteunt ook andere typen inhoud. Bijvoorbeeld MultipartContent en StreamContent. Zie voor een volledige lijst met ondersteunde inhoud HttpContent.
In het volgende voorbeeld ziet u een HTTP PUT-aanvraag:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
De voorgaande code is vergelijkbaar met het POST-voorbeeld. De SaveItemAsync methode roept PutAsync in plaats van PostAsync.
In het volgende voorbeeld ziet u een HTTP DELETE-aanvraag:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponse.EnsureSuccessStatusCode();
}
In de voorgaande code roept DeleteItemAsyncde methode aanDeleteAsync. Omdat HTTP DELETE-aanvragen doorgaans geen hoofdtekst bevatten, biedt de DeleteAsync methode geen overbelasting die een exemplaar accepteert.HttpContent
Zie HttpClientvoor meer informatie over het gebruik van verschillende HTTP-woorden met HttpClient.
Middleware voor uitgaande aanvragen
HttpClient heeft het concept van het delegeren van handlers die kunnen worden gekoppeld voor uitgaande HTTP-aanvragen.
IHttpClientFactory:
- Vereenvoudigt het definiëren van de handlers die voor elke benoemde client moeten worden toegepast.
- Ondersteunt registratie en ketening van meerdere handlers voor het bouwen van een middleware-pijplijn voor uitgaande aanvragen. Elk van deze handlers kan werk uitvoeren voor en na de uitgaande aanvraag. Dit patroon:
- Is vergelijkbaar met de binnenkomende middleware-pijplijn in ASP.NET Core.
- Biedt een mechanisme voor het beheren van kruislingse problemen rond HTTP-aanvragen, zoals:
- caching
- error handling
- serialization
- logging
Een delegerende handler maken:
- Afgeleid van DelegatingHandler.
- Overschrijf SendAsync. Voer code uit voordat u de aanvraag doorgeeft aan de volgende handler in de pijplijn:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Met de voorgaande code wordt gecontroleerd of de X-API-KEY header zich in de aanvraag bevindt. Als X-API-KEY ontbreekt, wordt BadRequest geretourneerd.
Meer dan één handler kan worden toegevoegd aan de configuratie voor een HttpClient met Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5001/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
// Remaining code deleted for brevity.
In de voorgaande code wordt de ValidateHeaderHandler code geregistreerd bij DI. Zodra geregistreerd, kan AddHttpMessageHandler worden aangeroepen, waarbij het type voor de handler wordt doorgegeven.
Meerdere handlers kunnen worden geregistreerd in de volgorde waarin ze moeten worden uitgevoerd. Elke handler verpakt de volgende handler totdat de laatste HttpClientHandler de aanvraag uitvoert:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
DI gebruiken in middleware voor uitgaande aanvragen
Wanneer IHttpClientFactory er een nieuwe delegeringshandler wordt gemaakt, wordt DI gebruikt om te voldoen aan de constructorparameters van de handler.
IHttpClientFactory maakt een afzonderlijk DI-bereik voor elke handler, wat kan leiden tot verrassend gedrag wanneer een handler een scoped service verbruikt.
Denk bijvoorbeeld aan de volgende interface en de implementatie, die een taak vertegenwoordigt als een bewerking met een id: OperationId
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Zoals de naam al aangeeft, wordt IOperationScoped geregistreerd bij DI met behulp van een scoped levensduur:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
De volgende delegerende handler verbruikt en gebruikt IOperationScoped om de X-OPERATION-ID header voor de uitgaande aanvraag in te stellen:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
Navigeer in het HttpRequestsSample downloaden naar /Operation en vernieuw deze. De waarde van het aanvraagbereik wordt voor elke aanvraag gewijzigd, maar de scopewaarde van de handler wordt slechts elke 5 seconden gewijzigd.
Handlers kunnen gebruikmaken van services van enige omvang. Services waarop handlers afhankelijk zijn, worden verwijderd wanneer de handler wordt verwijderd.
Gebruik een van de volgende methoden om de status per aanvraag te delen met berichthandlers:
- Geef gegevens door aan de handler met behulp van HttpRequestMessage.Options.
- Gebruiken IHttpContextAccessor om toegang te krijgen tot de huidige aanvraag.
- Maak een aangepast AsyncLocal<T> opslagobject om de gegevens door te geven.
Op Polly gebaseerde handlers gebruiken
IHttpClientFactory kan worden geïntegreerd met de bibliotheek van derden Polly. Polly is een uitgebreide bibliotheek voor tolerantie en tijdelijke foutafhandeling voor .NET. Hiermee kunnen ontwikkelaars beleid uitdrukken, zoals Retry, Circuit Breaker, Timeout, Bulkhead Isolation en Fallback op een vloeiende en thread-veilige manier.
Extensiemethoden worden geboden om het gebruik van Polly-beleidsregels met geconfigureerde HttpClient exemplaren in te schakelen. De Polly-extensies ondersteunen het toevoegen van op Polly gebaseerde handlers aan clients. Polly vereist het Microsoft.Extensions.Http.Polly NuGet-pakket.
Tijdelijke fouten afhandelen
Fouten treden meestal op wanneer externe HTTP-aanroepen tijdelijk zijn.
AddTransientHttpErrorPolicy hiermee kan een beleid worden gedefinieerd voor het afhandelen van tijdelijke fouten. Beleidsregels die zijn geconfigureerd met AddTransientHttpErrorPolicy de volgende antwoorden verwerken:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy biedt toegang tot een PolicyBuilder object dat is geconfigureerd voor het afhandelen van fouten die een mogelijke tijdelijke fout vertegenwoordigen:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
In de voorgaande code wordt een WaitAndRetryAsync beleid gedefinieerd. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd met een vertraging van 600 ms tussen pogingen.
Dynamisch beleid selecteren
Extensiemethoden worden geleverd om bijvoorbeeld AddPolicyHandlerop Polly gebaseerde handlers toe te voegen. De volgende AddPolicyHandler overload inspecteert het verzoek om te bepalen welk beleid moet worden toegepast.
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
Als de uitgaande aanvraag een HTTP GET is, wordt in de voorgaande code een time-out van 10 seconden toegepast. Voor elke andere HTTP-methode wordt een time-out van 30 seconden gebruikt.
Meerdere Polly-handlers toevoegen
Het is gebruikelijk om Polly-beleid te nesten:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
In het voorgaande voorbeeld:
- Er worden twee handlers toegevoegd.
- De eerste handler gebruikt AddTransientHttpErrorPolicy om een beleid voor opnieuw proberen toe te voegen. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd.
- Met de tweede
AddTransientHttpErrorPolicyaanroep wordt een beleid voor circuitonderbrekers toegevoegd. Verdere externe aanvragen worden 30 seconden geblokkeerd als 5 mislukte pogingen opeenvolgend plaatsvinden. Circuitonderbreker beleid is toestandsafhankelijk. Alle aanroepen via deze client delen dezelfde circuitstatus.
Beleid toevoegen uit het Polly-register
Een benadering voor het beheren van regelmatig gebruikte beleidsregels is om ze eenmaal te definiëren en te registreren bij een PolicyRegistry.
In de volgende code:
- De beleidsregels 'normaal' en 'lang' worden toegevoegd.
- AddPolicyHandlerFromRegistry voegt de beleidsregels 'regular' en 'long' toe uit het register.
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
Zie de IHttpClientFactory voor meer informatie over en Polly-integraties.
HttpClient en levensduurbeheer
Er wordt telkens een nieuw HttpClient exemplaar geretourneerd wanneer CreateClient deze IHttpClientFactorywordt aangeroepen. Een HttpMessageHandler wordt gecreëerd per benoemde cliënt. De fabriek beheert de levensduur van de HttpMessageHandler exemplaren.
IHttpClientFactory voegt de instanties van HttpMessageHandler die door de fabriek zijn gecreëerd samen om het gebruik van middelen te verminderen. Een HttpMessageHandler exemplaar kan opnieuw worden gebruikt vanuit de pool bij het maken van een nieuw HttpClient exemplaar als de levensduur ervan niet is verlopen.
Pooling van handlers is wenselijk omdat elke handler doorgaans zijn eigen onderliggende HTTP-verbindingen beheert. Het maken van meer handlers dan nodig is, kan leiden tot verbindingsvertragingen. Sommige handlers houden verbindingen ook voor onbepaalde tijd open, waardoor de handler niet kan reageren op DNS-wijzigingen (Domain Name System).
De standaardlevensduur van de handler is twee minuten. De standaardwaarde kan per benoemde client worden overschreven:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
HttpClient exemplaren kunnen over het algemeen worden behandeld als .NET-objecten die geen verwijdering vereisen. Opruimen annuleert uitgaande aanvragen en garandeert dat de gegeven HttpClient instantie na gebruik van Dispose niet meer kan worden gebruikt.
IHttpClientFactory houdt de resources bij en verwijdert deze die door exemplaren van HttpClient worden gebruikt.
Het levend houden van één HttpClient exemplaar voor een lange duur is een veelgebruikte methode voordat de opkomst van IHttpClientFactory. Dit patroon wordt overbodig na de migratie naar IHttpClientFactory.
Alternatieven voor IHttpClientFactory
Door gebruik te maken van IHttpClientFactory in een DI-ingeschakelde applicatie worden de volgende zaken vermeden:
- Problemen met uitputting van middelen door het groeperen van
HttpMessageHandlerinstanties. - Verouderde DNS-problemen oplossen door
HttpMessageHandlerinstanties regelmatig te rouleren.
Er zijn alternatieve manieren om de voorgaande problemen op te lossen met behulp van een SocketsHttpHandler-exemplaar met een lange levensduur.
- Maak een exemplaar van
SocketsHttpHandlerwanneer de app wordt gestart en gebruik deze voor de levensduur van de app. - Configureer PooledConnectionLifetime naar een geschikte waarde op basis van DNS-vernieuwingstijden.
- Maak
HttpClient-instanties zo nodig metnew HttpClient(handler, disposeHandler: false).
Met de voorgaande benaderingen worden de problemen met resourcebeheer op een vergelijkbare manier opgelost als IHttpClientFactory.
- De
SocketsHttpHandlerdeelt verbindingen tussenHttpClientexemplaren. Dit delen voorkomt uitputting van sockets. - De
SocketsHttpHandlercyclet verbindingen volgensPooledConnectionLifetimeom verouderde DNS-problemen te voorkomen.
Cookies
De gegroepeerde HttpMessageHandler exemplaren resulteren in CookieContainer gedeelde objecten.
CookieContainer Onverwacht object delen leidt vaak tot onjuiste code. Voor apps waarvoor cookies zijn vereist, kunt u het volgende overwegen:
- cookie Automatische verwerking uitschakelen
- Het vermijden van
IHttpClientFactory
Aanroep ConfigurePrimaryHttpMessageHandler om automatische cookie verwerking uit te schakelen:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Logging
Clients die zijn gemaakt via IHttpClientFactory leggen logboekberichten vast voor alle verzoeken. Schakel het juiste informatieniveau in de logboekconfiguratie in om de standaardlogboekberichten te zien. Aanvullende logboeken, zoals de logboeken van aanvraagheaders, worden alleen opgenomen op trace-niveau.
De logboekcategorie die voor elke client wordt gebruikt, bevat de naam van de client. Een client met de naam MyNamedClient registreert bijvoorbeeld berichten met een categorie System.Net.Http.HttpClient. MyNamedClient. LogicalHandler". Berichten met het achtervoegsel LogicalHandler komen voor buiten de pijplijn van de aanvraaghandler. Op de aanvraag worden berichten geregistreerd voordat andere handlers in de pijplijn deze hebben verwerkt. Bij het antwoord worden berichten geregistreerd nadat andere pipeline-handlers het antwoord hebben ontvangen.
Logboekregistratie vindt ook plaats in de pijplijn van de aanvraaghandler. In het voorbeeld van MyNamedClient worden deze berichten vastgelegd met de logboekcategorie System.Net.Http.HttpClient. MyNamedClient. ClientHandler". Voor de aanvraag gebeurt dit nadat alle andere handlers zijn uitgevoerd en direct voordat de aanvraag wordt verzonden. In het antwoord bevat deze logboekregistratie de status van het antwoord voordat deze wordt doorgestuurd via de handler-pijplijn.
Door logboekregistratie buiten en binnen de pijplijn in te schakelen, kunnen de wijzigingen die door de andere pijplijnhandlers zijn aangebracht, worden gecontroleerd. Dit kunnen wijzigingen in aanvraagheaders of de antwoordstatuscode zijn.
Als u de naam van de client in de logboekcategorie opvoegt, kunt u logboekfiltering voor specifieke benoemde clients inschakelen.
HttpMessageHandler configureren
Het kan nodig zijn om de configuratie van de binnenste HttpMessageHandler te beheren die door een client wordt gebruikt.
Er IHttpClientBuilder wordt een geretourneerd bij het toevoegen van benoemde of getypte clients. De ConfigurePrimaryHttpMessageHandler extensiemethode kan worden gebruikt om een gemachtigde te definiëren. De gemachtigde wordt gebruikt voor het maken en configureren van de primaire die HttpMessageHandler door die client wordt gebruikt:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
IHttpClientFactory gebruiken in een console-app
Voeg in een console-app de volgende pakketverwijzingen toe aan het project:
In het volgende voorbeeld:
- IHttpClientFactory is geregistreerd in de servicecontainer van de Generic Host.
-
MyServicemaakt een client factory-exemplaar van de service, die wordt gebruikt voor het maken van eenHttpClient.HttpClientwordt gebruikt om een webpagina op te halen. -
Maincreëert een scope om deGetPagemethode van de service uit te voeren en om de eerste 500 tekens van de inhoud van de webpagina naar de console te schrijven.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware voor headerdoorgifte
Headerdoorgifte is een ASP.NET Core-middleware om HTTP-headers van de binnenkomende aanvraag door te geven aan de uitgaande HTTP-clientaanvragen. Koptekst propagatie gebruiken:
Verwijs naar het Microsoft.AspNetCore.HeaderPropagation-pakket .
Configureer de middleware en
HttpClientinStartup:public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }De client bevat de geconfigureerde headers voor uitgaande aanvragen:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Aanvullende bronnen
Door Kirk Larkin, Steve Gordon, Glenn Condron en Ryan Nowak.
Een IHttpClientFactory kan worden geregistreerd en gebruikt voor het configureren en maken van HttpClient exemplaren in een app.
IHttpClientFactory biedt de volgende voordelen:
- Biedt een centrale locatie voor het benoemen en configureren van logische
HttpClientinstanties. Een client met de naam github kan bijvoorbeeld worden geregistreerd en geconfigureerd voor toegang tot GitHub. Een standaardclient kan worden geregistreerd voor algemene toegang. - Codifeert het concept van uitgaande middleware via het delegeren van handlers in
HttpClient. Biedt uitbreidingen voor op Polly gebaseerde middleware om te profiteren van het delegeren van handlers inHttpClient. - Beheert de pooling en levensduur van onderliggende
HttpClientMessageHandlerexemplaren. Automatisch beheer voorkomt veelvoorkomende DNS-problemen (Domain Name System) die optreden bij het handmatig beheren vanHttpClientlevensduur. - Voegt een configureerbare logboekregistratie-ervaring (via
ILogger) toe voor alle aanvragen die worden verzonden via clients die door de fabriek zijn gemaakt.
Voorbeeldcode bekijken of downloaden (hoe u kunt downloaden).
De voorbeeldcode in deze onderwerpversie gebruikt System.Text.Json voor het deserialiseren van JSON-inhoud die wordt geretourneerd in HTTP-antwoorden. Voor voorbeelden die gebruikmaken van Json.NET en ReadAsAsync<T>, gebruik de versiekiezer om een 2.x-versie van dit onderwerp te selecteren.
Consumptiepatronen
Er zijn verschillende manieren IHttpClientFactory om in een app te gebruiken:
De beste aanpak is afhankelijk van de vereisten van de app.
Basaal gebruik
IHttpClientFactory kan worden geregistreerd door te bellen AddHttpClient:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// Remaining code deleted for brevity.
Een IHttpClientFactory kan worden aangevraagd met behulp van afhankelijkheidsinjectie (DI). De volgende code gebruikt IHttpClientFactory om een HttpClient exemplaar te maken:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
Branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Het gebruik van IHttpClientFactory zoals in het voorgaande voorbeeld is een goede manier om een bestaande app te herstructureren. Het heeft geen invloed op hoe HttpClient wordt gebruikt. Op plaatsen waar HttpClient exemplaren worden gemaakt in een bestaande app, vervangt u deze exemplaren door aanroepen naar CreateClient.
Benoemde clients
Benoemde clients zijn een goede keuze wanneer:
- Voor de app zijn veel verschillende toepassingen van
HttpClientvereist. - Veel
HttpClients hebben een andere configuratie.
Configuratie voor een genummerde HttpClient kan tijdens de registratie in Startup.ConfigureServices worden opgegeven.
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
In de voorgaande code is de client geconfigureerd met:
- Het basisadres
https://api.github.com/. - Er zijn twee headers vereist om te werken met de GitHub-API.
CreateClient
Elke keer CreateClient wordt het volgende aangeroepen:
- Er wordt een nieuw exemplaar gemaakt
HttpClient. - De configuratieactie wordt aangeroepen.
Als u een benoemde client wilt maken, geeft u de naam door in CreateClient:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
In de voorgaande code hoeft de aanvraag geen hostnaam op te geven. De code kan alleen het pad doorgeven, omdat het basisadres dat is geconfigureerd voor de client wordt gebruikt.
Getypte clients
Getypte clients:
- Bied dezelfde mogelijkheden als benoemde clients zonder dat u tekenreeksen als sleutels hoeft te gebruiken.
- Biedt IntelliSense en compilerhulp voor het gebruik van cliënten.
- Geef één locatie op om een bepaalde
HttpClientlocatie te configureren en ermee te communiceren. Er kan bijvoorbeeld één getypeerde client worden gebruikt:- Voor één back-endeindpunt.
- Als u alle logica voor het eindpunt wilt inkapselen.
- Werk met DI en kan waar nodig worden geïnjecteerd in de app.
Een getypte client accepteert een HttpClient parameter in de constructor:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
"/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubIssue>>(responseStream);
}
}
Als u codeopmerkingen wilt zien die zijn vertaald naar andere talen dan Engels, laat het ons dan weten in dit GitHub-discussieprobleem.
In de voorgaande code:
- De configuratie wordt verplaatst naar de getypte client.
- Het
HttpClientobject wordt weergegeven als een openbare eigenschap.
API-specifieke methoden kunnen worden gemaakt die functionaliteit beschikbaar HttpClient maken. Met de GetAspNetDocsIssues methode wordt bijvoorbeeld code ingekapseld om openstaande problemen op te halen.
De volgende code roept AddHttpClient aan binnen Startup.ConfigureServices om een getypte clientklasse te registreren.
services.AddHttpClient<GitHubService>();
De getypte client wordt geregistreerd als tijdelijk bij DI. In de voorgaande code AddHttpClient wordt geregistreerd GitHubService als een tijdelijke service. Deze registratie maakt gebruik van een factory-methode voor het volgende:
- Maak een exemplaar van
HttpClient. - Maak een exemplaar van
GitHubService, door te geven in het exemplaar vanHttpClientde constructor.
De getypte client kan worden geïnjecteerd en direct gebruikt.
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
De configuratie voor een getypte client kan worden opgegeven tijdens de registratie in Startup.ConfigureServices, in plaats van in de constructor van de getypte client:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
De HttpClient kan worden ingekapseld in een getypte client. In plaats van deze als eigenschap weer te geven, definieert u een methode waarmee het HttpClient exemplaar intern wordt aangeroepen:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<string>>(responseStream);
}
}
In de voorgaande code wordt de HttpClient code opgeslagen in een privéveld. Toegang tot de HttpClient is via de openbare GetRepos methode.
Gegenereerde clients
IHttpClientFactory kan worden gebruikt in combinatie met bibliotheken van derden, zoals Refit. Refit is een REST bibliotheek voor .NET. Het converteert REST API's naar live-interfaces. Er wordt dynamisch een implementatie van de interface gegenereerd door de RestService, waarbij HttpClient de externe HTTP-aanroepen worden uitgevoerd.
Een interface en een antwoord worden gedefinieerd om de externe API en het bijbehorende antwoord weer te geven:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Een getypte client kan worden toegevoegd met Behulp van Refit om de implementatie te genereren:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddControllers();
}
De gedefinieerde interface kan waar nodig worden gebruikt, waarbij DI en Refit de implementatie leveren.
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
POST-, PUT- en DELETE-aanvragen maken
In de voorgaande voorbeelden gebruiken alle HTTP-aanvragen het GET HTTP-werkwoord.
HttpClient ondersteunt ook andere HTTP-woorden, waaronder:
- POST
- PUT
- DELETE
- PATCH
Zie voor een volledige lijst met ondersteunde HTTP-woorden HttpMethod.
In het volgende voorbeeld ziet u hoe u een HTTP POST-aanvraag maakt:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
In de voorgaande code is de CreateItemAsync methode:
- Serialiseert de
TodoItemparameter naar JSON met behulp vanSystem.Text.Json. Dit maakt gebruik van een exemplaar van JsonSerializerOptions het serialisatieproces te configureren. - Hiermee maakt u een exemplaar van StringContent het pakket van de geserialiseerde JSON voor verzending in de hoofdtekst van de HTTP-aanvraag.
- Aanroepen PostAsync om de JSON-inhoud naar de opgegeven URL te verzenden. Dit is een relatieve URL die wordt toegevoegd aan het HttpClient.BaseAddress.
- Aanroepen EnsureSuccessStatusCode om een uitzondering te genereren als de antwoordstatuscode niet aangeeft dat deze is geslaagd.
HttpClient ondersteunt ook andere typen inhoud. Bijvoorbeeld MultipartContent en StreamContent. Zie voor een volledige lijst met ondersteunde inhoud HttpContent.
In het volgende voorbeeld ziet u een HTTP PUT-aanvraag:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
De voorgaande code is vergelijkbaar met het POST-voorbeeld. De SaveItemAsync methode roept PutAsync in plaats van PostAsync.
In het volgende voorbeeld ziet u een HTTP DELETE-aanvraag:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponse.EnsureSuccessStatusCode();
}
In de voorgaande code roept DeleteItemAsyncde methode aanDeleteAsync. Omdat HTTP DELETE-aanvragen doorgaans geen hoofdtekst bevatten, biedt de DeleteAsync methode geen overbelasting die een exemplaar accepteert.HttpContent
Zie HttpClientvoor meer informatie over het gebruik van verschillende HTTP-woorden met HttpClient.
Middleware voor uitgaande aanvragen
HttpClient heeft het concept van het delegeren van handlers die kunnen worden gekoppeld voor uitgaande HTTP-aanvragen.
IHttpClientFactory:
- Vereenvoudigt het definiëren van de handlers die voor elke benoemde client moeten worden toegepast.
- Ondersteunt registratie en ketening van meerdere handlers voor het bouwen van een middleware-pijplijn voor uitgaande aanvragen. Elk van deze handlers kan werk uitvoeren voor en na de uitgaande aanvraag. Dit patroon:
- Is vergelijkbaar met de binnenkomende middleware-pijplijn in ASP.NET Core.
- Biedt een mechanisme voor het beheren van kruislingse problemen rond HTTP-aanvragen, zoals:
- caching
- error handling
- serialization
- logging
Een delegerende handler maken:
- Afgeleid van DelegatingHandler.
- Overschrijf SendAsync. Voer code uit voordat u de aanvraag doorgeeft aan de volgende handler in de pijplijn:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Met de voorgaande code wordt gecontroleerd of de X-API-KEY header zich in de aanvraag bevindt. Als X-API-KEY ontbreekt, wordt BadRequest geretourneerd.
Meer dan één handler kan worden toegevoegd aan de configuratie voor een HttpClient met Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5001/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
// Remaining code deleted for brevity.
In de voorgaande code wordt de ValidateHeaderHandler code geregistreerd bij DI. Zodra geregistreerd, kan AddHttpMessageHandler worden aangeroepen, waarbij het type voor de handler wordt doorgegeven.
Meerdere handlers kunnen worden geregistreerd in de volgorde waarin ze moeten worden uitgevoerd. Elke handler verpakt de volgende handler totdat de laatste HttpClientHandler de aanvraag uitvoert:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
DI gebruiken in middleware voor uitgaande aanvragen
Wanneer IHttpClientFactory er een nieuwe delegeringshandler wordt gemaakt, wordt DI gebruikt om te voldoen aan de constructorparameters van de handler.
IHttpClientFactory maakt een afzonderlijk DI-bereik voor elke handler, wat kan leiden tot verrassend gedrag wanneer een handler een scoped service verbruikt.
Denk bijvoorbeeld aan de volgende interface en de implementatie, die een taak vertegenwoordigt als een bewerking met een id: OperationId
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Zoals de naam al aangeeft, wordt IOperationScoped geregistreerd bij DI met behulp van een scoped levensduur:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
De volgende delegerende handler verbruikt en gebruikt IOperationScoped om de X-OPERATION-ID header voor de uitgaande aanvraag in te stellen:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
Navigeer in het HttpRequestsSample downloaden naar /Operation en vernieuw deze. De waarde van het aanvraagbereik wordt voor elke aanvraag gewijzigd, maar de scopewaarde van de handler wordt slechts elke 5 seconden gewijzigd.
Handlers kunnen gebruikmaken van services van enige omvang. Services waarop handlers afhankelijk zijn, worden verwijderd wanneer de handler wordt verwijderd.
Gebruik een van de volgende methoden om de status per aanvraag te delen met berichthandlers:
- Geef gegevens door aan de handler met behulp van HttpRequestMessage.Properties.
- Gebruiken IHttpContextAccessor om toegang te krijgen tot de huidige aanvraag.
- Maak een aangepast AsyncLocal<T> opslagobject om de gegevens door te geven.
Op Polly gebaseerde handlers gebruiken
IHttpClientFactory kan worden geïntegreerd met de bibliotheek van derden Polly. Polly is een uitgebreide bibliotheek voor tolerantie en tijdelijke foutafhandeling voor .NET. Hiermee kunnen ontwikkelaars beleid uitdrukken, zoals Retry, Circuit Breaker, Timeout, Bulkhead Isolation en Fallback op een vloeiende en thread-veilige manier.
Extensiemethoden worden geboden om het gebruik van Polly-beleidsregels met geconfigureerde HttpClient exemplaren in te schakelen. De Polly-extensies ondersteunen het toevoegen van op Polly gebaseerde handlers aan clients. Polly vereist het Microsoft.Extensions.Http.Polly NuGet-pakket.
Tijdelijke fouten afhandelen
Fouten treden meestal op wanneer externe HTTP-aanroepen tijdelijk zijn.
AddTransientHttpErrorPolicy hiermee kan een beleid worden gedefinieerd voor het afhandelen van tijdelijke fouten. Beleidsregels die zijn geconfigureerd met AddTransientHttpErrorPolicy de volgende antwoorden verwerken:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy biedt toegang tot een PolicyBuilder object dat is geconfigureerd voor het afhandelen van fouten die een mogelijke tijdelijke fout vertegenwoordigen:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
In de voorgaande code wordt een WaitAndRetryAsync beleid gedefinieerd. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd met een vertraging van 600 ms tussen pogingen.
Dynamisch beleid selecteren
Extensiemethoden worden geleverd om bijvoorbeeld AddPolicyHandlerop Polly gebaseerde handlers toe te voegen. De volgende AddPolicyHandler overload inspecteert het verzoek om te bepalen welk beleid moet worden toegepast.
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
Als de uitgaande aanvraag een HTTP GET is, wordt in de voorgaande code een time-out van 10 seconden toegepast. Voor elke andere HTTP-methode wordt een time-out van 30 seconden gebruikt.
Meerdere Polly-handlers toevoegen
Het is gebruikelijk om Polly-beleid te nesten:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
In het voorgaande voorbeeld:
- Er worden twee handlers toegevoegd.
- De eerste handler gebruikt AddTransientHttpErrorPolicy om een beleid voor opnieuw proberen toe te voegen. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd.
- Met de tweede
AddTransientHttpErrorPolicyaanroep wordt een beleid voor circuitonderbrekers toegevoegd. Verdere externe aanvragen worden 30 seconden geblokkeerd als 5 mislukte pogingen opeenvolgend plaatsvinden. Circuitonderbreker beleid is toestandsafhankelijk. Alle aanroepen via deze client delen dezelfde circuitstatus.
Beleid toevoegen uit het Polly-register
Een benadering voor het beheren van regelmatig gebruikte beleidsregels is om ze eenmaal te definiëren en te registreren bij een PolicyRegistry.
In de volgende code:
- De beleidsregels 'normaal' en 'lang' worden toegevoegd.
- AddPolicyHandlerFromRegistry voegt de beleidsregels 'regular' en 'long' toe uit het register.
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
Zie de IHttpClientFactory voor meer informatie over en Polly-integraties.
HttpClient en levensduurbeheer
Er wordt telkens een nieuw HttpClient exemplaar geretourneerd wanneer CreateClient deze IHttpClientFactorywordt aangeroepen. Een HttpMessageHandler wordt gecreëerd per benoemde cliënt. De fabriek beheert de levensduur van de HttpMessageHandler exemplaren.
IHttpClientFactory voegt de instanties van HttpMessageHandler die door de fabriek zijn gecreëerd samen om het gebruik van middelen te verminderen. Een HttpMessageHandler exemplaar kan opnieuw worden gebruikt vanuit de pool bij het maken van een nieuw HttpClient exemplaar als de levensduur ervan niet is verlopen.
Pooling van handlers is wenselijk omdat elke handler doorgaans zijn eigen onderliggende HTTP-verbindingen beheert. Het maken van meer handlers dan nodig is, kan leiden tot verbindingsvertragingen. Sommige handlers houden verbindingen ook voor onbepaalde tijd open, waardoor de handler niet kan reageren op DNS-wijzigingen (Domain Name System).
De standaardlevensduur van de handler is twee minuten. De standaardwaarde kan per benoemde client worden overschreven:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
HttpClient exemplaren kunnen over het algemeen worden behandeld als .NET-objecten die geen verwijdering vereisen. Opruimen annuleert uitgaande aanvragen en garandeert dat de gegeven HttpClient instantie na gebruik van Dispose niet meer kan worden gebruikt.
IHttpClientFactory houdt de resources bij en verwijdert deze die door exemplaren van HttpClient worden gebruikt.
Het levend houden van één HttpClient exemplaar voor een lange duur is een veelgebruikte methode voordat de opkomst van IHttpClientFactory. Dit patroon wordt overbodig na de migratie naar IHttpClientFactory.
Alternatieven voor IHttpClientFactory
Door gebruik te maken van IHttpClientFactory in een DI-ingeschakelde applicatie worden de volgende zaken vermeden:
- Problemen met uitputting van middelen door het groeperen van
HttpMessageHandlerinstanties. - Verouderde DNS-problemen oplossen door
HttpMessageHandlerinstanties regelmatig te rouleren.
Er zijn alternatieve manieren om de voorgaande problemen op te lossen met behulp van een SocketsHttpHandler-exemplaar met een lange levensduur.
- Maak een exemplaar van
SocketsHttpHandlerwanneer de app wordt gestart en gebruik deze voor de levensduur van de app. - Configureer PooledConnectionLifetime naar een geschikte waarde op basis van DNS-vernieuwingstijden.
- Maak
HttpClient-instanties zo nodig metnew HttpClient(handler, disposeHandler: false).
Met de voorgaande benaderingen worden de problemen met resourcebeheer op een vergelijkbare manier opgelost als IHttpClientFactory.
- De
SocketsHttpHandlerdeelt verbindingen tussenHttpClientexemplaren. Dit delen voorkomt uitputting van sockets. - De
SocketsHttpHandlercyclet verbindingen volgensPooledConnectionLifetimeom verouderde DNS-problemen te voorkomen.
Cookies
De gegroepeerde HttpMessageHandler exemplaren resulteren in CookieContainer gedeelde objecten.
CookieContainer Onverwacht object delen leidt vaak tot onjuiste code. Voor apps waarvoor cookies zijn vereist, kunt u het volgende overwegen:
- cookie Automatische verwerking uitschakelen
- Het vermijden van
IHttpClientFactory
Aanroep ConfigurePrimaryHttpMessageHandler om automatische cookie verwerking uit te schakelen:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Logging
Clients die zijn gemaakt via IHttpClientFactory leggen logboekberichten vast voor alle verzoeken. Schakel het juiste informatieniveau in de logboekconfiguratie in om de standaardlogboekberichten te zien. Aanvullende logboeken, zoals de logboeken van aanvraagheaders, worden alleen opgenomen op trace-niveau.
De logboekcategorie die voor elke client wordt gebruikt, bevat de naam van de client. Een client met de naam MyNamedClient registreert bijvoorbeeld berichten met een categorie System.Net.Http.HttpClient. MyNamedClient. LogicalHandler". Berichten met het achtervoegsel LogicalHandler komen voor buiten de pijplijn van de aanvraaghandler. Op de aanvraag worden berichten geregistreerd voordat andere handlers in de pijplijn deze hebben verwerkt. Bij het antwoord worden berichten geregistreerd nadat andere pipeline-handlers het antwoord hebben ontvangen.
Logboekregistratie vindt ook plaats in de pijplijn van de aanvraaghandler. In het voorbeeld van MyNamedClient worden deze berichten vastgelegd met de logboekcategorie System.Net.Http.HttpClient. MyNamedClient. ClientHandler". Voor de aanvraag gebeurt dit nadat alle andere handlers zijn uitgevoerd en direct voordat de aanvraag wordt verzonden. In het antwoord bevat deze logboekregistratie de status van het antwoord voordat deze wordt doorgestuurd via de handler-pijplijn.
Door logboekregistratie buiten en binnen de pijplijn in te schakelen, kunnen de wijzigingen die door de andere pijplijnhandlers zijn aangebracht, worden gecontroleerd. Dit kunnen wijzigingen in aanvraagheaders of de antwoordstatuscode zijn.
Als u de naam van de client in de logboekcategorie opvoegt, kunt u logboekfiltering voor specifieke benoemde clients inschakelen.
HttpMessageHandler configureren
Het kan nodig zijn om de configuratie van de binnenste HttpMessageHandler te beheren die door een client wordt gebruikt.
Er IHttpClientBuilder wordt een geretourneerd bij het toevoegen van benoemde of getypte clients. De ConfigurePrimaryHttpMessageHandler extensiemethode kan worden gebruikt om een gemachtigde te definiëren. De gemachtigde wordt gebruikt voor het maken en configureren van de primaire die HttpMessageHandler door die client wordt gebruikt:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
IHttpClientFactory gebruiken in een console-app
Voeg in een console-app de volgende pakketverwijzingen toe aan het project:
In het volgende voorbeeld:
- IHttpClientFactory is geregistreerd in de servicecontainer van de Generic Host.
-
MyServicemaakt een client factory-exemplaar van de service, die wordt gebruikt voor het maken van eenHttpClient.HttpClientwordt gebruikt om een webpagina op te halen. -
Maincreëert een scope om deGetPagemethode van de service uit te voeren en om de eerste 500 tekens van de inhoud van de webpagina naar de console te schrijven.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware voor headerdoorgifte
Headerdoorgifte is een ASP.NET Core-middleware om HTTP-headers van de binnenkomende aanvraag door te geven aan de uitgaande HTTP-clientaanvragen. Koptekst propagatie gebruiken:
Verwijs naar het Microsoft.AspNetCore.HeaderPropagation-pakket .
Configureer de middleware en
HttpClientinStartup:public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }De client bevat de geconfigureerde headers voor uitgaande aanvragen:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Aanvullende bronnen
Door Glenn Condron, Ryan Nowak en Steve Gordon
Een IHttpClientFactory kan worden geregistreerd en gebruikt voor het configureren en maken van HttpClient exemplaren in een app. Het biedt de volgende voordelen:
- Biedt een centrale locatie voor het benoemen en configureren van logische
HttpClientinstanties. Een github-client kan bijvoorbeeld worden geregistreerd en geconfigureerd voor toegang tot GitHub. Een standaardclient kan worden geregistreerd voor andere doeleinden. - Codifeert het concept van uitgaande middleware via het delegeren van handlers in
HttpClienten biedt uitbreidingen voor op Polly gebaseerde middleware om hiervan te profiteren. - Beheert de pooling en levensduur van onderliggende
HttpClientMessageHandlerexemplaren om veelvoorkomende DNS-problemen te voorkomen die optreden bij het handmatig beheren van de levensduur vanHttpClient. - Voegt een configureerbare logboekregistratie-ervaring (via
ILogger) toe voor alle aanvragen die worden verzonden via clients die door de fabriek zijn gemaakt.
Voorbeeldcode bekijken of downloaden (hoe download je)
Prerequisites
Voor projecten die zijn gericht op .NET Framework, moet het Microsoft.Extensions.Http NuGet-pakket worden geïnstalleerd. Projecten die gericht zijn op .NET Core en verwijzen naar de Microsoft.AspNetCore.App metapackage bevatten al het Microsoft.Extensions.Http pakket.
Consumptiepatronen
Er zijn verschillende manieren IHttpClientFactory om in een app te gebruiken:
Geen van hen zijn strikt boven elkaar. De beste aanpak is afhankelijk van de beperkingen van de app.
Basaal gebruik
De IHttpClientFactory kan worden geregistreerd door de AddHttpClient extensiemethode aan te roepen op de IServiceCollection, binnen de Startup.ConfigureServices methode.
services.AddHttpClient();
Zodra geregistreerd, kan code een IHttpClientFactory overal accepteren waar services kunnen worden geïnjecteerd met behulp van afhankelijkheidsinjectie (DI). Deze IHttpClientFactory kan worden gebruikt om een HttpClient exemplaar te maken:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
Branches = await response.Content
.ReadAsAsync<IEnumerable<GitHubBranch>>();
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Het gebruik IHttpClientFactory op deze manier is een goede manier om een bestaande app te herstructureren. Het heeft geen invloed op de manier die HttpClient wordt gebruikt. Op plaatsen waar momenteel HttpClient-instanties worden gemaakt, vervang deze door een aanroep naar CreateClient.
Benoemde clients
Als een app veel verschillende toepassingen vereist, waarbij HttpClient ieder een andere configuratie heeft, kunt u de optie gebruiken om benoemde clients in te zetten. De configuratie voor een benoemde HttpClient kan worden opgegeven tijdens de registratie in Startup.ConfigureServices.
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
In de voorgaande code AddHttpClient wordt de naam github opgegeven. Voor deze client is een aantal standaardconfiguraties toegepast, namelijk het basisadres en twee headers die nodig zijn om te werken met de GitHub-API.
Telkens wanneer CreateClient wordt aangeroepen, wordt er een nieuw exemplaar van HttpClient gemaakt en wordt de configuratieactie aangeroepen.
Als u een benoemde client wilt gebruiken, kan een tekenreeksparameter worden doorgegeven aan CreateClient. Geef de naam op van de client die moet worden gemaakt:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
PullRequests = await response.Content
.ReadAsAsync<IEnumerable<GitHubPullRequest>>();
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
In de voorgaande code hoeft de aanvraag geen hostnaam op te geven. Het kan alleen het pad doorgeven, omdat het basisadres dat is geconfigureerd voor de client wordt gebruikt.
Getypte clients
Getypte clients:
- Bied dezelfde mogelijkheden als benoemde clients zonder dat u tekenreeksen als sleutels hoeft te gebruiken.
- Biedt IntelliSense en compilerhulp voor het gebruik van cliënten.
- Geef één locatie op om een bepaalde
HttpClientlocatie te configureren en ermee te communiceren. Een getypte client kan bijvoorbeeld worden gebruikt voor een enkel backend-eindpunt en de volledige logica voor dat eindpunt inkapselen. - Gebruik DI en injecteer het waar nodig in uw app.
Een getypte client accepteert een HttpClient parameter in de constructor:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
"/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<GitHubIssue>>();
return result;
}
}
In de voorgaande code wordt de configuratie verplaatst naar de getypte client. Het HttpClient object wordt weergegeven als een openbare eigenschap. Het is mogelijk om API-specifieke methoden te definiëren die HttpClient functionaliteit blootleggen. De GetAspNetDocsIssues methode bevat de code die nodig is om de meest recente openstaande problemen op te vragen en te parseren vanuit een GitHub-opslagplaats.
Als u een getypte client wilt registreren, kan de algemene AddHttpClient extensiemethode worden gebruikt binnen Startup.ConfigureServices, waarbij u de getypte clientklasse opgeeft:
services.AddHttpClient<GitHubService>();
De getypte client wordt geregistreerd als tijdelijk bij DI. De getypte client kan worden geïnjecteerd en direct gebruikt.
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
Indien gewenst kan de configuratie voor een getypte client worden opgegeven tijdens de registratie in Startup.ConfigureServices, in plaats van in de constructor van de getypte client:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Het is mogelijk om HttpClient volledig in een getypte client in te kapselen. In plaats van deze als eigenschap beschikbaar te maken, kunnen openbare methoden worden opgegeven die het HttpClient exemplaar intern aanroepen.
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<string>>();
return result;
}
}
In de voorgaande code wordt het HttpClient opgeslagen als een privéveld. Alle toegang voor het maken van externe aanroepen verloopt via de GetRepos methode.
Gegenereerde clients
IHttpClientFactory kan worden gebruikt in combinatie met andere bibliotheken van derden, zoals Refit. Refit is een REST bibliotheek voor .NET. Het converteert REST API's naar live-interfaces. Er wordt dynamisch een implementatie van de interface gegenereerd door de RestService, waarbij HttpClient de externe HTTP-aanroepen worden uitgevoerd.
Een interface en een antwoord worden gedefinieerd om de externe API en het bijbehorende antwoord weer te geven:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Een getypte client kan worden toegevoegd met Behulp van Refit om de implementatie te genereren:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddMvc();
}
De gedefinieerde interface kan waar nodig worden gebruikt, waarbij DI en Refit de implementatie leveren.
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
Middleware voor uitgaande aanvragen
HttpClient heeft al het concept van het delegeren van handlers die kunnen worden gekoppeld voor uitgaande HTTP-aanvragen. Hiermee IHttpClientFactory kunt u eenvoudig de handlers definiëren die voor elke benoemde client moeten worden toegepast. Het biedt ondersteuning voor registratie en ketening van meerdere handlers om een middleware-pijplijn voor uitgaande aanvragen te bouwen. Elk van deze handlers kan werk uitvoeren voor en na de uitgaande aanvraag. Dit patroon is vergelijkbaar met de binnenkomende middleware-pijplijn in ASP.NET Core. Het patroon biedt een mechanisme voor het beheren van kruislingse problemen rond HTTP-aanvragen, waaronder caching, foutafhandeling, serialisatie en logboekregistratie.
Als u een handler wilt maken, definieert u een klasse die is afgeleid van DelegatingHandler. Overschrijf de methode voor het SendAsync uitvoeren van code voordat u de aanvraag doorgeeft aan de volgende handler in de pijplijn:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
De voorgaande code definieert een basishandler. Er wordt gecontroleerd of er een X-API-KEY header is opgenomen in de aanvraag. Als de header ontbreekt, kan deze de HTTP-aanroep voorkomen en een geschikt antwoord retourneren.
Tijdens de registratie kunnen een of meer handlers worden toegevoegd aan de configuratie voor een HttpClient. Deze taak wordt uitgevoerd via extensiemethoden op de IHttpClientBuilder.
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5000/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
In de voorgaande code wordt de ValidateHeaderHandler code geregistreerd bij DI. De handler moet in DI worden geregistreerd als een tijdelijke service, nooit als een gescopeerde service. Als de handler is geregistreerd als een scoped service en alle services waarop de handler afhankelijk is, wegwerpbaar zijn:
- De services van de handler kunnen worden verwijderd voordat de handler buiten het bereik valt.
- De weggegooide handlerservices zorgen ervoor dat de handler mislukt.
Zodra het is geregistreerd, AddHttpMessageHandler kan het worden aangeroepen, waarbij het handlertype wordt doorgegeven.
Meerdere handlers kunnen worden geregistreerd in de volgorde waarin ze moeten worden uitgevoerd. Elke handler verpakt de volgende handler totdat de laatste HttpClientHandler de aanvraag uitvoert:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Gebruik een van de volgende methoden om de status per aanvraag te delen met berichthandlers:
- Geef gegevens door aan de handler met behulp van
HttpRequestMessage.Properties. - Gebruiken
IHttpContextAccessorom toegang te krijgen tot de huidige aanvraag. - Maak een aangepast
AsyncLocalopslagobject om de gegevens door te geven.
Op Polly gebaseerde handlers gebruiken
IHttpClientFactory kan worden geïntegreerd met een populaire bibliotheek van derden met de naam Polly. Polly is een uitgebreide bibliotheek voor tolerantie en tijdelijke foutafhandeling voor .NET. Hiermee kunnen ontwikkelaars beleid uitdrukken, zoals Retry, Circuit Breaker, Timeout, Bulkhead Isolation en Fallback op een vloeiende en thread-veilige manier.
Extensiemethoden worden geboden om het gebruik van Polly-beleidsregels met geconfigureerde HttpClient exemplaren in te schakelen. De Polly-extensies:
- Ondersteuning voor het toevoegen van Polly-gebaseerde handlers aan clients.
- Kan worden gebruikt na het installeren van het Microsoft.Extensions.Http.Polly NuGet-pakket. Het pakket is niet opgenomen in het gedeelde framework ASP.NET Core.
Tijdelijke fouten afhandelen
De meest voorkomende fouten treden op wanneer externe HTTP-aanroepen tijdelijk zijn. Er is een handige extensiemethode AddTransientHttpErrorPolicy opgenomen waarmee een beleid kan worden gedefinieerd voor het afhandelen van tijdelijke fouten. Beleidsregels die zijn geconfigureerd met deze extensiemethode verwerken HttpRequestException, HTTP 5xx-antwoorden en HTTP 408-antwoorden.
De AddTransientHttpErrorPolicy extensie kan worden gebruikt binnen Startup.ConfigureServices. De extensie biedt toegang tot een PolicyBuilder object dat is geconfigureerd voor het afhandelen van fouten die een mogelijke tijdelijke fout vertegenwoordigen:
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
In de voorgaande code wordt een WaitAndRetryAsync beleid gedefinieerd. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd met een vertraging van 600 ms tussen pogingen.
Dynamisch beleid selecteren
Er bestaan aanvullende uitbreidingsmethoden die kunnen worden gebruikt om handlers op basis van Polly toe te voegen. Een dergelijke extensie is AddPolicyHandler, die meerdere overbelastingen heeft. Met één overload functie kan het verzoek worden geïnspecteerd om te bepalen welk beleid moet worden toegepast.
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
Als de uitgaande aanvraag een HTTP GET is, wordt in de voorgaande code een time-out van 10 seconden toegepast. Voor elke andere HTTP-methode wordt een time-out van 30 seconden gebruikt.
Meerdere Polly-handlers toevoegen
Het is gebruikelijk om Polly-beleid te nesten om verbeterde functionaliteit te bieden:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
In het voorgaande voorbeeld worden twee handlers toegevoegd. De eerste gebruikt de AddTransientHttpErrorPolicy extensie om een beleid voor opnieuw proberen toe te voegen. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd. De tweede aanroep voegt een circuitonderbrekerbeleid toe aan AddTransientHttpErrorPolicy. Verdere externe aanvragen worden 30 seconden geblokkeerd als vijf mislukte pogingen opeenvolgend plaatsvinden. Circuitonderbreker beleid is toestandsafhankelijk. Alle aanroepen via deze client delen dezelfde circuitstatus.
Beleid toevoegen uit het Polly-register
Een benadering voor het beheren van regelmatig gebruikte beleidsregels is om ze eenmaal te definiëren en te registreren bij een PolicyRegistry. Er wordt een extensiemethode opgegeven waarmee een handler kan worden toegevoegd met behulp van een beleid uit het register:
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regulartimeouthandler")
.AddPolicyHandlerFromRegistry("regular");
In de voorgaande code worden twee beleidsregels geregistreerd wanneer de PolicyRegistry wordt toegevoegd aan de ServiceCollection. Als u een beleid uit het register wilt gebruiken, wordt de AddPolicyHandlerFromRegistry methode gebruikt, waarbij de naam van het beleid wordt doorgegeven die moet worden toegepast.
Meer informatie over IHttpClientFactory en Polly-integraties vindt u op de Polly-wiki.
HttpClient en levensduurbeheer
Er wordt telkens een nieuw HttpClient exemplaar geretourneerd wanneer CreateClient deze IHttpClientFactorywordt aangeroepen. Er is een HttpMessageHandler per genoemde klant. De fabriek beheert de levensduur van de HttpMessageHandler exemplaren.
IHttpClientFactory voegt de instanties van HttpMessageHandler die door de fabriek zijn gecreëerd samen om het gebruik van middelen te verminderen. Een HttpMessageHandler exemplaar kan opnieuw worden gebruikt vanuit de pool bij het maken van een nieuw HttpClient exemplaar als de levensduur ervan niet is verlopen.
Pooling van handlers is wenselijk omdat elke handler doorgaans zijn eigen onderliggende HTTP-verbindingen beheert. Het maken van meer handlers dan nodig is, kan leiden tot verbindingsvertragingen. Sommige handlers houden verbindingen ook voor onbepaalde tijd open, waardoor de handler niet kan reageren op DNS-wijzigingen.
De standaardlevensduur van de handler is twee minuten. De standaardwaarde kan per benoemde client worden overschreven. Als u deze wilt overschrijven, roept SetHandlerLifetime u de IHttpClientBuilder geretourneerde aan bij het maken van de client:
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Verwijdering van de klant is niet vereist. Opruimen annuleert uitgaande aanvragen en garandeert dat de gegeven HttpClient instantie na gebruik van Dispose niet meer kan worden gebruikt.
IHttpClientFactory houdt de resources bij en verwijdert deze die door exemplaren van HttpClient worden gebruikt. De HttpClient exemplaren kunnen over het algemeen worden behandeld als .NET-objecten die geen verwijdering vereisen.
Het levend houden van één HttpClient exemplaar voor een lange duur is een veelgebruikte methode voordat de opkomst van IHttpClientFactory. Dit patroon wordt overbodig na de migratie naar IHttpClientFactory.
Alternatieven voor IHttpClientFactory
Door gebruik te maken van IHttpClientFactory in een DI-ingeschakelde applicatie worden de volgende zaken vermeden:
- Problemen met uitputting van middelen door het groeperen van
HttpMessageHandlerinstanties. - Verouderde DNS-problemen oplossen door
HttpMessageHandlerinstanties regelmatig te rouleren.
Er zijn alternatieve manieren om de voorgaande problemen op te lossen met behulp van een SocketsHttpHandler-exemplaar met een lange levensduur.
- Maak een exemplaar van
SocketsHttpHandlerwanneer de app wordt gestart en gebruik deze voor de levensduur van de app. - Configureer PooledConnectionLifetime naar een geschikte waarde op basis van DNS-vernieuwingstijden.
- Maak
HttpClient-instanties zo nodig metnew HttpClient(handler, disposeHandler: false).
Met de voorgaande benaderingen worden de problemen met resourcebeheer op een vergelijkbare manier opgelost als IHttpClientFactory.
- De
SocketsHttpHandlerdeelt verbindingen tussenHttpClientexemplaren. Dit delen voorkomt uitputting van sockets. - De
SocketsHttpHandlercyclet verbindingen volgensPooledConnectionLifetimeom verouderde DNS-problemen te voorkomen.
Cookies
De gegroepeerde HttpMessageHandler exemplaren resulteren in CookieContainer gedeelde objecten.
CookieContainer Onverwacht object delen leidt vaak tot onjuiste code. Voor apps waarvoor cookies zijn vereist, kunt u het volgende overwegen:
- cookie Automatische verwerking uitschakelen
- Het vermijden van
IHttpClientFactory
Aanroep ConfigurePrimaryHttpMessageHandler om automatische cookie verwerking uit te schakelen:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Logging
Clients die zijn gemaakt via IHttpClientFactory leggen logboekberichten vast voor alle verzoeken. Schakel het juiste informatieniveau in uw logboekconfiguratie in om de standaardlogboekberichten te zien. Aanvullende logboeken, zoals de logboeken van aanvraagheaders, worden alleen opgenomen op trace-niveau.
De logboekcategorie die voor elke client wordt gebruikt, bevat de naam van de client. Een client met de naam MyNamedClient registreert bijvoorbeeld berichten met een categorie van System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. Berichten met het achtervoegsel LogicalHandler komen voor buiten de pijplijn van de aanvraaghandler. Op de aanvraag worden berichten geregistreerd voordat andere handlers in de pijplijn deze hebben verwerkt. Bij het antwoord worden berichten geregistreerd nadat andere pipeline-handlers het antwoord hebben ontvangen.
Logboekregistratie vindt ook plaats in de pijplijn van de aanvraaghandler. In het voorbeeld van MyNamedClient worden deze berichten vastgelegd in de logboekcategorie System.Net.Http.HttpClient.MyNamedClient.ClientHandler. Voor de aanvraag gebeurt dit nadat alle andere handlers zijn uitgevoerd en direct voordat de aanvraag op het netwerk wordt verzonden. In het antwoord bevat deze logboekregistratie de status van het antwoord voordat deze wordt doorgestuurd via de handler-pijplijn.
Door logboekregistratie buiten en binnen de pijplijn in te schakelen, kunnen de wijzigingen die door de andere pijplijnhandlers zijn aangebracht, worden gecontroleerd. Dit kunnen wijzigingen in aanvraagheaders zijn, bijvoorbeeld of de antwoordstatuscode.
Als u de naam van de client in de logboekcategorie opvoegt, kunt u waar nodig logboekfiltering voor specifieke benoemde clients inschakelen.
HttpMessageHandler configureren
Het kan nodig zijn om de configuratie van de binnenste HttpMessageHandler te beheren die door een client wordt gebruikt.
Er IHttpClientBuilder wordt een geretourneerd bij het toevoegen van benoemde of getypte clients. De ConfigurePrimaryHttpMessageHandler extensiemethode kan worden gebruikt om een gemachtigde te definiëren. De gemachtigde wordt gebruikt voor het maken en configureren van de primaire die HttpMessageHandler door die client wordt gebruikt:
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
IHttpClientFactory gebruiken in een console-app
Voeg in een console-app de volgende pakketverwijzingen toe aan het project:
In het volgende voorbeeld:
- IHttpClientFactory is geregistreerd in de servicecontainer van de Generic Host.
-
MyServicemaakt een client factory-exemplaar van de service, die wordt gebruikt voor het maken van eenHttpClient.HttpClientwordt gebruikt om een webpagina op te halen. - De methode van
GetPagede service wordt uitgevoerd om de eerste 500 tekens van de webpagina-inhoud naar de console te schrijven. ZieProgram.Mainvoor meer informatie over het aanroepen van services.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware voor headerdoorgifte
Headerdoorgifte is een door de community ondersteunde middleware om HTTP-headers van de binnenkomende aanvraag door te geven aan de uitgaande HTTP-clientaanvragen. Koptekst propagatie gebruiken:
Verwijs naar de door de community ondersteunde poort van het pakket HeaderPropagation. ASP.NET Core 3.1 of hoger ondersteunt Microsoft.AspNetCore.HeaderPropagation.
Configureer de middleware en
HttpClientinStartup:public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseMvc(); }De client bevat de geconfigureerde headers voor uitgaande aanvragen:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);