using DigitalData.MessagingService.Publisher; using DigitalData.MessagingService.Abstraction; using DigitalData.MessagingService.RabbitMQ; using Microsoft.Extensions.DependencyInjection; namespace DigitalData.MessagingService.Client; /// /// Provides a static, self-contained client for sending emails via RabbitMQ /// without requiring a host-level dependency injection container. /// /// /// This class manages its own internal using a /// pattern so the DI container is only built once, /// on the first call to . /// public static class EmailSender { /// /// Internal event used to accumulate service registrations before the /// is built. Handlers are added by /// and invoked exactly once during /// lazy initialization. /// private static event Action ConfigureServices = delegate { }; /// /// The lazily-initialized internal service provider. /// Built on first access by invoking all registered /// handlers. /// private static readonly Lazy LazyProvider = new(() => { var services = new ServiceCollection(); services.AddLogging(); ConfigureServices?.Invoke(services); return services.BuildServiceProvider(); }); /// /// Gets a value indicating whether the messaging service has been connected /// and the internal has been initialized. /// /// /// if has been called /// and the provider is built; otherwise . /// public static bool IsConnected => LazyProvider.IsValueCreated; /// /// Configures and establishes a connection to RabbitMQ, then initializes /// the internal dependency injection container. /// /// /// A delegate used to configure the , /// such as host, port, credentials, and exchange settings. /// /// /// Controls the behavior when this method is called while already connected. /// Defaults to . /// /// /// Thrown when the service is already connected and /// is . /// public static void ConnectRabbitMq(Action configure, OnReconnect onReconnect = OnReconnect.ThrowException) { if(IsConnected && onReconnect == OnReconnect.ThrowException) throw new InvalidOperationException("Messaging service is already connected."); ConfigureServices += services => services.AddMessagingServicePublisher(configure); _ = LazyProvider.Value; // Force initialization } /// /// Configures and establishes a connection to RabbitMQ using a URL, then initializes /// the internal dependency injection container. /// /// /// The RabbitMQ server URL (e.g. amqp://hostname:5672/virtualhost). /// The host, port, and virtual host are extracted from this URL. /// /// The username used to authenticate with RabbitMQ. /// The password used to authenticate with RabbitMQ. /// /// Controls the behavior when this method is called while already connected. /// Defaults to . /// /// /// Thrown when the service is already connected and /// is . /// public static void ConnectRabbitMq(string url, string username, string password, OnReconnect onReconnect = OnReconnect.ThrowException) { var uri = new Uri(url); ConnectRabbitMq(cfg => { cfg.HostName = uri.Host; cfg.Port = uri.IsDefaultPort ? 5672 : uri.Port; cfg.UserName = username; cfg.Password = password; if (!string.IsNullOrEmpty(uri.AbsolutePath) && uri.AbsolutePath != "/") cfg.VirtualHost = Uri.UnescapeDataString(uri.AbsolutePath.TrimStart('/')); }, onReconnect); } /// /// Enqueues the specified email to the RabbitMQ messaging pipeline. /// /// The outgoing email data to enqueue. /// /// Thrown when has not been called prior to sending. /// /// /// This method maps to , /// then resolves from the internal /// service provider and calls EnqueueAsync in a fire-and-forget manner. /// Ensure that any unhandled exceptions from the async operation are handled /// at the publisher level. /// public static void Send(Email email) { if(!IsConnected) throw new InvalidOperationException("Messaging service is not connected. Call ConnectRabbitMq first."); var publisher = LazyProvider.Value.GetRequiredService(); publisher.EnqueueAsync(new SendingEmailEvent() { Id = Guid.NewGuid(), Mail = email, QueuedAt = DateTime.Now }); } }