Table of Contents

Watching

Please find this dedicated tutorial video on YouTube about how to configure logging with Apizr:

Configuring Polly

If you are referencing the Apizr.Extensions.Microsoft.DependencyInjection package (extended registration), you may want to reference the Microsoft.Extensions.Http.Resilience optional package too, so that you can use all the Microsoft Resilience goodness.

Anyway, both extended and static registrations let you configure Polly behaviors straight the way with the yet referenced Polly.Extensions package.

Using Microsoft Resilience

With the extended registration approach only (not available with the static one), the Microsoft.Extensions.Http.Resilience optional package offers a pre-configured way to handle requests resilience, applied globally to all methods of an api interface.

Installing

First, you should read more about it from the official documentation.

Then, please install this package:

Project Current Upcoming
Microsoft.Extensions.Http.Resilience NuGet NuGet Pre Release

Registering

Finally, just register it using ConfigureHttpClientBuilder then AddStandardResilienceHandler methods like so:

options => options.ConfigureHttpClientBuilder(builder => builder
    .AddStandardResilienceHandler())

If you need more control over pipeline scope, like per method tunning, you should use the Polly.Extensions integration instead or even mix both approaches, applying some global resilience handling with Microsoft.Extensions.Http.Resilience and some specific ones with Polly.Extensions.

Configuring

If you need more control over resilience settings, you can provide your configuration.

You can do it either automatically from settings or manually with options.

Automatically

First, define your resilience settings like so:

"ResilienceOptions": {
    "Retry": {
        "BackoffType": "Exponential",
        "UseJitter": true,
        "MaxRetryAttempts": 3
    }
}

Then provide it to the Resilience Handler:

options => options.ConfigureHttpClientBuilder(builder => builder
	.AddStandardResilienceHandler(configuration.GetSection("ResilienceOptions")))
Manually

Just provide your configuration thanks to the dedicated builder:

options => options.ConfigureHttpClientBuilder(builder => builder
    .AddStandardResilienceHandler(resilienceOptions =>
    {
        resilienceOptions.CircuitBreaker.MinimumThroughput = 10;
        // and so on...
    }))

Using Polly Extensions

With both extended and static registrations, the Polly.Extensions integration offers many ways to handle requests resilience, individually or globally, and can be configured at design, register or request time.

Apizr comes with a ResiliencePipeline attribute to apply some resilience strategies on apis, handled by Polly.

You’ll find also resilience pipeline attributes dedicated to each Http methods like GetResiliencePipeline, PostResiliencePipeline and so on, and some others to CRUD apis like CreateResiliencePipeline, ReadResiliencePipeline and so on…

Polly will help you to manage some retry scenarios but can do more. Please refer to its official documentation if you’d like to know more about it.

First, add the request options parameter [RequestOptions] IApizrRequestOptions options to your api methods to ensure your pipelines will be applied and don't forget to pass the options to your api methods at request time.

Registering

Here is how to define a resilience pipeline with some strategies.

var resiliencePipelineBuilder = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddRetry(
        new RetryStrategyOptions<HttpResponseMessage>
        {
            ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
                .Handle<HttpRequestException>()
                .HandleResult(response =>
                    response.StatusCode is >= HttpStatusCode.InternalServerError
                        or HttpStatusCode.RequestTimeout),
            Delay = TimeSpan.FromSeconds(1),
            MaxRetryAttempts = 3,
            UseJitter = true,
            BackoffType = DelayBackoffType.Exponential
        });

Now we have to register our pipeline:

There's nothing specific to do with Apizr about Polly when using the extended approach.

Just don't forget to register it into your container like you usualy do:

// (Polly) Add the resilience pipeline with its key to your container
services.AddResiliencePipeline<string, HttpResponseMessage>("TransientHttpError",
    builder => builder.AddPipeline(resiliencePipelineBuilder.Build()));

Note that TransientHttpError here is a key that will be used to identify the pipeline to apply to apis.

Activating

You can activate resiliencing either at:

  • Design time by attribute decoration
  • Register time by fluent configuration
  • Request time by fluent configuration
