Skip to content

The library provides an outgoing request resiliency pipeline for HttpClient, using policies from the PoliNorError library.

License

Notifications You must be signed in to change notification settings

kolan72/PoliNorError.Extensions.Http

Repository files navigation

PoliNorError.Extensions.Http

The library provides an outgoing request resiliency pipeline for HttpClient, using policies from the PoliNorError library.

PoliNorError.Extensions.Http

Pipeline

⚑ Key Features

Explicit resiliency pipeline based on DelegatingHandlers

Works with

- Typed and named `HttpClient`
- `IHttpClientFactory`

Flexible policy creation

- Inline policies
- Policies resolved from `IServiceProvider`
- Context-aware policy creation

Powerful final-handler failure filtering

Precisely control which HTTP responses and exceptions should be treated as failures:

- Transient HTTP errors (5xx, 408, 429)
- `HttpRequestException`
- Custom status codes or status code categories

Full exception transparency

Failures are surfaced via a single, rich exception HttpPolicyResultException, preserving:

- The original exception
- HTTP response details
- Policy execution results

Control exception flow between handlers using IncludeException<TException>

Deep PoliNorError integration

Use PoliNorError's fluent APIs for:

- Retry, fallback, and custom policies
- Exception filtering and processing
- Policy result inspection and logging

.NET Standard 2.0 compatible

πŸ”‘ Key Concepts

  • 🟦 Resiliency pipeline - the pipeline of DelegatingHandler, using policies from the PoliNorError library.
  • ➑ OuterHandler is the first handler in the pipeline (closest to the request initiator).
  • β¬… InnerHandler is the next handler in the pipeline (closer to the final destination).
  • πŸ”΅ FinalHandler is the innermost handler in the pipeline.
  • ❌ Transient HTTP errors are temporary failures that occur when making HTTP requests (HTTP 5xx, HTTP 408, HTTP 429 and HttpRequestException).

πŸš€ Usage

βš™ Configure typed or named HttpClient:

services.AddHttpClient<IAskCatService, AskCatService>((sp, config) =>
	{
		...
		config.BaseAddress = new Uri(settings.BaseUri);
		...
	})...

, where AskCatService is a service that implements IAskCatService, with HttpClient or IHttpClientFactory injected.


🧩 Use the library's IHttpClientBuilder.WithResiliencePipeline extension method to build a pipeline of DelegatingHandlers. Within this scope, configure a handler to use a policy via the AddPolicyHandler method:

services.AddHttpClient<IAskCatService, AskCatService>((spForClient, client) =>
	{
			...
	})
	.WithResiliencePipeline((pb) => 
		pb
		.AddPolicyHandler(PolicyJustCreated)
		.AddPolicyHandler((IServiceProvider sp) => funcThatUsesServiceProviderToCreatePolicy(sp))
		...
	)

Or use the WithResiliencePipeline method overload that includes an additional context parameter:

services.AddHttpClient<IAskCatService, AskCatService>((spForClient, client) =>
	{
			...
	})
	.WithResiliencePipeline<SomeContextType>((pb) => 
		pb
		.AddPolicyHandler((SomeContextType ctx, IServiceProvider sp) => 
			funcThatUsesContextAndServiceProviderToCreatePolicy(ctx, sp))

		.AddPolicyHandler((IServiceProvider sp) => funcThatUsesServiceProviderToCreatePolicy(sp))
		...
	, context)

, where

  • pb - represents the pipeline builder.
  • PolicyJustCreated - a policy from the PoliNorError library.
  • funcThatUsesServiceProviderToCreatePolicy - Func that uses the IServiceProvider to create a policy.
  • funcThatUsesContextAndServiceProviderToCreatePolicy - Func that uses the IServiceProvider and context to create a policy.

πŸ”΅ Complete the pipeline by calling AsFinalHandler on the last handler and configuring HttpErrorFilter to filter transient HTTP errors,

services.AddHttpClient<IAskCatService, AskCatService>((sp, config) =>
	{
			...
	})
	.WithResiliencePipeline((pb) => 
		pb
		...
		.AddPolicyHandler(PolicyForFinalHandler)
		// βœ” Adds transient http errors to the response handling filter.
		.AsFinalHandler(HttpErrorFilter.HandleTransientHttpErrors())
		...
	)

and/or any non-successful status codes or categories

		...
		.AsFinalHandler(HttpErrorFilter.HandleHttpRequestException()
			// βœ” Also adds 5XX status codes to the response handling filter.
			.OrServerError())
		...

Use IncludeException<TException> on the pipeline builder to allow an outer handler to handle only filtered exceptions from an inner handler or outside the pipeline:

		...
		.AsFinalHandler(HttpErrorFilter.HandleTransientHttpErrors())
		// βœ” Include 'SomeExceptionFromNonPipelineHandler' exceptions in the filter 
		//when thrown by a non-pipeline handler (in this case).
		.IncludeException<SomeExceptionFromNonPipelineHandler>()
		...

⚾ Wrap HttpClient calls in a catch block for HttpPolicyResultException. For unsuccessful requests, inspect the properties of HttpPolicyResultException to access response details:

try
{
	...
	using var response = await _client.GetAsync(uri, token);
	...
}
catch (OperationCanceledException oe)
{
	...
}
catch (HttpPolicyResultException hpre)
{
	// βœ” If the response status code matches the handling filter status code:
	if (hpre.HasFailedResponse)
	{
		//For example, log a failed status code.
		logger.LogError("Failed status code: {StatusCode}.", hpre.FailedResponseData.StatusCode);
	}
}
catch (Exception ex)
{
	...
}

