基于 SOC 职业分类
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/rudironsoni/Synaxis --skill dotnet-http-client命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
AI-powered wiki generation for code repositories with commands, agents, and skills
Routes .NET/C# work to domain skills. Loads coding-standards for code paths.
Skill manifest management for dotnet-agent-harness. Tracks skill dependencies, conflicts, version compatibility, and provides validation and resolution tools. Triggers on: skill manifest, dependency resolution, skill compatibility, version conflicts, build manifest, validate dependencies.
| name | dotnet-http-client |
| category | web |
| subcategory | minimal-apis |
| description | Consumes HTTP APIs. IHttpClientFactory, typed/named clients, resilience, DelegatingHandlers. |
| license | MIT |
| targets | ["*"] |
| tags | ["architecture","dotnet","skill"] |
| version | 0.0.1 |
| author | dotnet-agent-harness |
| invocable | true |
| claudecode | {"allowed-tools":["Read","Grep","Glob","Bash","Write","Edit"]} |
| codexcli | {"short-description":".NET skill guidance for architecture tasks"} |
| opencode | {"allowed-tools":["Read","Grep","Glob","Bash","Write","Edit"]} |
| copilot | {} |
| geminicli | {} |
| antigravity | {} |
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.
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.
Creating HttpClient instances directly causes two problems:
HttpClient instance holds its own connection pool. Creating and disposing many
instances exhausts available sockets (SocketException: Address already in use).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 this
var client = new HttpClient(); // Socket exhaustion risk
// Do not do this either
static readonly HttpClient _client = new(); // DNS staleness risk
// Do this -- use IHttpClientFactory
builder.Services.AddHttpClient();
```text
---
## Named Clients
Register clients name scenarios you consume multiple APIs different configurations:
```csharp
builder.Services.AddHttpClient(, client =>
{
client.BaseAddress = Uri();
client.DefaultRequestHeaders.Add(, );
client.Timeout = TimeSpan.FromSeconds();
});
builder.Services.AddHttpClient(, client =>
{
client.BaseAddress = Uri();
client.DefaultRequestHeaders.Add(, );
});
{
Task<Product?> GetProductAsync(
productId, CancellationToken ct)
{
client = clientFactory.CreateClient();
response = client.GetAsync(, 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. ** ** -- ``
.
---
##
- [ .](:
- [ ](:
- [ ](:
- [ ](:
- [](:
Primary approach: Use Serena symbol operations for efficient code navigation:
serena_find_symbol instead of text searchserena_get_symbols_overview for file organizationserena_find_referencing_symbols for impact analysisserena_replace_symbol_body for clean modificationsWhen to use Serena vs traditional tools:
Example workflow:
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"
# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"