Skip to main content

Getting Started

Build and wire a production-ready shield in about five minutes.

Install

dotnet add package Kevlar

The core targets netstandard2.0 (so .NET Framework 4.6.2+ works), net8.0, and net10.0. See the canonical package table for optional integrations and testing support.

Protect your first call

using System.Net.Http;
using Kevlar;

var shield = Shield.Retry(3);

using var client = new HttpClient();
using var response = await shield.ExecuteAsync(
ct => client.GetAsync("https://example.com", ct));

Retry(3) means up to 4 total attempts: the initial call plus three retries. Its default backoff is exponential with equal jitter, starting at 250 ms and capped at 30 seconds. Always forward the cancellation token passed to your delegate; timeout and hedging strategies use it to stop abandoned work.

.NET Framework

The .NET SDK console template does not accept -f net48. Create an SDK-style console project, then edit its project file to target .NET Framework and enable the language features used above:

<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<Reference Include="System.Net.Http" />
</ItemGroup>

The HTTP example also needs the explicit using System.Net.Http; shown above because .NET Framework does not supply that implicit using. Expect MSBuild to generate binding redirects for the transitive compatibility dependencies: Microsoft.Bcl.AsyncInterfaces 8.0.0, Microsoft.Bcl.TimeProvider 8.0.1, Reservoir 1.4.0, System.Runtime.CompilerServices.Unsafe 6.1.2, System.Threading.Tasks.Extensions 4.6.3, and System.ValueTuple 4.5.0.

Shields are immutable and thread-safe. Build one and reuse it. Reuse also preserves state: calls through the same shield share circuit-breaker and limiter state.

Compose strategies

Strategies execute in reading order: the first strategy is the outermost, like ASP.NET middleware:

var productionShield = Shield
.Timeout(TimeSpan.FromSeconds(30)) // total budget for all attempts
.When<HttpRequestException>()
.Or<TimeoutExceededException>()
.Retry(3)
.CircuitBreaker(consecutiveFailures: 5, breakDuration: TimeSpan.FromSeconds(30))
.Timeout(TimeSpan.FromSeconds(5)); // budget for each attempt

Here, 30-second timeout wraps retry and circuit breaker. Final timeout applies separately to each attempt. Handling clause is ambient: retry and circuit breaker both handle listed failures. See Composition and Handling failures for full rules.

Wire production services

Install Microsoft dependency-injection and HttpClientFactory integrations:

dotnet add package Kevlar.Extensions.DependencyInjection
dotnet add package Kevlar.Extensions.Http

Add named shields and resilient HTTP clients in Program.cs:

using Kevlar;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

services.AddShield("database", Shield
.Timeout(TimeSpan.FromSeconds(10))
.Retry(3));

services.AddHttpClient("catalog", client =>
client.BaseAddress = new Uri("https://catalog.example.com"))
.AddStandardShield();

using var serviceProvider = services.BuildServiceProvider();

AddShield registers a reusable named shield. AddStandardShield installs a production HTTP pipeline with total and per-attempt timeouts, retry, and circuit breaker. POST, PATCH, and custom methods remain single-attempt unless replay is explicitly enabled for an operation that is safe to repeat. Continue with Dependency injection or HTTP resilience to resolve and customize them. Registration extensions live in Microsoft.Extensions.DependencyInjection, so ASP.NET Core projects get them through implicit usings without importing a Kevlar package namespace.

How to test it

Use FakeTimeProvider to advance retry and timeout delays instantly, then inspect pipeline shape, telemetry, and strategy state with Kevlar.Testing. Follow Testing for executable examples instead of waiting on wall-clock timers.

Next steps