πŸ” Adding Handlers Based on RetryPolicy Using the AddRetryHandler Extension Methods.

The AddRetryHandler extension methods provide a fluent way to attach a RetryPolicy to an HTTP message handler pipeline. One of these methods allows adding a handler via RetryPolicyOptions and is responsible for setting up RetryPolicy details, including:

  • Error processing,
  • Policy result handling,
  • Error filters,
  • Policy naming,
  • Delay between retries,
  • And ultimately registering the policy with AddPolicyHandler.

Example: Retry with logging, filtering, and delay:

var retryOptions = new RetryPolicyOptions()
{
	PolicyName = "MyRetryPolicy",

	ConfigureErrorProcessing = (bp) =>
		bp.WithErrorProcessorOf(
			(Exception ex, ProcessingErrorInfo pi) =>
				loggerTest.LogError(
					ex, 
					"Exception on attempt { Attempt }:", 
					pi.GetRetryCount() + 1)),

	ConfigureErrorFilter = (f) => f.ExcludeError<SomeException>(),

	ConfigurePolicyResultHandling = (handlers) => handlers.AddHandler(
			(pr, _) =>
			{
				if (pr.IsFailed)
				{
					loggerTest.LogWarning(
						"{Errors} exceptions were thrown during handling by {PolicyName}.",
						pr.Errors.Count(),
						pr.PolicyName);
				}
			}
		),
		
	RetryDelay = ConstantRetryDelay.Create(TimeSpan.FromSeconds(1))	
};

This example configures RetryPolicyOptions with:

  • A policy name ("MyRetryPolicy"),
  • An error processor (logs exceptions with attempt numbers),
  • An error filter (excludes SomeException),
  • A result handler (logs warnings about exception counts),
  • A 1-second constant delay between retries.

Attach a retry handler to the pipeline using these options:

services.AddHttpClient<IAskCatService, AskCatService>((sp, config) =>
	{
			...
	})
	.WithResiliencePipeline((pb) => 
		pb
		...
		//Maximum number of retries: 3  
		.AddRetryHandler(3, retryOptions)
		.AsFinalHandler(HttpErrorFilter.HandleTransientHttpErrors())
		...
	)

You can also configure RetryPolicy details inline using the AddRetryHandler overload that accepts an Action<RetryPolicyOptions>.

πŸ“œ HttpPolicyResultException properties

Public properties of the HttpPolicyResultException:

  • InnerException
    • If the response status code matches the handling filter’s status code, it will be a special FailedHttpResponseException.
    • If no handlers inside or outside the resiliency pipeline throw an exception, and the HttpClient’s primary handler throws an HttpRequestException, the InnerException will be that HttpRequestException.
    • Otherwise, the exception originates from one of the handlers, either inside or outside the resiliency pipeline.
  • FailedResponseData - not null if the status code part of the handling filter matches the response status code.
  • HasFailedResponse - true if FailedResponseData is not null.
  • PolicyResult - specifies the PolicyResult<HttpResponseMessage> result that is produced by a policy that belongs to the DelegatingHandler that throws this exception.
  • InnermostPolicyResult - specifies the PolicyResult<HttpResponseMessage> result produced by a policy of the final handler or by a handler in the pipeline that throws its own exception.
  • IsErrorExpected - indicates whether the filter for the original exception was satisfied.
  • IsCanceled - indicates whether the execution was canceled.

πŸ› οΈ Understanding the Exception Hierarchy

One of the key features of this library is the HttpPolicyResultException, which captures the execution history of the pipeline within its properties. When a request fails after exhausting all policies, this exception contains several "layers" of information:

  • PolicyResult: This is the result from the outer handler that finally gave up and threw the exception.
  • InnermostPolicyResult: Access InnermostPolicyResult to evaluate the root cause encountered by the final handler in the pipeline.
  • FailedResponseData: Contains the HttpStatusCode and other useful failure details. This property is non-null only if the response status code matches your configured HttpErrorFilter status code.

❓ Why PoliNorError.Extensions.Http?

Declarative pipeline builder for HttpClient via WithResiliencePipeline

First-class support for typed and named HttpClient

You decide what a failure is

  • Filter transient HTTP errors in the flexible final handler and control exception flow between handlers.

One clear failure signal

  • All handled failures surface as a single, information-rich HttpPolicyResultException.

Helpers to add handlers with rich configuration (AddRetryHandler, AddFallbackHandler)

First-class PoliNorError integration

  • Advanced error processing, contextual logging, and policy result inspection.

🐈 Samples CSharp

See the /samples folder for concrete examples.

πŸ”— Links And Thanks

Steve Gordon. HttpClientFactory in ASP.NET Core 2.1 (Part 3) :
https://www.stevejgordon.co.uk/httpclientfactory-aspnetcore-outgoing-request-middleware-pipeline-delegatinghandlers

Martin Tomka. Building resilient cloud services with .NET 8 : https://devblogs.microsoft.com/dotnet/building-resilient-cloud-services-with-dotnet-8/

Thomas Levesque. Fun with the HttpClient pipeline : https://thomaslevesque.com/2016/12/08/fun-with-the-httpclient-pipeline/

Milan Jovanovic. Extending HttpClient With Delegating Handlers in ASP.NET Core :
https://www.milanjovanovic.tech/blog/extending-httpclient-with-delegating-handlers-in-aspnetcore

Josef Ottosson. Testing your Polly policies :
https://josef.codes/testing-your-polly-policies/

About

The library provides an outgoing request resiliency pipeline for HttpClient, using policies from the PoliNorError library.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages