File: HttpMessageHandlerBuilder.cs
Web Access
Project: src\src\libraries\Microsoft.Extensions.Http\src\Microsoft.Extensions.Http.csproj (Microsoft.Extensions.Http)
// 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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
 
namespace Microsoft.Extensions.Http
{
    /// <summary>
    /// A builder abstraction for configuring <see cref="HttpMessageHandler"/> instances.
    /// </summary>
    /// <remarks>
    /// The <see cref="HttpMessageHandlerBuilder"/> is registered in the service collection as
    /// a transient service. Callers should retrieve a new instance for each <see cref="HttpMessageHandler"/> to
    /// be created. Implementors should expect each instance to be used a single time.
    /// </remarks>
    public abstract class HttpMessageHandlerBuilder
    {
        /// <summary>
        /// Gets or sets the name of the <see cref="HttpClient"/> being created.
        /// </summary>
        /// <remarks>
        /// The <see cref="Name"/> is set by the <see cref="IHttpClientFactory"/> infrastructure
        /// and is public for unit testing purposes only. Setting the <see cref="Name"/> outside of
        /// testing scenarios may have unpredictable results.
        /// </remarks>
        [DisallowNull]
        public abstract string? Name { get; set; }
 
        /// <summary>
        /// Gets or sets the primary <see cref="HttpMessageHandler"/>.
        /// </summary>
        public abstract HttpMessageHandler PrimaryHandler { get; set; }
 
        /// <summary>
        /// Gets a list of additional <see cref="DelegatingHandler"/> instances used to configure an
        /// <see cref="HttpClient"/> pipeline.
        /// </summary>
        public abstract IList<DelegatingHandler> AdditionalHandlers { get; }
 
        /// <summary>
        /// Gets an <see cref="IServiceProvider"/> which can be used to resolve services
        /// from the dependency injection container.
        /// </summary>
        /// <remarks>
        /// This property is sensitive to the value of
        /// <see cref="HttpClientFactoryOptions.SuppressHandlerScope"/>. If <c>true</c> this
        /// property will be a reference to the application's root service provider. If <c>false</c>
        /// (default) this will be a reference to a scoped service provider that has the same
        /// lifetime as the handler being created.
        /// </remarks>
        public virtual IServiceProvider Services { get; } = null!;
 
        /// <summary>
        /// Creates an <see cref="HttpMessageHandler"/>.
        /// </summary>
        /// <returns>
        /// An <see cref="HttpMessageHandler"/> built from the <see cref="PrimaryHandler"/> and
        /// <see cref="AdditionalHandlers"/>.
        /// </returns>
        public abstract HttpMessageHandler Build();
 
        /// <summary>
        /// Constructs an instance of <see cref="HttpMessageHandler"/> by chaining <paramref name="additionalHandlers"/> one after another with <paramref name="primaryHandler"/> in the
        /// end of the chain. The resulting pipeline is used by <see cref="IHttpClientFactory"/> infrastructure to create <see cref="HttpClient"/> instances with customized message
        /// handlers. The resulting pipeline can also be accessed by using <see cref="IHttpMessageHandlerFactory"/> instead of <see cref="IHttpClientFactory"/>.
        /// </summary>
        /// <param name="primaryHandler">An instance of <see cref="HttpMessageHandler"/> to operate at the bottom of the handler chain and actually handle the HTTP transport operations.</param>
        /// <param name="additionalHandlers">An ordered list of <see cref="DelegatingHandler"/> instances to be invoked as part
        /// of sending an <see cref="HttpRequestMessage"/> and receiving an <see cref="HttpResponseMessage"/>.
        /// The handlers are invoked in a top-down fashion. That is, the first entry is invoked first for
        /// an outbound request message but last for an inbound response message.</param>
        /// <returns>The HTTP message handler chain.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="primaryHandler "/> or <paramref name="additionalHandlers "/> is <see langword="null"/>.</exception>
        /// <exception cref="InvalidOperationException"><paramref name="additionalHandlers "/> contains a <see langword="null"/> entry.
        /// -or-
        /// The <c>DelegatingHandler.InnerHandler</c> property must be <see langword="null"/>. <c>DelegatingHandler</c> instances provided to <c>HttpMessageHandlerBuilder</c> must not be reused or cached.</exception>
        protected internal static HttpMessageHandler CreateHandlerPipeline(HttpMessageHandler primaryHandler, IEnumerable<DelegatingHandler> additionalHandlers)
        {
            ThrowHelper.ThrowIfNull(primaryHandler);
            ThrowHelper.ThrowIfNull(additionalHandlers);
 
            // This is similar to https://github.com/aspnet/AspNetWebStack/blob/master/src/System.Net.Http.Formatting/HttpClientFactory.cs#L58
            // but we don't want to take that package as a dependency.
 
            IReadOnlyList<DelegatingHandler> additionalHandlersList = additionalHandlers as IReadOnlyList<DelegatingHandler> ?? additionalHandlers.ToArray();
 
            HttpMessageHandler next = primaryHandler;
            for (int i = additionalHandlersList.Count - 1; i >= 0; i--)
            {
                DelegatingHandler handler = additionalHandlersList[i];
                if (handler == null)
                {
                    string message = SR.Format(SR.HttpMessageHandlerBuilder_AdditionalHandlerIsNull, nameof(additionalHandlers));
                    throw new InvalidOperationException(message);
                }
 
                // Checking for this allows us to catch cases where someone has tried to re-use a handler. That really won't
                // work the way you want and it can be tricky for callers to figure out.
                if (handler.InnerHandler != null)
                {
                    string message = SR.Format(SR.HttpMessageHandlerBuilder_AdditionHandlerIsInvalid,
                        nameof(DelegatingHandler.InnerHandler),
                        nameof(DelegatingHandler),
                        nameof(HttpMessageHandlerBuilder),
                        Environment.NewLine,
                        handler);
                    throw new InvalidOperationException(message);
                }
 
                handler.InnerHandler = next;
                next = handler;
            }
 
            return next;
        }
    }
}