Kanject.Core.Adapter
3.7.3
Prefix Reserved
See the version list below for details.
dotnet add package Kanject.Core.Adapter --version 3.7.3
NuGet\Install-Package Kanject.Core.Adapter -Version 3.7.3
<PackageReference Include="Kanject.Core.Adapter" Version="3.7.3" />
<PackageVersion Include="Kanject.Core.Adapter" Version="3.7.3" />
<PackageReference Include="Kanject.Core.Adapter" />
paket add Kanject.Core.Adapter --version 3.7.3
#r "nuget: Kanject.Core.Adapter, 3.7.3"
#:package Kanject.Core.Adapter@3.7.3
#addin nuget:?package=Kanject.Core.Adapter&version=3.7.3
#tool nuget:?package=Kanject.Core.Adapter&version=3.7.3
Kanject.Core.Adapter
Kanject.Core.Adapter is the outbound HTTP integration layer for Kanject services. It combines typed HttpClient registration, resilient retries, authentication and header helpers, health pings, AOT-safe JSON overloads, and an optional source generator that turns declarative endpoint contracts into production-ready adapter methods.
Use it when an application needs a stable boundary around a payment gateway, shipping carrier, CRM, internal microservice, or any other HTTP API. Application code depends on a typed adapter rather than spreading URLs, credentials, serialization, and status-code handling across controllers and domain services.
Installation
dotnet add package Kanject.Core.Adapter
The package supports .NET 8, .NET 9, and .NET 10. It includes Kanject.Core.Adapter.Annotations transitively for NuGet consumers, so generated adapters normally require no second package reference.
Choose an authoring style
| Style | Best for | What you write |
|---|---|---|
| Generated endpoints | Most integrations and documentation-first contracts | Attributes, request/response DTOs, and partial method signatures |
| Hand-written adapter | Custom protocols, unusual response handling, or incremental migration | A subclass plus calls to GetAsync, PostAsync, PutAsync, or DeleteAsync |
Both styles use the same AbstractServiceAdapter, HttpClientFactory registration, retry policy, cancellation flow, and response helpers.
Quick start: generated shipping adapter
The following contract uses API-key authentication, a settings-driven base URL, source-generated JSON metadata, route substitution, and an endpoint-specific correlation header.
using System.Text.Json.Serialization;
using Kanject.Core.Adapter;
using Kanject.Core.Adapter.Attributes;
using Kanject.Core.Adapter.Enums;
[AdapterAuth(
AuthScheme.ApiKey,
SettingsType = typeof(ParcelApiOptions),
HeaderMappings = ["X-Api-Key:ApiKey"])]
[AdapterBaseUrl(nameof(ParcelApiOptions.BaseUrl))]
[AdapterDisablePing]
[AdapterJsonSerializerContext(typeof(ParcelAdapterJsonContext))]
public partial class ParcelAdapter(
HttpClient httpClient,
ParcelApiOptions settings) : AbstractServiceAdapter(httpClient, shouldDisposeHttpClient: false)
{
// The generator resolves settings-backed templates from this field.
private readonly ParcelApiOptions _settings = settings;
[AdapterEndpoint(HttpVerb.Post, "v1/shipments")]
[AdapterHeader("Idempotency-Key", "{request.IdempotencyKey}")]
public virtual partial Task<CreateShipmentResponse?> CreateShipmentAsync(
CreateShipmentRequest request,
CancellationToken cancellationToken);
[AdapterEndpoint(
HttpVerb.Get,
"v1/shipments/{ShipmentId}",
NotFoundReturnsNull = true)]
public virtual partial Task<ShipmentStatus?> GetShipmentAsync(
ShipmentLookup request,
CancellationToken cancellationToken);
}
public sealed class ParcelApiOptions
{
public required string BaseUrl { get; init; }
public required string ApiKey { get; init; }
}
public sealed record CreateShipmentRequest(
string IdempotencyKey,
string RecipientName,
string PostalCode,
decimal WeightKg);
public sealed record CreateShipmentResponse(string ShipmentId, string LabelUrl);
public sealed record ShipmentLookup(string ShipmentId);
public sealed record ShipmentStatus(string ShipmentId, string Status);
[JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)]
[JsonSerializable(typeof(CreateShipmentRequest))]
[JsonSerializable(typeof(CreateShipmentResponse))]
[JsonSerializable(typeof(ShipmentStatus))]
internal sealed partial class ParcelAdapterJsonContext : JsonSerializerContext;
Register the settings and every generated adapter from the composition root:
using Microsoft.Extensions.DependencyInjection;
builder.Services.AddSingleton(new ParcelApiOptions
{
BaseUrl = builder.Configuration["ParcelApi:BaseUrl"]
?? throw new InvalidOperationException("ParcelApi:BaseUrl is required."),
ApiKey = builder.Configuration["ParcelApi:ApiKey"]
?? throw new InvalidOperationException("ParcelApi:ApiKey is required.")
});
builder.Services.AddGeneratedServiceAdapters(options =>
{
options.ServiceAdapterMaximumRetryCount = 3;
options.HandlerLifetime = 5; // minutes
});
Consume it like any typed dependency:
public sealed class ShippingService(ParcelAdapter parcels)
{
public Task<CreateShipmentResponse?> DispatchAsync(
CreateShipmentRequest request,
CancellationToken cancellationToken) =>
parcels.CreateShipmentAsync(request, cancellationToken);
}
Hand-written adapter
Use the runtime directly when an endpoint requires custom interpretation. Prefer the JsonTypeInfo<T> overloads in trimmed or Native AOT applications.
using Kanject.Core.Adapter;
using Kanject.Core.Adapter.Extensions;
public sealed class FraudAdapter(HttpClient httpClient)
: AbstractServiceAdapter(httpClient, shouldDisposeHttpClient: false)
{
public async Task<FraudDecision?> ScoreAsync(
FraudRequest request,
CancellationToken cancellationToken)
{
using var response = await PostAsync(
"v2/score",
request,
FraudJsonContext.Default.FraudRequest,
cancellationToken);
if (response.StatusCode == System.Net.HttpStatusCode.UnprocessableEntity)
{
ProblemDetails = await response.ReadProblemDetailsAsync();
return null;
}
response.EnsureSuccessStatusCode();
return await response.ReceiveJsonAsync(FraudJsonContext.Default.FraudDecision);
}
}
Runtime reference
Registration and resilience
services.AddServiceAdapter<FraudAdapter>(options =>
{
options.ServiceAdapterMaximumRetryCount = 3;
options.HandlerLifetime = 5;
// options.DisableRetryPolicy = true;
});
AddServiceAdapter<T>() registers a typed client through IHttpClientFactory. Unless disabled, it retries transient HTTP failures and 404 Not Found responses using decorrelated-jitter backoff. The default retry count is 2 and the default handler lifetime is 2 minutes. Only enable retries for operations that are safe to repeat; use idempotency keys for retryable writes.
HTTP operations
| Operation | Supported payloads | Cancellation |
|---|---|---|
GetAsync |
Path; path plus dictionary or key/value query parameters | Token-aware overloads |
PostAsync |
Typed JSON, source-generated JSON metadata, multipart form data | Token-aware overloads |
PutAsync |
Typed JSON, source-generated JSON metadata, multipart form data, empty body | Token-aware overloads |
DeleteAsync |
Path | Token-aware overload |
Legacy overloads without a CancellationToken remain available, but request-scoped code should pass its token explicitly.
Authentication and headers
Generated adapters support None, Bearer, Basic, ApiKey, OAuth, and Identity auth metadata. Runtime adapters can use AddAccessToken, AddAccessTokenFunc, SetAuthorizationHeader, AddRequestHeader, SetOrReplaceHeader, SetHeaders, and the specialized language, user-agent, and cache-control helpers.
[AdapterHeader] is preferable for endpoint-specific values because the generator applies it to the individual HttpRequestMessage; default headers live on the shared typed HttpClient and affect later calls from that adapter instance.
Ping and warm-up
Adapters ping adapter/ping by default. Configure a different remote path with [AdapterPing("health")] or UsePing("health"), or opt out with [AdapterDisablePing] / DisablePing().
An ASP.NET Core service can expose the matching local endpoint and optionally keep registered adapters warm:
app.UseAdapterPing();
app.UseWarmUpAdapters(
duration: TimeSpan.FromMinutes(30),
interval: 300);
Warm-up starts background pings for adapters registered through AddServiceAdapter<T>(). Treat it as a serverless cold-start tool, not a replacement for platform health checks.
Response helpers
| Helper | Purpose |
|---|---|
ReceiveJsonAsync<T>() |
Deserialize JSON using compatibility/reflection serialization |
ReceiveJsonAsync(JsonTypeInfo<T>) |
Deserialize JSON with source-generated metadata; preferred for trim/AOT |
ReceiveStringAsync() |
Read a raw response body |
ReadProblemDetailsAsync() |
Parse RFC-style problem details |
GetErrors() |
Convert ValidationProblemDetails into ModelStateDictionary |
Dispose each HttpResponseMessage after reading it. Do not dispose a DI-managed HttpClient; pass shouldDisposeHttpClient: false from adapters created by IHttpClientFactory.
Generated endpoint metadata
The generator recognizes these contract attributes:
| Attribute | Scope | Documentation meaning |
|---|---|---|
AdapterEndpoint |
Method | Verb, path, content type, auth override, error policy, query template, response envelope |
AdapterHeader |
Method | Per-request header and value template |
AdapterAuth |
Class | Default auth scheme and credential mappings |
OAuthClientCredentials |
Class | Token endpoint, credentials, scopes, expiry buffer |
AdapterBaseUrl |
Class | Default base URL property on the settings object |
AdapterPing / AdapterDisablePing |
Class | Remote liveness behavior |
AdapterDependency |
Class | Extra constructor dependency to include in generated documentation |
AdapterJsonSerializerContext |
Class | Source-generated JSON context used by endpoint bodies |
See the annotations reference for every property, error behavior, analyzer diagnostic, authentication example, and generated-file shape.
Documentation artifacts
Every build containing generated adapter endpoints can emit two integration artifacts:
| Artifact | Consumer |
|---|---|
{AssemblyName}.adapterschema.md |
Humans, internal docs portals, and AI documentation tooling |
{AssemblyName}.adapteropenapi.json |
Swagger UI, Redoc, Postman, Insomnia, NSwag, and OpenAPI Generator |
The source generator derives both artifacts from endpoint signatures, XML comments, DTO shapes, route/query/header templates, auth attributes, and response metadata. For useful output:
- Add XML
<summary>,<param>,<returns>,<remarks>, and<example>comments to public adapter methods and DTOs. - Use domain names such as
CreateShipmentRequest, not generic names such asRequestModel. - Model route and query values as typed parameters or DTO properties so the generator can document them.
- Declare response wrappers with
ResponseWrapperandUnwrapPropertyrather than hiding envelope behavior in prose. - Declare non-obvious dependencies with
[AdapterDependency].
Generated adapter classes must remain unsealed because the generator emits virtual endpoint and lifecycle hooks. Declare them as public partial class, not public sealed partial class.
Store settings-backed base URLs without a trailing slash when endpoint paths begin with one in generated interpolation. This avoids producing a double-slash path such as https://api.example.com//v1/orders.
Output directories and opt-outs are configurable in the consuming project:
<PropertyGroup>
<AdapterSchemaOutputDir>docs/generated</AdapterSchemaOutputDir>
<AdapterOpenApiOutputDir>docs/generated/openapi</AdapterOpenApiOutputDir>
<GenerateAdapterSchema>true</GenerateAdapterSchema>
<GenerateAdapterOpenApi>true</GenerateAdapterOpenApi>
</PropertyGroup>
Production checklist
- Store credentials in a secret provider or environment-backed configuration, never in source.
- Pass request cancellation tokens through every adapter call.
- Set an
HttpClient.Timeoutappropriate to the upstream service. - Make retried write endpoints idempotent.
- Prefer
[AdapterJsonSerializerContext]andJsonTypeInfo<T>overloads for trimming and Native AOT. - Choose
ErrorBehaviordeliberately; document whether missing resources returnnullor throw. - Avoid logging authorization headers, secrets, or full sensitive payloads.
- Test success, validation failure, rate limit, timeout, cancellation, and malformed-response paths.
Runnable example
The Adapter sample models a parcel-carrier integration with API-key authentication, idempotency, route parameters, generated Markdown/OpenAPI artifacts, DI registration, and a fake upstream handler so it can run without external credentials.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Kanject.Core.Adapter.Annotations (>= 1.5.0)
- Kanject.Core.Api.Abstractions (>= 3.6.2)
- Kanject.Core.CacheDb.Abstractions (>= 3.5.2)
- Microsoft.Extensions.Http.Polly (>= 10.0.8)
- Polly.Contrib.WaitAndRetry (>= 1.1.1)
-
net8.0
- Kanject.Core.Adapter.Annotations (>= 1.5.0)
- Kanject.Core.Api.Abstractions (>= 3.6.2)
- Kanject.Core.CacheDb.Abstractions (>= 3.5.2)
- Microsoft.Extensions.Http.Polly (>= 8.0.23)
- Polly.Contrib.WaitAndRetry (>= 1.1.1)
-
net9.0
- Kanject.Core.Adapter.Annotations (>= 1.5.0)
- Kanject.Core.Api.Abstractions (>= 3.6.2)
- Kanject.Core.CacheDb.Abstractions (>= 3.5.2)
- Microsoft.Extensions.Http.Polly (>= 9.0.9)
- Polly.Contrib.WaitAndRetry (>= 1.1.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.