Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Best practices for consuming HTTP APIs in .NET applications using IHttpClientFactory. Covers named and typed clients,
resilience pipeline integration, DelegatingHandler chains for cross-cutting concerns, and testing strategies.
Scope
IHttpClientFactory patterns (named and typed clients)
DelegatingHandler chains for cross-cutting concerns
Resilience pipeline integration with HTTP clients
Testing strategies for HTTP client code
Out of scope
DI container mechanics and service lifetimes -- see [skill:dotnet-csharp-dependency-injection]
Async/await patterns and cancellation token propagation -- see [skill:dotnet-csharp-async-patterns]
Integration testing frameworks -- see [skill:dotnet-integration-testing]
Cross-references: [skill:dotnet-resilience] for resilience pipeline configuration,
[skill:dotnet-csharp-dependency-injection] for service registration, [skill:dotnet-csharp-async-patterns] for async HTTP
patterns.
Why IHttpClientFactory
Creating HttpClient instances directly causes two problems:
Socket exhaustion -- each HttpClient instance holds its own connection pool. Creating and disposing many
instances exhausts available sockets (SocketException: Address already in use).
DNS staleness -- a long-lived singleton HttpClient caches DNS lookups indefinitely, missing DNS changes during
blue-green deployments or failovers.
IHttpClientFactory solves both by managing HttpMessageHandler lifetimes with automatic pooling and rotation
(default: 2-minute handler lifetime).
// Do not do thisvar client = new HttpClient(); // Socket exhaustion risk// Do not do this eitherstaticreadonly HttpClient _client = new(); // DNS staleness risk// Do this -- use IHttpClientFactory
builder.Services.AddHttpClient();
```text
---
## Named Clients
Register clients by name for scenarios where you consume multiple APIs with different configurations:
```csharp
// Registration
builder.Services.AddHttpClient("catalog-api", client =>
{
client.BaseAddress = new Uri("https://catalog.internal");
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.Timeout = TimeSpan.FromSeconds(30);
});
builder.Services.AddHttpClient("payment-api", client =>
{
client.BaseAddress = new Uri("https://payments.internal");
client.DefaultRequestHeaders.Add("X-Api-Version", "2");
});
// UsagepublicsealedclassOrderService(IHttpClientFactory clientFactory)
{
publicasync Task<Product?> GetProductAsync(
string productId, CancellationToken ct)
{
var client = clientFactory.CreateClient("catalog-api");
var response = await client.GetAsync($"/products/{productId}", ct);
(response.StatusCode == HttpStatusCode.NotFound)
{
;
}
response.EnsureSuccessStatusCode();
response.Content
.ReadFromJsonAsync<Product>(ct);
}
}
```json
---
Typed clients encapsulate HTTP logic behind a strongly-typed . Prefer typed clients a service consumes a
single API multiple operations:
```csharp
{
Task<Product?> GetProductAsync(
productId, CancellationToken ct = )
{
response = httpClient.GetAsync(
, ct);
(response.StatusCode == HttpStatusCode.NotFound)
{
;
}
response.EnsureSuccessStatusCode();
response.Content
.ReadFromJsonAsync<Product>(ct);
}
Task<PagedResult<Product>> ListProductsAsync(
page = ,
pageSize = ,
CancellationToken ct = )
{
response = httpClient.GetAsync(
, ct);
response.EnsureSuccessStatusCode();
( response.Content
.ReadFromJsonAsync<PagedResult<Product>>(ct))!;
}
{
response = httpClient.PostAsJsonAsync(
, request, ct);
response.EnsureSuccessStatusCode();
( response.Content
.ReadFromJsonAsync<Product>(ct))!;
}
}
builder.Services.AddHttpClient<CatalogApiClient>(client =>
{
client.BaseAddress = Uri();
client.DefaultRequestHeaders.Add(, );
});
```json
For testability, define an :
```
{
Task<Product?> GetProductAsync( productId, CancellationToken ct = );
Task<PagedResult<Product>> ListProductsAsync( page = , pageSize = , CancellationToken ct = );
}
{
}
builder.Services.AddHttpClient<ICatalogApiClient, CatalogApiClient>(client =>
{
client.BaseAddress = Uri();
});
```text
---
Apply resilience to HTTP clients `Microsoft.Extensions.Http.Resilience`. See [skill:dotnet-resilience]
detailed pipeline configuration, strategy options, migration guidance.
;
})
.AddStandardResilienceHandler();
```text
```csharp
builder.Services
.AddHttpClient<CatalogApiClient>(client =>
{
client.BaseAddress = Uri();
})
.AddStandardResilienceHandler(options =>
{
options.Retry.MaxRetryAttempts = ;
options.Retry.Delay = TimeSpan.FromSeconds();
options.CircuitBreaker.BreakDuration = TimeSpan.FromSeconds();
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds();
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds();
});
```text
For idempotent read operations tail latency matters:
```csharp
builder.Services
.AddHttpClient()
.AddStandardHedgingHandler(options =>
{
options.Hedging.MaxHedgedAttempts = ;
options.Hedging.Delay = TimeSpan.FromMilliseconds();
});
```text
See [skill:dotnet-resilience] to use hedging vs standard retry.
---
`DelegatingHandler` provides a pipeline of message handlers that process outgoing requests incoming responses. Use
them cross-cutting concerns that apply to HTTP traffic.
{
{
stopwatch = Stopwatch.StartNew();
logger.LogInformation(
,
request.Method,
request.RequestUri);
response = .SendAsync(request, cancellationToken);
stopwatch.Stop();
logger.LogInformation(
,
request.Method,
request.RequestUri,
()response.StatusCode,
stopwatch.ElapsedMilliseconds);
response;
}
}
builder.Services.AddTransient<RequestLoggingHandler>();
builder.Services
.AddHttpClient<CatalogApiClient>()
.AddHttpMessageHandler<RequestLoggingHandler>();
```text
```
{
{
apiKey = config[]
?? InvalidOperationException();
request.Headers.Add(, apiKey);
.SendAsync(request, cancellationToken);
}
}
builder.Services.AddTransient<ApiKeyHandler>();
builder.Services
.AddHttpClient<CatalogApiClient>()
.AddHttpMessageHandler<ApiKeyHandler>();
```text
```
{
{
token = httpContextAccessor.HttpContext?
.Request.Headers.Authorization
.ToString()
.Replace(, );
(!.IsNullOrEmpty(token))
{
request.Headers.Authorization =
AuthenticationHeaderValue(, token);
}
.SendAsync(request, cancellationToken);
}
}
```text
```csharp
:
{
HeaderName = ;
{
(!request.Headers.Contains(HeaderName))
{
correlationId = Activity.Current?.Id
?? Guid.NewGuid().ToString();
request.Headers.Add(HeaderName, correlationId);
}
.SendAsync(request, cancellationToken);
}
}
```text
Handlers are added execution order:
```csharp
builder.Services.AddTransient<CorrelationIdHandler>();
builder.Services.AddTransient<BearerTokenHandler>();
builder.Services.AddTransient<RequestLoggingHandler>();
builder.Services
.AddHttpClient<CatalogApiClient>(client =>
{
client.BaseAddress = Uri();
})
.AddHttpMessageHandler<CorrelationIdHandler>()
.AddHttpMessageHandler<BearerTokenHandler>()
.AddHttpMessageHandler<RequestLoggingHandler>()
.AddStandardResilienceHandler();
```text
**Note:** In `IHttpClientFactory`, handlers registered first are outermost. `.AddStandardResilienceHandler()` added last
innermost -- it wraps the actual HTTP call directly. This means retries happen inside the resilience handler without
re-executing the outer DelegatingHandlers. This typically correct: correlation IDs auth tokens are once
the outer handlers, the resilience layer retries the raw HTTP call. If you need per-;
client.BaseAddress = Uri(baseUrl);
});
```text
```json
{
: {
: {
:
}
}
}
```text
The handler lifetime minutes. Adjust services different DNS characteristics:
```csharp
builder.Services
.AddHttpClient<CatalogApiClient>()
.SetHandlerLifetime(TimeSpan.FromMinutes());
```csharp
**Shorter lifetime** ( min): services behind load balancers frequent DNS changes. **Longer lifetime** (
min): stable services connection reuse improves performance.
---
Test typed clients providing a mock handler that returns controlled responses:
```csharp
{
[]
{
expectedProduct = Product { Id = , Name = };
handler = MockHttpMessageHandler(
HttpResponseMessage(HttpStatusCode.OK)
{
Content = JsonContent.Create(expectedProduct)
});
httpClient = HttpClient(handler)
{
BaseAddress = Uri()
};
client = CatalogApiClient(httpClient);
result = client.GetProductAsync();
Assert.NotNull(result);
Assert.Equal(, result.Name);
}
[]
{
handler = MockHttpMessageHandler(
HttpResponseMessage(HttpStatusCode.NotFound));
httpClient = HttpClient(handler)
{
BaseAddress = Uri()
};
client = CatalogApiClient(httpClient);
result = client.GetProductAsync();
Assert.Null(result);
}
}
{
HttpRequestMessage? _lastRequest;
HttpRequestMessage? LastRequest => _lastRequest;
{
_lastRequest = request;
Task.FromResult(response);
}
}
```text
Test handlers isolation providing an inner handler:
```csharp
{
[]
{
config = ConfigurationBuilder()
.AddInMemoryCollection( Dictionary<, ?>
{
[] =
})
.Build();
innerHandler = MockHttpMessageHandler(
HttpResponseMessage(HttpStatusCode.OK));
handler = ApiKeyHandler(config)
{
InnerHandler = innerHandler
};
client = HttpClient(handler)
{
BaseAddress = Uri()
};
client.GetAsync();
Assert.NotNull(innerHandler.LastRequest);
Assert.Equal(
,
innerHandler.LastRequest.Headers
.GetValues().Single());
}
}
```text
Test the full HTTP client pipeline including DI registration:
```csharp
```csharp
---
| Factor | Named Client | Typed Client |
| -------------- | ----------------------------- | ----------------------------------- |
| API surface | Simple ( calls) | Rich (multiple operations) |
| Type safety | Requires name | Strongly typed |
| Encapsulation | HTTP logic consuming | |
| | `` | |
| | | |
| | - | |
** .** , -
.
---
##
- ** ** -- ` ()`
- ** ** -- -
- ** ** -- `()` ( [:-])
- ** ** -- `` (, , )
- ** ** -- --
- ** ** --
- ** / ** -- ``
---
##
1. ** ``** -- `` .
.
2. ** ** -- . ``
( ), `` .
3. ** `` ** -- ` (":
. ` (":
.
4. ** ** -- `()`
`` . - .
(/ ). - ,
.
5. ** ** -- ``
.
---
##
- [ .](:
- [ ](:
- [ ](:
- [ ](:
- [](:
Code Navigation (Serena MCP)
Primary approach: Use Serena symbol operations for efficient code navigation:
Find definitions: serena_find_symbol instead of text search
Understand structure: serena_get_symbols_overview for file organization
Track references: serena_find_referencing_symbols for impact analysis
Precise edits: serena_replace_symbol_body for clean modifications
When to use Serena vs traditional tools:
Use Serena: Navigation, refactoring, dependency analysis, precise edits
Use Read/Grep: Reading full files, pattern matching, simple text operations
Fallback: If Serena unavailable, traditional tools work fine
The standard handler applies the full pipeline (rate limiter, total timeout, retry, circuit breaker, attempt timeout)
with sensible defaults:
```csharp
builder.Services
.AddHttpClient<CatalogApiClient>(client =>
{
client.BaseAddress = new Uri("https://catalog.internal")
### Standard Handler with Custom Options
new
"https://catalog.internal"
5
1
15
5
60
### Hedging Handler (for Read-Only APIs)
where
"search-api"
2
500
for
when
## DelegatingHandlers
and
for
### Handler Pipeline Order
Handlers execute in registration order forrequests (outermost to innermost) and reverse order for responses:
```text
Request --> Handler A --> Handler B --> Handler C --> HttpClientHandler --> Server
Response <-- Handler A <-- Handler B <-- Handler C <-- HttpClientHandler <-- Server
```text
### Common Handlers
#### Request Logging
```csharp
publicsealedclassRequestLoggingHandler(
ILogger<RequestLoggingHandler> logger) : DelegatingHandler
retry token refresh (e.g.,
expired bearer tokens), move the token handler inside the resilience boundary or use a custom
`ResiliencePipelineBuilder` callback.
---
## Configuration Patterns
### Base Address from Configuration
```csharp
builder.Services.AddHttpClient<CatalogApiClient>(client =>
{
var baseUrl = builder.Configuration["Services:CatalogApi:BaseUrl"]
?? thrownew InvalidOperationException(
"CatalogApi base URL not configured")