Files
ScadaBridge/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/ServiceCollectionExtensions.cs
T
Joseph Doherty 9cff87fe85 docs(comments): strip internal task/milestone/bundle bookkeeping from code comments
Remove project bookkeeping citations from shipped code comments across the
solution: hyphenated task IDs (WP-14, StoreAndForward-025), milestone/task/
issue refs (M3, Task 4, Audit Log #23, #21), Bundle X task-bundle labels,
and C/D/K/S/T phase labels.

Comment text only — no code logic, string/log literals, or XML-doc structure
changed. Genuine descriptions are preserved (only the citation is stripped),
and technical lookalikes are retained (UTF-8, SHA-256, T00:00:00, M365,
UTC-5, pre-C4/pre-C5 schema versions). Flagged by the new CommentChecker
TaskReferenceInComment / TrackingReferenceInComment checks plus targeted
grep passes; full solution builds clean, append-only guard tests pass.
2026-07-07 11:03:26 -04:00

92 lines
5.2 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Kpi;
using ZB.MOM.WW.ScadaBridge.NotificationOutbox.Delivery;
using ZB.MOM.WW.ScadaBridge.NotificationOutbox.Kpi;
namespace ZB.MOM.WW.ScadaBridge.NotificationOutbox;
/// <summary>
/// DI registration for the Notification Outbox component: binds
/// <see cref="NotificationOutboxOptions"/> and registers the channel delivery adapters.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>Configuration section bound to <see cref="NotificationOutboxOptions"/>.</summary>
public const string OptionsSection = "ScadaBridge:NotificationOutbox";
/// <summary>Configuration section bound to <see cref="SmsOptions"/>.</summary>
public const string SmsOptionsSection = "ScadaBridge:Sms";
/// <summary>
/// Registers the Notification Outbox services: the <see cref="NotificationOutboxOptions"/>
/// binding and the channel delivery adapters.
///
/// This extension covers only the outbox-specific registrations. The
/// <see cref="EmailNotificationDeliveryAdapter"/> reuses the
/// <see cref="ZB.MOM.WW.ScadaBridge.NotificationService"/> SMTP machinery —
/// <c>Func&lt;ISmtpClientWrapper&gt;</c>, <c>OAuth2TokenService</c> and
/// <c>NotificationOptions</c> — so the caller (the Host on the central node) must also
/// call <c>AddNotificationService()</c>. Re-registering those services here would
/// duplicate them; relying on <c>AddNotificationService</c> keeps a single source of truth.
///
/// <see cref="EmailNotificationDeliveryAdapter"/> is registered <em>scoped</em> because it
/// takes a scoped <see cref="ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories.INotificationRepository"/>
/// directly. The <see cref="NotificationOutboxActor"/> resolves the adapters from a fresh
/// scope per dispatch sweep rather than holding them, so no scoped adapter is captured by
/// the singleton actor.
///
/// The <see cref="SmsNotificationDeliveryAdapter"/> (Twilio-REST) is registered the same
/// way alongside the Email adapter, so both resolve from
/// <c>GetServices&lt;INotificationDeliveryAdapter&gt;()</c>. It also binds + validates
/// <see cref="SmsOptions"/> (<c>ScadaBridge:Sms</c>) and the named <c>"Twilio"</c>
/// <see cref="HttpClient"/>; unlike the Email adapter it needs no NotificationService
/// machinery (a single outbound HTTPS POST per recipient).
/// </summary>
/// <param name="services">The DI service collection to register notification outbox services into.</param>
/// <returns>The same <paramref name="services"/> instance for chaining.</returns>
public static IServiceCollection AddNotificationOutbox(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services.AddOptions<NotificationOutboxOptions>()
.BindConfiguration(OptionsSection);
// Bind + validate SmsOptions (ScadaBridge:Sms): the SMS adapter's body cap and
// per-request timeout fallback. ValidateOnStart surfaces a bad option at host
// startup rather than at first delivery; the validator is registered via
// TryAddEnumerable so a re-call of AddNotificationOutbox does not double-register it.
services.AddOptions<SmsOptions>()
.BindConfiguration(SmsOptionsSection)
.ValidateOnStart();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IValidateOptions<SmsOptions>, SmsOptionsValidator>());
// Named HTTP client backing the Twilio-REST SMS adapter. Pooled handler lifetime
// is managed by IHttpClientFactory; the adapter applies the per-request timeout
// via a linked CTS (so the client's own Timeout stays at its default).
services.AddHttpClient(SmsNotificationDeliveryAdapter.HttpClientName);
// Scoped: the adapters hold a scoped INotificationRepository. Each is registered
// both under the interface (so the dispatch sweep can enumerate every channel
// adapter — both Email and SMS must resolve from GetServices<INotificationDeliveryAdapter>())
// and as the concrete type (so callers and tests can resolve it directly).
services.AddScoped<EmailNotificationDeliveryAdapter>();
services.AddScoped<INotificationDeliveryAdapter>(
sp => sp.GetRequiredService<EmailNotificationDeliveryAdapter>());
services.AddScoped<SmsNotificationDeliveryAdapter>();
services.AddScoped<INotificationDeliveryAdapter>(
sp => sp.GetRequiredService<SmsNotificationDeliveryAdapter>());
// KPI history: the recorder singleton enumerates every IKpiSampleSource each
// sampling pass to snapshot the outbox delivery KPIs into the central history store.
// TryAddEnumerable is idempotent — no double-registration if AddNotificationOutbox
// is ever called more than once, matching the AuditLog idiom.
services.TryAddEnumerable(ServiceDescriptor.Scoped<IKpiSampleSource, NotificationOutboxKpiSampleSource>());
return services;
}
}