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(EmailContext 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
});
}
}