File: DependencyInjection\PollyHttpClientBuilderExtensions.cs
Web Access
Project: src\src\HttpClientFactory\Polly\src\Microsoft.Extensions.Http.Polly.csproj (Microsoft.Extensions.Http.Polly)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Net.Http;
using Microsoft.Extensions.Http;
using Polly;
using Polly.Extensions.Http;
using Polly.Registry;
 
namespace Microsoft.Extensions.DependencyInjection;
 
/// <summary>
/// Extensions methods for configuring <see cref="PolicyHttpMessageHandler"/> message handlers as part of
/// and <see cref="HttpClient"/> message handler pipeline.
/// </summary>
public static class PollyHttpClientBuilderExtensions
{
    /// <summary>
    /// Adds a <see cref="PolicyHttpMessageHandler"/> which will surround request execution with the provided
    /// <see cref="IAsyncPolicy{HttpResponseMessage}"/>.
    /// </summary>
    /// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
    /// <param name="policy">The <see cref="IAsyncPolicy{HttpResponseMessage}"/>.</param>
    /// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>
    /// <remarks>
    /// <para>
    /// See the remarks on <see cref="PolicyHttpMessageHandler"/> for guidance on configuring policies.
    /// </para>
    /// </remarks>
    public static IHttpClientBuilder AddPolicyHandler(this IHttpClientBuilder builder, IAsyncPolicy<HttpResponseMessage> policy)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }
 
        if (policy == null)
        {
            throw new ArgumentNullException(nameof(policy));
        }
 
        builder.AddHttpMessageHandler(() => new PolicyHttpMessageHandler(policy));
        return builder;
    }
 
    /// <summary>
    /// Adds a <see cref="PolicyHttpMessageHandler"/> which will surround request execution with a policy returned
    /// by the <paramref name="policySelector"/>.
    /// </summary>
    /// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
    /// <param name="policySelector">
    /// Selects an <see cref="IAsyncPolicy{HttpResponseMessage}"/> to apply to the current request.
    /// </param>
    /// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>
    /// <remarks>
    /// <para>
    /// See the remarks on <see cref="PolicyHttpMessageHandler"/> for guidance on configuring policies.
    /// </para>
    /// </remarks>
    public static IHttpClientBuilder AddPolicyHandler(
        this IHttpClientBuilder builder,
        Func<HttpRequestMessage, IAsyncPolicy<HttpResponseMessage>> policySelector)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }
 
        if (policySelector == null)
        {
            throw new ArgumentNullException(nameof(policySelector));
        }
 
        builder.AddHttpMessageHandler(() => new PolicyHttpMessageHandler(policySelector));
        return builder;
    }
 
    /// <summary>
    /// Adds a <see cref="PolicyHttpMessageHandler"/> which will surround request execution with a policy returned
    /// by the <paramref name="policySelector"/>.
    /// </summary>
    /// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
    /// <param name="policySelector">
    /// Selects an <see cref="IAsyncPolicy{HttpResponseMessage}"/> to apply to the current request.
    /// </param>
    /// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>
    /// <remarks>
    /// <para>
    /// See the remarks on <see cref="PolicyHttpMessageHandler"/> for guidance on configuring policies.
    /// </para>
    /// </remarks>
    public static IHttpClientBuilder AddPolicyHandler(
        this IHttpClientBuilder builder,
        Func<IServiceProvider, HttpRequestMessage, IAsyncPolicy<HttpResponseMessage>> policySelector)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }
 
        if (policySelector == null)
        {
            throw new ArgumentNullException(nameof(policySelector));
        }
 
        builder.AddHttpMessageHandler((services) =>
        {
            return new PolicyHttpMessageHandler((request) => policySelector(services, request));
        });
        return builder;
    }
 
    /// <summary>
    /// Adds a <see cref="PolicyHttpMessageHandler"/> which will surround request execution with a policy returned
    /// by the <see cref="IReadOnlyPolicyRegistry{String}"/>.
    /// </summary>
    /// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
    /// <param name="policyKey">
    /// The key used to resolve a policy from the <see cref="IReadOnlyPolicyRegistry{String}"/>.
    /// </param>
    /// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>
    /// <remarks>
    /// <para>
    /// See the remarks on <see cref="PolicyHttpMessageHandler"/> for guidance on configuring policies.
    /// </para>
    /// </remarks>
    public static IHttpClientBuilder AddPolicyHandlerFromRegistry(this IHttpClientBuilder builder, string policyKey)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }
 
        if (policyKey == null)
        {
            throw new ArgumentNullException(nameof(policyKey));
        }
 
        builder.AddHttpMessageHandler((services) =>
        {
            var registry = services.GetRequiredService<IReadOnlyPolicyRegistry<string>>();
 
            var policy = registry.Get<IAsyncPolicy<HttpResponseMessage>>(policyKey);
 
            return new PolicyHttpMessageHandler(policy);
        });
        return builder;
    }
 
    /// <summary>
    /// Adds a <see cref="PolicyHttpMessageHandler"/> which will surround request execution with a policy returned
    /// by the <see cref="IReadOnlyPolicyRegistry{String}"/>.
    /// </summary>
    /// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
    /// <param name="policySelector">
    /// Selects an <see cref="IAsyncPolicy{HttpResponseMessage}"/> to apply to the current request.
    /// </param>
    /// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>
    /// <remarks>
    /// <para>
    /// See the remarks on <see cref="PolicyHttpMessageHandler"/> for guidance on configuring policies.
    /// </para>
    /// </remarks>
    public static IHttpClientBuilder AddPolicyHandlerFromRegistry(
        this IHttpClientBuilder builder,
        Func<IReadOnlyPolicyRegistry<string>, HttpRequestMessage, IAsyncPolicy<HttpResponseMessage>> policySelector)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }
 
        if (policySelector == null)
        {
            throw new ArgumentNullException(nameof(policySelector));
        }
 
        builder.AddHttpMessageHandler((services) =>
        {
            var registry = services.GetRequiredService<IReadOnlyPolicyRegistry<string>>();
            return new PolicyHttpMessageHandler((request) => policySelector(registry, request));
        });
        return builder;
    }
 
    /// <summary>
    /// Adds a <see cref="PolicyHttpMessageHandler"/> which will surround request execution with a <see cref="Policy"/>
    /// created by executing the provided configuration delegate. The policy builder will be preconfigured to trigger
    /// application of the policy for requests that fail with conditions that indicate a transient failure.
    /// </summary>
    /// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
    /// <param name="configurePolicy">A delegate used to create a <see cref="IAsyncPolicy{HttpResponseMessage}"/>.</param>
    /// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>
    /// <remarks>
    /// <para>
    /// See the remarks on <see cref="PolicyHttpMessageHandler"/> for guidance on configuring policies.
    /// </para>
    /// <para>
    /// The <see cref="PolicyBuilder{HttpResponseMessage}"/> provided to <paramref name="configurePolicy"/> has been
    /// preconfigured errors to handle errors in the following categories:
    /// <list type="bullet">
    /// <item><description>Network failures (as <see cref="HttpRequestException"/>)</description></item>
    /// <item><description>HTTP 5XX status codes (server errors)</description></item>
    /// <item><description>HTTP 408 status code (request timeout)</description></item>
    /// </list>
    /// </para>
    /// <para>
    /// The policy created by <paramref name="configurePolicy"/> will be cached indefinitely per named client. Policies
    /// are generally designed to act as singletons, and can be shared when appropriate. To share a policy across multiple
    /// named clients, first create the policy and then pass it to multiple calls to 
    /// <see cref="AddPolicyHandler(IHttpClientBuilder, IAsyncPolicy{HttpResponseMessage})"/> as desired.
    /// </para>
    /// </remarks>
    public static IHttpClientBuilder AddTransientHttpErrorPolicy(
        this IHttpClientBuilder builder,
        Func<PolicyBuilder<HttpResponseMessage>, IAsyncPolicy<HttpResponseMessage>> configurePolicy)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }
 
        if (configurePolicy == null)
        {
            throw new ArgumentNullException(nameof(configurePolicy));
        }
 
        var policyBuilder = HttpPolicyExtensions.HandleTransientHttpError();
 
        // Important - cache policy instances so that they are singletons per handler.
        var policy = configurePolicy(policyBuilder);
 
        builder.AddHttpMessageHandler(() => new PolicyHttpMessageHandler(policy));
        return builder;
    }
 
    /// <summary>
    /// Adds a <see cref="PolicyHttpMessageHandler"/> which will surround request execution with a policy returned
    /// by executing provided key selection logic <paramref name="keySelector"/> and <paramref name="policyFactory"/>
    /// </summary>
    /// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
    /// <param name="policyFactory">Selects an <see cref="IAsyncPolicy{HttpResponseMessage}"/> to apply to the current request based on key selection.</param>
    /// <param name="keySelector">A delegate used to generate a policy key based on the <see cref="HttpRequestMessage"/>.</param>
    /// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>
    /// <remarks>
    /// <para>
    /// Key generated by <paramref name="policyFactory"/> is first used to lookup existing policies from IPolicyRegistry. If policy does not exist in the registry, create a new policy with <paramref name="policyFactory"/> and add it in IPolicyRegistry.
    /// </para>
    /// <para>
    /// See the remarks on <see cref="PolicyHttpMessageHandler"/> for guidance on configuring policies.
    /// </para>
    /// </remarks>
    public static IHttpClientBuilder AddPolicyHandler(this IHttpClientBuilder builder, Func<IServiceProvider, HttpRequestMessage, string, IAsyncPolicy<HttpResponseMessage>> policyFactory, Func<HttpRequestMessage, string> keySelector)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }
 
        if (keySelector == null)
        {
            throw new ArgumentNullException(nameof(keySelector));
        }
 
        if (policyFactory == null)
        {
            throw new ArgumentNullException(nameof(policyFactory));
        }
 
        builder.AddHttpMessageHandler((services) =>
        {
            var registry = services.GetRequiredService<IPolicyRegistry<string>>();
            return new PolicyHttpMessageHandler((request) =>
            {
                var key = keySelector(request);
 
                if (registry.TryGet<IAsyncPolicy<HttpResponseMessage>>(key, out var policy))
                {
                    return policy;
                }
 
                var newPolicy = policyFactory(services, request, key);
                registry[key] = newPolicy;
                return newPolicy;
            });
        });
        return builder;
    }
}