using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using ZB.MOM.WW.ScadaBridge.Communication.Grpc; namespace ZB.MOM.WW.ScadaBridge.Communication; public static class ServiceCollectionExtensions { /// Registers communication services including options, , gRPC client factory, and debug stream. /// The DI service collection to register services into. /// The same to allow chaining. public static IServiceCollection AddCommunication(this IServiceCollection services) { // Canonical section is ScadaBridge:Communication — the section every real // appsettings actually writes (arch-review 08 round 2 NF8). The Host used to // supply the real values via a duplicate Configure<> binding; that duplicate is // gone, so AddCommunication must bind the canonical section itself. services.AddOptions() .BindConfiguration("ScadaBridge:Communication") .ValidateOnStart(); services.TryAddEnumerable( ServiceDescriptor.Singleton, CommunicationOptionsValidator>()); services.AddSingleton(); // Explicit factory rather than AddSingleton(): the ISitePskProvider dependency is // optional (central registers one, a site node does not), and constructor selection // over a nullable interface parameter is exactly the case the container cannot decide // for itself — GetService returns null cleanly where constructor injection would throw. services.AddSingleton(sp => new SiteStreamGrpcClientFactory( sp.GetRequiredService(), sp.GetRequiredService>(), sp.GetService())); services.AddSingleton(); // Aggregated live alarm cache (plan #10, Task 4): transient, in-memory, shared // per-site aggregator backing the operator Alarm Summary page. Singleton so every // Blazor circuit shares one aggregator per site; started/stopped by viewer // reference-count. No persistence (locked [PERM]: no central alarm store). services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService()); // Startup reconciliation handler — scoped (holds scoped repositories), resolved // per-request by CentralCommunicationActor inside a DI scope. Harmless on site // hosts: only the central actor ever resolves it. services.AddScoped(); return services; } /// Hook for registering additional DI services needed by communication actors; actor creation itself happens inside AkkaHostedService. /// The DI service collection to register services into. /// The same to allow chaining. public static IServiceCollection AddCommunicationActors(this IServiceCollection services) { // Actor registration happens in AkkaHostedService.RegisterCentralActors/RegisterSiteActors. // This method is a hook for any additional DI registrations needed by the communication actors. return services; } }