AyCode.Core/AyCode.Services/SignalRs/AcSignalRProtocolExtensions.cs

91 lines
4.6 KiB
C#

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace AyCode.Services.SignalRs;
/// <summary>
/// Client-side registration extension for the <see cref="AyCodeBinaryHubProtocol"/> (<c>"acbinary"</c>).
/// Mirrors the ASP.NET Core idiomatic pattern of <c>AddJsonProtocol(...)</c> /
/// <c>AddMessagePackProtocol(...)</c>.
/// <para>
/// For the server-side equivalent see <c>AcSignalRServerProtocolExtensions</c> in
/// <c>AyCode.Services.Server</c> — kept separate to avoid dragging the server SignalR assembly
/// (<c>Microsoft.AspNetCore.SignalR.Core</c>) into pure client projects (MAUI, WASM).
/// </para>
/// </summary>
public static class AcSignalRProtocolExtensions
{
/// <summary>
/// Registers <see cref="AyCodeBinaryHubProtocol"/> as the protocol for a client <see cref="HubConnection"/>.
/// Resolves <see cref="AcBinaryHubProtocolOptions"/> from <see cref="IOptions{TOptions}"/> in the
/// <see cref="HubConnectionBuilder"/>'s inner DI scope.
/// <para>
/// ⚠️ <b>Limitation:</b> the outer ASP.NET Core / MAUI / WASM <c>services.Configure&lt;AcBinaryHubProtocolOptions&gt;(...)</c>
/// registrations do <i>not</i> flow into the <c>HubConnectionBuilder.Services</c> inner collection —
/// it is an isolated container. This overload therefore always falls back to default options
/// when used from a client host that uses <c>appsettings.json</c>-driven configuration.
/// Prefer <see cref="AddAcBinaryProtocol(IHubConnectionBuilder, AcBinaryHubProtocolOptions, Action{AcBinaryHubProtocolOptions})"/>
/// in that case, passing a pre-resolved <see cref="AcBinaryHubProtocolOptions"/> from the outer service provider.
/// </para>
/// </summary>
public static IHubConnectionBuilder AddAcBinaryProtocol(this IHubConnectionBuilder builder, Action<AcBinaryHubProtocolOptions>? configure = null)
{
builder.Services.AddSingleton<IHubProtocol>(sp => BuildProtocol(sp, configure));
return builder;
}
/// <summary>
/// Registers <see cref="AyCodeBinaryHubProtocol"/> for a client <see cref="HubConnection"/> using
/// an <see cref="AcBinaryHubProtocolOptions"/> instance supplied by the caller — typically resolved
/// from the outer host's <see cref="IOptions{TOptions}"/> and cloned. An optional
/// <paramref name="configure"/> action may apply last-minute overrides (e.g. attaching a logger
/// instance) on the cloned options.
/// <para>
/// Use this overload when <c>AcBinaryHubProtocolOptions</c> is bound from <c>appsettings.json</c>
/// via <c>services.Configure&lt;AcBinaryHubProtocolOptions&gt;(...)</c> in the outer host's
/// service collection — the <see cref="HubConnectionBuilder"/>'s isolated inner DI cannot see
/// those registrations, so the options must be passed explicitly.
/// </para>
/// </summary>
public static IHubConnectionBuilder AddAcBinaryProtocol(this IHubConnectionBuilder builder, AcBinaryHubProtocolOptions options, Action<AcBinaryHubProtocolOptions>? configure = null)
{
if (options is null) throw new ArgumentNullException(nameof(options));
builder.Services.AddSingleton<IHubProtocol>(sp =>
{
// Clone so callers can safely reuse their options instance across multiple connections
// without the factory's Logger fallback or configure overrides leaking back.
var opts = options.Clone();
opts.Logger ??= sp.GetService<ILogger<AcBinaryHubProtocol>>();
configure?.Invoke(opts);
opts.Validate();
return new AyCodeBinaryHubProtocol(opts);
});
return builder;
}
/// <summary>
/// Shared factory used by both client (this file) and server
/// (<c>AcSignalRServerProtocolExtensions</c> in AyCode.Services.Server).
/// Resolves options from DI (<c>IOptions&lt;T&gt;</c>), clones them, applies the inline
/// <paramref name="configure"/> override, validates, and constructs the protocol.
/// </summary>
public static IHubProtocol BuildProtocol(IServiceProvider sp, Action<AcBinaryHubProtocolOptions>? configure)
{
var diOptions = sp.GetService<IOptions<AcBinaryHubProtocolOptions>>()?.Value;
var options = diOptions?.Clone() ?? new AcBinaryHubProtocolOptions();
options.Logger ??= sp.GetService<ILogger<AcBinaryHubProtocol>>();
configure?.Invoke(options);
options.Validate();
return new AyCodeBinaryHubProtocol(options);
}
}