Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Polly-Contrib/Simmy/llms.txt

Use this file to discover all available pages before exploring further.

Simmy integrates naturally with the .NET dependency injection model. Because chaos policies are just IAsyncPolicy or IPolicy instances, you can register them through IServiceCollection the same way you register any other Polly policy — but with an important twist: you want chaos active only in specific environments, only for specific operations, and ideally without touching your existing resilience policy code at all. The three patterns below show exactly how to achieve that.
The Simmy sample app contains full working implementations of all three patterns demonstrated here. It is the canonical reference for production-style DI wiring.

Pattern 1: Environment-Gated Startup

The simplest way to keep chaos out of production is to register chaos policies only when the host environment is not Production. Your normal Polly policies are registered unconditionally; the chaos layer is added only in Dev and Staging builds.
1

Register your baseline Polly policies

Add your retry, timeout, and circuit-breaker policies as you normally would. These registrations stay completely unchanged — Simmy adds its own layer on top.
public void ConfigureServices(IServiceCollection services, IWebHostEnvironment env)
{
    // Normal resilience policies — untouched
    services.AddHttpClient<CatalogService>()
        .AddPolicyHandler(GetRetryPolicy())
        .AddPolicyHandler(GetTimeoutPolicy());
}
2

Add the chaos layer for non-production environments

Wrap the same HttpClient registration with a chaos policy only when the environment is not Production. The chaos policy is injected innermost — after the resilience policies — so the outer resilience policies react to anything Simmy injects.
public void ConfigureServices(IServiceCollection services, IWebHostEnvironment env)
{
    // Normal resilience policies — untouched
    services.AddHttpClient<CatalogService>()
        .AddPolicyHandler(GetRetryPolicy())
        .AddPolicyHandler(GetTimeoutPolicy());

    // Chaos layer — non-production environments only
    if (!env.IsProduction())
    {
        services.AddHttpClient<CatalogService>()
            .AddPolicyHandler(GetChaosPolicy());
    }
}
3

Define the chaos policy factory

Keep the chaos policy factory method private to Startup. A 5 % injection rate is a safe starting point; you can increase it once you’re confident in your resilience behaviour.
private IAsyncPolicy<HttpResponseMessage> GetChaosPolicy()
{
    return MonkeyPolicy.InjectExceptionAsync(with =>
        with.Fault(new HttpRequestException("Simmy: injected network fault"))
            .InjectionRate(0.05)
            .Enabled()
    );
}
Using env.IsProduction() is a compile-time check. For runtime control — turning chaos on or off without redeploying — combine this pattern with Pattern 3.

Pattern 2: Inserting Chaos Without Changing Existing Policy Code

One of Simmy’s main design goals is that you should be able to inject chaos into an existing app without modifying any existing Polly configuration code. You achieve this by wrapping whatever policies are already registered with a new MonkeyPolicy wrapper at registration time. The trick is that PolicyWrap evaluates policies from outermost to innermost. By adding the chaos policy last in the AddPolicyHandler chain (i.e. innermost), the existing outer policies — retry, circuit breaker, fallback — handle whatever Simmy throws or returns.
// Startup.cs — existing policy registrations are not touched
public void ConfigureServices(IServiceCollection services, IWebHostEnvironment env)
{
    var clientBuilder = services.AddHttpClient<OrderService>()
        .AddPolicyHandler(GetFallbackPolicy())
        .AddPolicyHandler(GetCircuitBreakerPolicy())
        .AddPolicyHandler(GetRetryPolicy());

    // Chaos is added as a separate, optional step — zero changes above
    if (!env.IsProduction())
    {
        clientBuilder.AddPolicyHandler(GetChaosPolicy());
    }
}
You can also inject a result (a concrete HttpResponseMessage) instead of an exception, which lets you simulate bad HTTP responses — for example a 503 Service Unavailable — that your retry and fallback policies should handle:
private IAsyncPolicy<HttpResponseMessage> GetChaosResultPolicy()
{
    return MonkeyPolicy.InjectResultAsync<HttpResponseMessage>(with =>
        with.Result(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable))
            .InjectionRate(0.05)
            .Enabled()
    );
}
Do not place the chaos policy outermost in a PolicyWrap. If chaos is outermost, your resilience policies will never see the injected faults — the chaos policy sits outside them and short-circuits before they execute.

Pattern 3: Driving Chaos from External Configuration

Hardcoding .Enabled() or .InjectionRate(0.05) is fine for local development, but for staging environments you want to be able to flip chaos on and off — and change injection rates — without redeploying. Simmy supports this through EnabledWhen and the delegate overload of InjectionRate, both of which receive the Polly Context and a CancellationToken. The pattern is to inject a settings provider (e.g. via IOptions<ChaosSettings> or a custom IChaosSettingsProvider) and read the current settings on every execution. Because Polly’s Context.OperationKey identifies which operation is running, you can enable chaos selectively per-operation.
private IAsyncPolicy<HttpResponseMessage> GetChaosPolicy(IChaosSettingsProvider chaosSettings)
{
    return MonkeyPolicy.InjectExceptionAsync(with =>
        with.Fault(new HttpRequestException("Simmy: injected fault from config"))
            .EnabledWhen(async (ctx, ct) =>
            {
                var settings = await chaosSettings.GetAsync();
                return settings.IsChaosEnabled(ctx.OperationKey);
            })
            .InjectionRate(async (ctx, ct) =>
            {
                var settings = await chaosSettings.GetAsync();
                return settings.GetInjectionRate(ctx.OperationKey);
            })
    );
}
Register the chaos settings provider in DI so it can be resolved alongside the policy:
public void ConfigureServices(IServiceCollection services, IWebHostEnvironment env)
{
    services.Configure<ChaosSettings>(Configuration.GetSection("ChaosSettings"));
    services.AddSingleton<IChaosSettingsProvider, ChaosSettingsProvider>();

    if (!env.IsProduction())
    {
        services.AddHttpClient<PaymentService>()
            .AddPolicyHandler((sp, _) =>
            {
                var chaosSettings = sp.GetRequiredService<IChaosSettingsProvider>();
                return GetChaosPolicy(chaosSettings);
            });
    }
}
A matching appsettings.Development.json section might look like this:
// Accessed via IConfiguration — keys are OperationKey values set on Context
// "ChaosSettings": {
//   "PaymentService.Charge": { "Enabled": true, "InjectionRate": 0.10 },
//   "PaymentService.Refund": { "Enabled": false, "InjectionRate": 0.0  }
// }
You can back IChaosSettingsProvider with Azure App Configuration, AWS Parameter Store, or any other remote store so that chaos can be toggled in real time without any redeployment at all. See the Simmy and Azure App Configuration blog post for a detailed walkthrough.
When using EnabledWhen or the delegate overload of InjectionRate, always tag each Execute call with a Context carrying the appropriate OperationKey. Without it, ctx.OperationKey will be empty and your per-operation settings won’t resolve correctly.
var context = new Context("PaymentService.Charge");
await policyWrap.ExecuteAsync(ct => chargeAsync(ct), context, cancellationToken);

Build docs developers (and LLMs) love