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 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());
return services;
}
///
/// Registers the communication services that only a CENTRAL node can construct.
/// Call alongside from the Central composition root only.
///
///
///
/// is scoped and takes IDeploymentManagerRepository —
/// an EF repository registered by AddConfigurationDatabase, which only central calls.
/// It used to live in the shared on the argument that it was
/// "harmless on site hosts: only the central actor ever resolves it". That is true of the
/// runtime behaviour but not of the container: the registration was UNCONSTRUCTIBLE on a
/// site node, so ValidateOnBuild failed the whole site graph and the site branch
/// could not have scope validation turned on at all (deferred register row 50). A
/// registration nothing can ever build is not harmless — it is the thing that stops the
/// validator from being usable, and it hides whatever real defect the validator would
/// otherwise have found.
///
///
/// The DI service collection to register services into.
/// The same to allow chaining.
public static IServiceCollection AddCentralCommunication(this IServiceCollection services)
{
// Startup reconciliation handler — scoped (holds scoped repositories), resolved
// per-request by CentralCommunicationActor inside a DI scope.
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;
}
}