Description
Original issue by @tekian
Background and motivation
The purpose of the new API, represented by the HttpClientSocketHandlingExtensions
, SocketsHttpHandlerBuilder
and SocketsHttpHandlerOptions
classes, is to provide a more convenient and fluent way to configure SocketsHttpHandler
instances for named HttpClient
instances in applications that use dependency injection and the IHttpClientFactory
.
API Proposal
public class SocketsHttpHandlerOptions
{
public bool AllowAutoRedirect { get; set; }
public bool UseCookies { get; set; }
public int MaxConnectionsPerServer { get; set; }
public DecompressionMethods AutomaticDecompression { get; set; }
public TimeSpan ConnectTimeout { get; set; }
public TimeSpan PooledConnectionLifetime { get; set; }
public TimeSpan PooledConnectionIdleTimeout { get; set; }
#if NET5_0_OR_GREATER
public TimeSpan KeepAlivePingDelay { get; set; }
public TimeSpan KeepAlivePingTimeout { get; set; }
#endif
}
public class SocketsHttpHandlerBuilder
{
public string Name { get; }
public IServiceCollection Services { get; }
public SocketsHttpHandlerBuilder ConfigureHandler(Action<SocketsHttpHandler> configure) {}
public SocketsHttpHandlerBuilder ConfigureHandler(Action<IServiceProvider, SocketsHttpHandler> configure) {}
public SocketsHttpHandlerBuilder ConfigureOptions(IConfigurationSection section) {}
public SocketsHttpHandlerBuilder ConfigureOptions(Action<SocketsHttpHandlerOptions> configure) {}
}
public static class HttpClientSocketHandlingExtensions
{
public static IHttpClientBuilder AddSocketsHttpHandler(this IHttpClientBuilder builder) {}
public static IHttpClientBuilder AddSocketsHttpHandler(this IHttpClientBuilder builder, Action<SocketsHttpHandlerBuilder> configure) {}
}
API Usage
{
"HttpClientSettings": {
"MyClient": {
"AllowAutoRedirect": true,
"UseCookies": true,
"ConnectTimeout": "00:00:05"
}
}
}
public void ConfigureServices(IServiceCollection services)
{
IConfiguration configuration = ...;
services
.AddHttpClient("MyClient")
.AddSocketsHttpHandler(builder =>
{
builder.ConfigureOptions(configuration.GetSection("HttpClientSettings:MyClient"));
});
}
Additionally, we could consider also adding following convenience methods:
public class SocketsHttpHandlerBuilder
{
public SocketsHttpHandlerBuilder ConfigureClientCertificate(Func<IServiceProvider, X509Certificate2> clientCertificate) { ...}
public SocketsHttpHandlerBuilder DisableRemoteCertificateValidation() { ... }
}
....either in this form or as an extension methods.
Alternative Designs
No response
Risks
No response
Background and motivation
The purpose of the new API, represented by the ISocketsHttpHandlerBuilder
, SocketsHttpHandlerBuilderExtensions
and UseSocketsHttpHandler
, is to provide a more convenient and fluent way to configure SocketsHttpHandler
instances for named HttpClient
instances in applications that use dependency injection and the IHttpClientFactory
. One of the main asks is to be able to configure SocketsHttpHandler
via a configuration file.
This is a convenience API for some of the most widely used basic scenarios investigated by dotnet/extensions team.
While it is possible to achieve the same result by existing methods, the API significantly simplifies it in the common scenarios.
Note: this API is .NET 5+ only.
API Proposal
namespace Microsoft.Extensions.DependencyInjection;
// existing
public static class HttpClientBuilderExtensions
{
#if NET5_0_OR_GREATER
// new
[UnsupportedOSPlatform("browser")]
public static IHttpClientBuilder UseSocketsHttpHandler(this IHttpClientBuilder builder,
Action<SocketsHttpHandler, IServiceProvider>? configureHandler = null) {}
[UnsupportedOSPlatform("browser")]
public static IHttpClientBuilder UseSocketsHttpHandler(this IHttpClientBuilder builder,
Action<ISocketsHttpHandlerBuilder> configureBuilder) {}
#endif
// existing (+2 overloads)
// public static IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this IHttpClientBuilder builder, Func<HttpMessageHandler> configureHandler) {}
}
#if NET5_0_OR_GREATER
// new
public interface ISocketsHttpHandlerBuilder
{
string Name { get; }
IServiceCollection Services { get; }
}
// new
public static class SocketsHttpHandlerBuilderExtensions
{
[UnsupportedOSPlatform("browser")]
public static ISocketsHttpHandlerBuilder Configure(this ISocketsHttpHandlerBuilder builder,
Action<SocketsHttpHandler, IServiceProvider> configure) {}
[UnsupportedOSPlatform("browser")]
public static ISocketsHttpHandlerBuilder Configure(this ISocketsHttpHandlerBuilder builder,
IConfigurationSection configurationSection) {}
}
#endif
API Usage
// uses SocketsHttpHandler as a primary handler
services.AddHttpClient("foo")
.UseSocketsHttpHandler();
// sets up properties on the handler directly
services.AddHttpClient("bar")
.UseSocketsHttpHandler((handler, _) =>
{
handler.PooledConnectionLifetime = TimeSpan.FromMinutes(2);
handler.UseCookies = false;
handler.MaxConnectionsPerServer = 1;
});
// loads properties from config file and chains setting up SslOptions via builder
services.AddHttpClient("baz")
.UseSocketsHttpHandler(builder =>
builder.Configure(configuration.GetSection("HttpClientSettings:baz"))
.Configure((handler, _) => handler.SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = delegate { return true; },
})
);
{
"HttpClientSettings": {
"baz": {
"AllowAutoRedirect": true,
"UseCookies": false,
"ConnectTimeout": "00:00:05"
}
}
}
Illustrative only: additional extension methods
services.AddHttpClient("qux")
.UseSocketsHttpHandler(builder =>
builder.Configure(configuration.GetSection("HttpClientSettings:qux"))
.SetMaxConnectionsPerServer(1)
.DisableRemoteCertificateValidation()
);
// e.g. added by user or a 3rd party lib
public static class MyFluentSocketsHttpHandlerBuilderExtensions
{
public static ISocketsHttpHandlerBuilder DisableRemoteCertificateValidation(this ISocketsHttpHandlerBuilder builder, bool allowAutoRedirect) {}
public static ISocketsHttpHandlerBuilder ConfigureClientCertificate(Func<IServiceProvider, X509Certificate> clientCertificate) {}
// ...
public static ISocketsHttpHandlerBuilder SetAllowAutoRedirect(this ISocketsHttpHandlerBuilder builder, bool allowAutoRedirect) {}
public static ISocketsHttpHandlerBuilder SetUseCookies(this ISocketsHttpHandlerBuilder builder, bool useCookies) {}
public static ISocketsHttpHandlerBuilder SetMaxConnectionsPerServer(this ISocketsHttpHandlerBuilder builder, int maxConnectionsPerServer) {}
public static ISocketsHttpHandlerBuilder SetConnectTimeout(this ISocketsHttpHandlerBuilder builder, TimeSpan connectTimeout) {}
public static ISocketsHttpHandlerBuilder SetPooledConnectionLifetime(this ISocketsHttpHandlerBuilder builder, TimeSpan pooledConnectionLifetime) {}
public static ISocketsHttpHandlerBuilder SetPooledConnectionIdleTimeout(this ISocketsHttpHandlerBuilder builder, TimeSpan pooledConnectionIdleTimeout) {}
public static ISocketsHttpHandlerBuilder SetKeepAlivePingDelay(this ISocketsHttpHandlerBuilder builder, TimeSpan keepAlivePingDelay) {}
public static ISocketsHttpHandlerBuilder SetKeepAlivePingTimeout(this ISocketsHttpHandlerBuilder builder, TimeSpan keepAlivePingTimeout) {}
// ...
}