Refactor namespace and project structure
Replaced `DigitalData.MessagingService.Client.DependencyInjection` namespace with `DigitalData.MessagingService.Client` across the codebase to simplify and streamline the project structure. Removed unused `Microsoft.Extensions.Logging` and `System` dependencies from `EmailSender.cs`. Updated project references in `DigitalData.MessagingService.Tests.csproj` to reflect the namespace and project restructuring. Adjusted `using` directives in test files to align with the new namespace.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
using DigitalData.MessagingService.Publisher;
|
||||
using DigitalData.MessagingService.Publisher.Abstraction;
|
||||
using DigitalData.MessagingService.RabbitMQ;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace DigitalData.MessagingService.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a static, self-contained client for sending emails via RabbitMQ
|
||||
/// without requiring a host-level dependency injection container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class manages its own internal <see cref="IServiceProvider"/> using a
|
||||
/// <see cref="Lazy{T}"/> pattern so the DI container is only built once,
|
||||
/// on the first call to <see cref="ConnectRabbitMq(Action{RabbitMqConfiguration}, OnReconnect)"/>.
|
||||
/// </remarks>
|
||||
public static class EmailSender
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal event used to accumulate service registrations before the
|
||||
/// <see cref="IServiceProvider"/> is built. Handlers are added by
|
||||
/// <see cref="ConnectRabbitMq(Action{RabbitMqConfiguration}, OnReconnect)"/> and invoked exactly once during
|
||||
/// lazy initialization.
|
||||
/// </summary>
|
||||
private static event Action<IServiceCollection> ConfigureServices = delegate { };
|
||||
|
||||
/// <summary>
|
||||
/// The lazily-initialized internal service provider.
|
||||
/// Built on first access by invoking all registered
|
||||
/// <see cref="ConfigureServices"/> handlers.
|
||||
/// </summary>
|
||||
private static readonly Lazy<IServiceProvider> LazyProvider = new(() =>
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
ConfigureServices?.Invoke(services);
|
||||
return services.BuildServiceProvider();
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the messaging service has been connected
|
||||
/// and the internal <see cref="IServiceProvider"/> has been initialized.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <see langword="true"/> if <see cref="ConnectRabbitMq(Action{RabbitMqConfiguration}, OnReconnect)"/> has been called
|
||||
/// and the provider is built; otherwise <see langword="false"/>.
|
||||
/// </value>
|
||||
public static bool IsConnected => LazyProvider.IsValueCreated;
|
||||
|
||||
/// <summary>
|
||||
/// Configures and establishes a connection to RabbitMQ, then initializes
|
||||
/// the internal dependency injection container.
|
||||
/// </summary>
|
||||
/// <param name="configure">
|
||||
/// A delegate used to configure the <see cref="RabbitMqConfiguration"/>,
|
||||
/// such as host, port, credentials, and exchange settings.
|
||||
/// </param>
|
||||
/// <param name="onReconnect">
|
||||
/// Controls the behavior when this method is called while already connected.
|
||||
/// Defaults to <see cref="OnReconnect.ThrowException"/>.
|
||||
/// </param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown when the service is already connected and
|
||||
/// <paramref name="onReconnect"/> is <see cref="OnReconnect.ThrowException"/>.
|
||||
/// </exception>
|
||||
public static void ConnectRabbitMq(Action<RabbitMqConfiguration> 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures and establishes a connection to RabbitMQ using a URL, then initializes
|
||||
/// the internal dependency injection container.
|
||||
/// </summary>
|
||||
/// <param name="url">
|
||||
/// The RabbitMQ server URL (e.g. <c>amqp://hostname:5672/virtualhost</c>).
|
||||
/// The host, port, and virtual host are extracted from this URL.
|
||||
/// </param>
|
||||
/// <param name="username">The username used to authenticate with RabbitMQ.</param>
|
||||
/// <param name="password">The password used to authenticate with RabbitMQ.</param>
|
||||
/// <param name="onReconnect">
|
||||
/// Controls the behavior when this method is called while already connected.
|
||||
/// Defaults to <see cref="OnReconnect.ThrowException"/>.
|
||||
/// </param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown when the service is already connected and
|
||||
/// <paramref name="onReconnect"/> is <see cref="OnReconnect.ThrowException"/>.
|
||||
/// </exception>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues the specified email event to the RabbitMQ messaging pipeline.
|
||||
/// </summary>
|
||||
/// <param name="email">The outgoing email event to enqueue.</param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown when <see cref="ConnectRabbitMq(Action{RabbitMqConfiguration}, OnReconnect)"/> has not been called prior to sending.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// This method resolves <see cref="IOutgoingEmailPublisher"/> from the internal
|
||||
/// service provider and calls <c>EnqueueAsync</c> in a fire-and-forget manner.
|
||||
/// Ensure that any unhandled exceptions from the async operation are handled
|
||||
/// at the publisher level.
|
||||
/// </remarks>
|
||||
public static void Send(OutgoingEmailEvent email)
|
||||
{
|
||||
if(!IsConnected)
|
||||
throw new InvalidOperationException("Messaging service is not connected. Call ConnectRabbitMq first.");
|
||||
|
||||
var publisher = LazyProvider.Value.GetRequiredService<IOutgoingEmailPublisher>();
|
||||
publisher.EnqueueAsync(email);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user