ResiliencePipeline attribute

Apizr comes with a ResiliencePipeline attribute which activate resiliencing at any level (all Assembly apis, classic interface/crud class apis or specific classic interface api method).

Here is classic api an example:

[assembly:ResiliencePipeline("TransientHttpError")]
namespace Apizr.Sample
{
    [BaseAddress("https://reqres.in/api")]
    public interface IReqResService
    {
        [Get("/users")]
        Task<UserList> GetUsersAsync();
    }
}

Here we are using the ResiliencePipeline attribute at assembly level (all methods of all apis), but you can use it at interface/class level (all methods of one api) or method level (decorated api methods only).

You may want to set pipelines scoped to a group of methods instead, like all Get http methods or Post ones. You can do it at assembly or interface/class levels thanks to one of the provided scoped attributes:

  • Http methods grouping:
    • GetResiliencePipeline
    • PostResiliencePipeline
    • PutResiliencePipeline
    • DeleteResiliencePipeline
    • PatchResiliencePipeline
    • OptionsResiliencePipeline
    • HeadResiliencePipeline

You’ll find some more resilience pipeline attributes but dedicated to CRUD apis (the ones starting with Read, ReadAll, Create, Update or Delete prefix), so you could activate resiliencing at method/request level for CRUD apis too.

Here is CRUD api an example:

namespace Apizr.Sample.Models
{
    [BaseAddress("https://reqres.in/api/users")]
    [ReadAllResiliencePipeline("TransientHttpError")]
    [ReadResiliencePipeline("AnotherHttpError")]
    public record User
    {
        [JsonPropertyName("id")]
        public int Id { get; init; }

        [JsonPropertyName("first_name")]
        public string FirstName { get; init; }

        [JsonPropertyName("last_name")]
        public string LastName { get; init; }

        [JsonPropertyName("avatar")]
        public string Avatar { get; init; }

        [JsonPropertyName("email")]
        public string Email { get; init; }
    }
}

As usual, you can mix levels and pipelines as all will be wrapped in the end.

Fluent configuration
Automatically

Resiliencing could be activated automatically by providing an IConfiguration instance containing resilience pipeline settings to Apizr:

options => options.WithConfiguration(context.Configuration)

We can activate it at common level (to all apis), specific level (dedicated to a named api) or even request level (dedicated to a named api's method).

Please heads to the Settings) doc article to see how to configure resiliencing automatically from settings.

Manually

You can activate resiliencing at any levels by providing pipeline keys with this fluent option :

// pipeline keys
options => options.WithResiliencePipelineKeys(["TransientHttpError"])

// OR the same with method scope
options => options.WithResiliencePipelineKeys(["TransientHttpError"], [ApizrRequestMethod.HttpGet, ApizrRequestMethod.CrudRead])

Using

Apizr will automatically tell Polly to handle request with pipelines that get a key matching the one provided by attributes or fluent options.

Tunning Polly Context

Automatically

Context parameters could be set automatically by providing an IConfiguration instance containing the context settings:

options => options.WithConfiguration(context.Configuration)

We can set it at common level (to all apis), specific level (dedicated to a named api) or even request level (dedicated to a named api's method).

Please heads to the Settings) doc article to see how to configure context automatically from loaded settings configuration.

Manually

Some advanced options are also available to configure Polly context itself at any level:

options => options.WithResilienceContextOptions(contextOptions =>
    contextOptions.ReturnToPoolOnComplete(false) // true by default
        .ContinueOnCapturedContext(false))

Here is the one to provide Resilience Properties to Polly context at any level:

// direct configuration
options => options.WithResilienceProperty(testKey2, "testValue2.2")

// OR factory configuration
options => options.WithResilienceProperty(testKey2, () => "testValue2.2")

// OR extended factory configuration
options => options.WithResilienceProperty(testKey2, serviceProvider => 
    serviceProvider.GetRequiredService<ISettingsService>().MyTestValue)

Note that if you provide a property with the same key at different levels, the closest one to the request will be the one used by Apizr.