Refactor and enhance static ReCClient configuration
Introduced a new `BuildStaticClient(Action<StaticBuildConfiguration>)` method for flexible and detailed static `IServiceProvider` configuration. Added the `StaticBuildConfiguration` class to encapsulate optional settings like `BaseAddress`, `ConfigureClient`, `Logger`, and more. Refactored existing `BuildStaticClient` overloads to use the new method, ensuring consistency and reducing duplication. Added support for optional `ILogger` instances and improved validation to enforce proper configuration. Marked existing `BuildStaticClient` methods as obsolete, recommending the new method. Enhanced thread-safety using `Interlocked.CompareExchange`. Updated XML documentation and added conditional compilation for `NETFRAMEWORK` compatibility. These changes improve maintainability, usability, and alignment with modern .NET practices.
This commit is contained in:
@@ -101,6 +101,53 @@ namespace ReC.Client
|
|||||||
return services.BuildServiceProvider();
|
return services.BuildServiceProvider();
|
||||||
}, System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);
|
}, System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures and builds the static <see cref="IServiceProvider"/> for creating <see cref="ReCClient"/> instances.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method should only be called once during application startup. The underlying
|
||||||
|
/// <see cref="IServiceProvider"/> is created lazily and thread-safely on first access via <see cref="Create"/>.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="configure">Callback that populates a <see cref="StaticBuildConfiguration"/> instance.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="configure"/> is null.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown when neither <see cref="StaticBuildConfiguration.BaseAddress"/> nor <see cref="StaticBuildConfiguration.ConfigureClient"/> is set, when both are set, or when the static provider has already been built.</exception>
|
||||||
|
[Obsolete("Use a local service collection instead of the static provider.")]
|
||||||
|
public static void BuildStaticClient(Action<StaticBuildConfiguration> configure)
|
||||||
|
{
|
||||||
|
if (configure == null)
|
||||||
|
throw new ArgumentNullException(nameof(configure));
|
||||||
|
|
||||||
|
var cfg = new StaticBuildConfiguration();
|
||||||
|
configure(cfg);
|
||||||
|
|
||||||
|
var hasBaseAddress = !string.IsNullOrWhiteSpace(cfg.BaseAddress);
|
||||||
|
var hasConfigureClient = cfg.ConfigureClient != null;
|
||||||
|
|
||||||
|
if (!hasBaseAddress && !hasConfigureClient)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Either {nameof(StaticBuildConfiguration.BaseAddress)} or {nameof(StaticBuildConfiguration.ConfigureClient)} must be set on {nameof(StaticBuildConfiguration)}.");
|
||||||
|
|
||||||
|
if (hasBaseAddress && hasConfigureClient)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"{nameof(StaticBuildConfiguration.BaseAddress)} and {nameof(StaticBuildConfiguration.ConfigureClient)} are mutually exclusive on {nameof(StaticBuildConfiguration)}.");
|
||||||
|
|
||||||
|
Action<IServiceCollection> register = services =>
|
||||||
|
{
|
||||||
|
if (hasBaseAddress)
|
||||||
|
services.AddRecClient(cfg.BaseAddress, cfg.ConfigureOptions);
|
||||||
|
else
|
||||||
|
services.AddRecClient(cfg.ConfigureClient, cfg.ConfigureOptions);
|
||||||
|
|
||||||
|
if (cfg.Logger != null)
|
||||||
|
services.AddSingleton(cfg.Logger);
|
||||||
|
|
||||||
|
cfg.ConfigureServices?.Invoke(services);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (System.Threading.Interlocked.CompareExchange(ref _staticConfigure, register, null) != null)
|
||||||
|
throw new InvalidOperationException("Static Provider is already built.");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures and builds the static <see cref="IServiceProvider"/> for creating <see cref="ReCClient"/> instances.
|
/// Configures and builds the static <see cref="IServiceProvider"/> for creating <see cref="ReCClient"/> instances.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -110,17 +157,21 @@ namespace ReC.Client
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="apiUri">The base URI of the ReC API.</param>
|
/// <param name="apiUri">The base URI of the ReC API.</param>
|
||||||
/// <param name="configureOptions">An optional callback to configure <see cref="ReCClientOptions"/>.</param>
|
/// <param name="configureOptions">An optional callback to configure <see cref="ReCClientOptions"/>.</param>
|
||||||
|
/// <param name="logger">An optional <see cref="ILogger"/> instance to be used by the <see cref="ReCClient"/>. When provided, it is registered as a singleton in the internal service collection.</param>
|
||||||
/// <exception cref="InvalidOperationException">Thrown if the static provider has already been built.</exception>
|
/// <exception cref="InvalidOperationException">Thrown if the static provider has already been built.</exception>
|
||||||
[Obsolete("Use a local service collection instead of the static provider.")]
|
[Obsolete("Use BuildStaticClient(Action<StaticBuildConfiguration>) instead.")]
|
||||||
#if NETFRAMEWORK
|
#if NETFRAMEWORK
|
||||||
public static void BuildStaticClient(string apiUri, Action<ReCClientOptions> configureOptions = null)
|
public static void BuildStaticClient(string apiUri, Action<ReCClientOptions> configureOptions = null, ILogger logger = null)
|
||||||
#else
|
#else
|
||||||
public static void BuildStaticClient(string apiUri, Action<ReCClientOptions>? configureOptions = null)
|
public static void BuildStaticClient(string apiUri, Action<ReCClientOptions>? configureOptions = null, ILogger<ReCClient>? logger = null)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
Action<IServiceCollection> configure = services => services.AddRecClient(apiUri, configureOptions);
|
BuildStaticClient(cfg =>
|
||||||
if (System.Threading.Interlocked.CompareExchange(ref _staticConfigure, configure, null) != null)
|
{
|
||||||
throw new InvalidOperationException("Static Provider is already built.");
|
cfg.BaseAddress = apiUri;
|
||||||
|
cfg.ConfigureOptions = configureOptions;
|
||||||
|
cfg.Logger = logger;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -132,17 +183,21 @@ namespace ReC.Client
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="configureClient">An action to configure the <see cref="HttpClient"/>.</param>
|
/// <param name="configureClient">An action to configure the <see cref="HttpClient"/>.</param>
|
||||||
/// <param name="configureOptions">An optional callback to configure <see cref="ReCClientOptions"/>.</param>
|
/// <param name="configureOptions">An optional callback to configure <see cref="ReCClientOptions"/>.</param>
|
||||||
|
/// <param name="logger">An optional <see cref="ILogger"/> instance to be used by the <see cref="ReCClient"/>. When provided, it is registered as a singleton in the internal service collection.</param>
|
||||||
/// <exception cref="InvalidOperationException">Thrown if the static provider has already been built.</exception>
|
/// <exception cref="InvalidOperationException">Thrown if the static provider has already been built.</exception>
|
||||||
[Obsolete("Use a local service collection instead of the static provider.")]
|
[Obsolete("Use BuildStaticClient(Action<StaticBuildConfiguration>) instead.")]
|
||||||
#if NETFRAMEWORK
|
#if NETFRAMEWORK
|
||||||
public static void BuildStaticClient(Action<HttpClient> configureClient, Action<ReCClientOptions> configureOptions = null)
|
public static void BuildStaticClient(Action<HttpClient> configureClient, Action<ReCClientOptions> configureOptions = null, ILogger logger = null)
|
||||||
#else
|
#else
|
||||||
public static void BuildStaticClient(Action<HttpClient> configureClient, Action<ReCClientOptions>? configureOptions = null)
|
public static void BuildStaticClient(Action<HttpClient> configureClient, Action<ReCClientOptions>? configureOptions = null, ILogger<ReCClient>? logger = null)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
Action<IServiceCollection> configure = services => services.AddRecClient(configureClient, configureOptions);
|
BuildStaticClient(cfg =>
|
||||||
if (System.Threading.Interlocked.CompareExchange(ref _staticConfigure, configure, null) != null)
|
{
|
||||||
throw new InvalidOperationException("Static Provider is already built.");
|
cfg.ConfigureClient = configureClient;
|
||||||
|
cfg.ConfigureOptions = configureOptions;
|
||||||
|
cfg.Logger = logger;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
63
src/ReC.Client/StaticBuildConfiguration.cs
Normal file
63
src/ReC.Client/StaticBuildConfiguration.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace ReC.Client
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration object for <see cref="ReCClient.BuildStaticClient(Action{StaticBuildConfiguration})"/>.
|
||||||
|
/// Groups all optional settings for the static <see cref="ReCClient"/> bootstrap path.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Either <see cref="BaseAddress"/> or <see cref="ConfigureClient"/> must be set; setting both at the same time is not allowed.
|
||||||
|
/// </remarks>
|
||||||
|
public class StaticBuildConfiguration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base URI of the ReC API. Mutually exclusive with <see cref="ConfigureClient"/>.
|
||||||
|
/// </summary>
|
||||||
|
#if NETFRAMEWORK
|
||||||
|
public string BaseAddress { get; set; }
|
||||||
|
#else
|
||||||
|
public string? BaseAddress { get; set; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback that configures the underlying <see cref="HttpClient"/>. Mutually exclusive with <see cref="BaseAddress"/>.
|
||||||
|
/// </summary>
|
||||||
|
#if NETFRAMEWORK
|
||||||
|
public Action<HttpClient> ConfigureClient { get; set; }
|
||||||
|
#else
|
||||||
|
public Action<HttpClient>? ConfigureClient { get; set; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional callback to configure <see cref="ReCClientOptions"/>.
|
||||||
|
/// </summary>
|
||||||
|
#if NETFRAMEWORK
|
||||||
|
public Action<ReCClientOptions> ConfigureOptions { get; set; }
|
||||||
|
#else
|
||||||
|
public Action<ReCClientOptions>? ConfigureOptions { get; set; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional logger instance to be registered as a singleton in the internal service collection.
|
||||||
|
/// </summary>
|
||||||
|
#if NETFRAMEWORK
|
||||||
|
public ILogger Logger { get; set; }
|
||||||
|
#else
|
||||||
|
public ILogger<ReCClient>? Logger { get; set; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional callback for additional service registrations on the internal <see cref="IServiceCollection"/>
|
||||||
|
/// (e.g. <c>services.AddLogging(...)</c> or custom dependencies).
|
||||||
|
/// </summary>
|
||||||
|
#if NETFRAMEWORK
|
||||||
|
public Action<IServiceCollection> ConfigureServices { get; set; }
|
||||||
|
#else
|
||||||
|
public Action<IServiceCollection>? ConfigureServices { get; set; }
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user