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