The library provides an outgoing request resiliency pipeline for HttpClient, using policies from the PoliNorError library.
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
- π¦ Resiliency pipeline - the pipeline of
DelegatingHandler, using policies from thePoliNorErrorlibrary. - β‘ 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).
β 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-Functhat uses theIServiceProviderto create a policy.funcThatUsesContextAndServiceProviderToCreatePolicy-Functhat uses theIServiceProviderand 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)
{
...
}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.
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>.
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 anHttpRequestException, theInnerExceptionwill be thatHttpRequestException. - Otherwise, the exception originates from one of the handlers, either inside or outside the resiliency pipeline.
- If the response status code matches the handling filterβs status code, it will be a special
FailedResponseData- not null if the status code part of the handling filter matches the response status code.HasFailedResponse- true ifFailedResponseDatais not null.PolicyResult- specifies thePolicyResult<HttpResponseMessage>result that is produced by a policy that belongs to theDelegatingHandlerthat throws this exception.InnermostPolicyResult- specifies thePolicyResult<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.
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: AccessInnermostPolicyResultto evaluate the root cause encountered by the final handler in the pipeline.FailedResponseData: Contains theHttpStatusCodeand other useful failure details. This property is non-null only if the response status code matches your configuredHttpErrorFilterstatus code.
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.
See the /samples folder for concrete examples.
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/

