diff --git a/Documents/GeneratedContext/xDashboard-xEventService-20260819_112307.txt b/Documents/GeneratedContext/xDashboard-xEventService-20260819_112307.txt
new file mode 100644
index 0000000..1f7b2ee
--- /dev/null
+++ b/Documents/GeneratedContext/xDashboard-xEventService-20260819_112307.txt
@@ -0,0 +1,1584 @@
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Configuration\XEventServiceConfiguration.cs
+
+using System.Collections.Generic;
+using RabbitMQ.Client;
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Configuration
+{
+ ///
+ /// a configuration class for describing how to connect rabbit mq server
+ /// and how channels must be declared ...
+ ///
+ public class XEventServiceConfiguration
+ {
+ ///
+ /// Message Broker Address ...
+ ///
+ public string Url { get; set; }
+
+ ///
+ /// Max Retries to Sending Message ...
+ ///
+ public int MaxRetries { get; set; } = 3;
+
+ ///
+ /// Allowed Message Broker ...
+ ///
+ public XEventBroker Broker { get; set; } = XEventBroker.None;
+
+ ///
+ /// RabbitMQ Specific Options ...
+ /// Only used when Broker is XRabbitMQ ...
+ ///
+ public XRabbitMQOptions RabbitMQ { get; set; } = new XRabbitMQOptions();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\ConfigurationNodeNames.cs
+
+namespace xEventService.Constants
+{
+ public partial struct ConfigurationNodeNames {
+ public const string EVENT_SERVICE_NODE_NAME = "EventServiceConfiguration";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventBroker.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available Brokers ...
+ ///
+ public enum XEventBroker
+ {
+ [StringValue("None")]
+ None,
+
+ [StringValue("XKafka")]
+ XKafka,
+
+ [StringValue("XRabbitMQ")]
+ XRabbitMQ
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventServiceConstants.cs
+
+namespace xEventService.Constants
+{
+ public struct XEventServiceConstants
+ {
+ //
+ // Service Extensions Log Tag ...
+ public const string XEventServiceDILogTag = "XEventService";
+
+ //
+ // Defaults ...
+ public const string XDefaultTopic = "XSaherelm";
+ public const string XDefaultConsumerGroup = "XSaherElmGroup";
+
+ //
+ public const string XWildcard = "*";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XRabbitMQExchangeType.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available RabbitMQ Exchange Types ...
+ ///
+ public struct XRabbitMQExchangeTypes
+ {
+ public const string None = "none";
+ public const string Direct = "direct";
+ public const string Fanout = "fanout";
+ public const string Topic = "topic";
+ public const string Headers = "headers";
+ }
+
+ ///
+ /// RabbitMQ Exchange Type Enum ...
+ ///
+ public enum XRabbitMQExchangeType
+ {
+ [StringValue(XRabbitMQExchangeTypes.None)]
+ None,
+
+ [StringValue(XRabbitMQExchangeTypes.Direct)]
+ Direct,
+
+ [StringValue(XRabbitMQExchangeTypes.Fanout)]
+ Fanout,
+
+ [StringValue(XRabbitMQExchangeTypes.Topic)]
+ Topic,
+
+ [StringValue(XRabbitMQExchangeTypes.Headers)]
+ Headers,
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\DI\XDIHelperExtension.cs
+
+using System;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.DI
+{
+ public static partial class XDIHelperExtension
+ {
+ ///
+ /// Extract EventService Configurations ...
+ ///
+ ///
+ ///
+ public static XEventServiceConfiguration GetXEventServiceConfigurations(
+ this IConfiguration source
+ )
+ {
+ //
+ var configSection = source
+ .GetSection(ConfigurationNodeNames.EVENT_SERVICE_NODE_NAME);
+ var result = configSection.Get();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ IConfiguration configuration
+ )
+ {
+ //
+ var config = configuration.GetXEventServiceConfigurations();
+ services.AddXEventServiceConfigurations(config);
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ // Validate ...
+ if (!configuration.IsValid())
+ {
+ //
+ Log("Registration Failed, due Invalid Configuration ...");
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Register Configuration as Singleton ...
+ services.AddSingleton(configuration);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// a LogTag for Service ...
+ ///
+ private static string XLogTag = XEventServiceConstants.XEventServiceDILogTag;
+
+ ///
+ /// print a log in Console ...
+ ///
+ ///
+ private static void Log(string message)
+ {
+ Console.WriteLine($"{XLogTag} => {message}");
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Extensions\XEventServiceExtensions.cs
+
+using System.Linq;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Extensions
+{
+ ///
+ /// Extensions for Event Service ...
+ ///
+ public static class XEventServiceExtensions
+ {
+ ///
+ /// Validate Event Broker ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventBroker source)
+ {
+ //
+ var result =
+ !source.IsNull() &&
+ source != XEventBroker.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Event Service Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventServiceConfiguration source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ source.Broker.IsValid() &&
+ !source.Url.IsNullOrEmpty() &&
+ source.Url.IsValidUrl();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Exchange Type ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQExchangeType source)
+ {
+ //
+ var result = source != XRabbitMQExchangeType.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQOptions source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Exchange.IsNullOrEmpty() &&
+ !source.ExchangeType.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Message ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventMessage source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Action.IsNullOrEmpty() &&
+ !source.Sender.IsNullOrEmpty();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Request ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventRequest source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Topic.IsNullOrEmpty() &&
+ source.Message.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Array is Wildcard passed or not ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedWildcard(this string[] source)
+ {
+ //
+ var result =
+ source is not null &&
+ source.Length == 1 &&
+ source.Any(s =>
+ s == XEventServiceConstants.XWildcard);
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate a Collection is Valid for Kafka Consumer ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedCollection(
+ this string[] source
+ )
+ {
+ //
+ var result = source.HasChild();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Host address ...
+ ///
+ ///
+ ///
+ public static string GetRabbitMQHost(this XEventServiceConfiguration source)
+ {
+ //
+ var result = string.Empty;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ result = parts[0];
+ if (result.IsNullOrEmpty() ||
+ !result.IsValidUrl()
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Port Number ...
+ ///
+ ///
+ ///
+ public static int GetRabbitMQPort(this XEventServiceConfiguration source)
+ {
+ //
+ int result = -1;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Try Parse Port as Integer ...
+ int.TryParse(parts[1], out result);
+ if (result <= 0)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXKafkaProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// a Service for Produce Kafka Messages ...
+ ///
+ public interface IXKafkaProducerService : IDisposable
+ {
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXRabbitMQProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public interface IXRabbitMQProducerService : IDisposable
+ {
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ ///
+ /// an Instance of .
+ /// Topic is used as Exchange Name ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message to Default Exchange ...
+ ///
+ ///
+ /// an Instance of ...
+ ///
+ ///
+ /// Target Exchange Name. if empty, uses default from configuration ...
+ ///
+ ///
+ /// Routing Key for message routing ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventMessage.cs
+
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Message Descriptor for Events ...
+ ///
+ public class XEventMessage
+ {
+ ///
+ /// Specified Action for Fire ...
+ ///
+ [JsonRequired]
+ public string Action { get; set; }
+
+ ///
+ /// Specified Action Sender ...
+ ///
+ [JsonRequired]
+ public string Sender { get; set; }
+
+ ///
+ /// Sending Time ...
+ ///
+ [JsonRequired]
+ public DateTime Offset { get; set; }
+
+ ///
+ /// Specified Message ...
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// Metadata for Message ...
+ ///
+ public IDictionary Payload { get; set; } = new Dictionary();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventRequest.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Request for Sending an Event ...
+ ///
+ public class XEventRequest
+ {
+ ///
+ /// Specified Which Kafka Topic ...
+ ///
+ public string Topic { get; set; } = XEventServiceConstants.XDefaultTopic;
+
+ ///
+ /// Specified Kafka Meesage to Send ...
+ ///
+ public XEventMessage Message { get; set; }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XRabbitMQOptions.cs
+
+using RabbitMQ.Client;
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// RabbitMQ-specific connection and messaging options ...
+ ///
+ public class XRabbitMQOptions
+ {
+
+ ///
+ /// Connection Username ...
+ ///
+ public string Username { get; set; } = "guest";
+
+ ///
+ /// Connection Password ...
+ ///
+ public string Password { get; set; } = "guest";
+
+ ///
+ /// Virtual Host ...
+ ///
+ public string VirtualHost { get; set; } = "/";
+
+ ///
+ /// Default Exchange Name ...
+ ///
+ public string Exchange { get; set; } = "x.saherelm.events";
+
+ ///
+ /// Default Exchange Type (fanout, direct, topic, headers) ...
+ ///
+ public XRabbitMQExchangeType ExchangeType { get; set; } = XRabbitMQExchangeType.Fanout;
+
+ ///
+ /// Default Queue Name ...
+ /// if empty, a dynamic queue will be generated ...
+ ///
+ public string Queue { get; set; } = string.Empty;
+
+ ///
+ /// Default Routing Key ...
+ ///
+ public string RoutingKey { get; set; } = string.Empty;
+
+ ///
+ /// Exchange Durable Flag ...
+ ///
+ public bool ExchangeDurable { get; set; } = true;
+
+ ///
+ /// Exchange AutoDelete Flag ...
+ ///
+ public bool ExchangeAutoDelete { get; set; } = false;
+
+ ///
+ /// Queue Durable Flag ...
+ ///
+ public bool QueueDurable { get; set; } = true;
+
+ ///
+ /// Queue Exclusive Flag ...
+ ///
+ public bool QueueExclusive { get; set; } = false;
+
+ ///
+ /// Queue AutoDelete Flag ...
+ ///
+ public bool QueueAutoDelete { get; set; } = false;
+
+ ///
+ /// Enable Automatic Recovery on Connection Loss ...
+ ///
+ public bool AutomaticRecoveryEnabled { get; set; } = true;
+
+ ///
+ /// Network Recovery Interval in Seconds ...
+ ///
+ public int NetworkRecoveryIntervalSeconds { get; set; } = 5;
+
+ ///
+ /// Prefetch Count for Consumer ...
+ ///
+ public ushort PrefetchCount { get; set; } = 1;
+
+ ///
+ /// Enable Manual Acknowledgement ...
+ ///
+ public bool ManualAck { get; set; } = true;
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XBaseEventConsumerBackgroundService.cs
+
+using System;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xCommons.Providers;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// Base Event Consumer Service ...
+ ///
+ public abstract class XBaseEventConsumerBackgroundService : XResumableBackgroundService
+ {
+ ///
+ /// Consumed Topic Messages ...
+ ///
+ protected readonly string topic;
+
+ ///
+ /// Allowed Senders ...
+ ///
+ public readonly string[] allowedSenders;
+
+ ///
+ /// Allowed Actions ...
+ ///
+ public readonly string[] allowedActions;
+
+ ///
+ /// Configuration ...
+ ///
+ protected readonly XEventServiceConfiguration configuration;
+
+ protected XBaseEventConsumerBackgroundService(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ logger: logger,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validate Actions ...
+ if (!allowedActions
+ .IsAllowedCollection() ||
+ !allowedSenders
+ .IsAllowedCollection() ||
+ configuration.IsValid()
+ )
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ this.topic = topic;
+ this.configuration = configuration;
+ this.allowedActions = allowedActions;
+ this.allowedSenders = allowedSenders;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaConsumerServiceBase.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XKafkaConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ ///
+ /// Kafka Consumer Class ...
+ ///
+ protected readonly IConsumer consumer;
+
+ protected XKafkaConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (configuration.Broker != XEventBroker.XKafka)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Consumer Configuration ...
+ var config = new ConsumerConfig
+ {
+ EnableAutoCommit = false,
+ BootstrapServers = configuration.Url,
+ AutoOffsetReset = AutoOffsetReset.Earliest,
+ GroupId = XEventServiceConstants.XDefaultConsumerGroup,
+ };
+ consumer = new ConsumerBuilder(config).Build();
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Subscribe(topic);
+
+ //
+ // Consuming ...
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ //
+ // Consuming Topic ...
+ var consumeResult = consumer.Consume(cancellationToken);
+ if (consumeResult.IsNullOrDefault() ||
+ consumeResult.Message.IsNullOrDefault()
+ )
+ {
+ //
+ await DoWorkAsync(
+ payload: consumeResult.Message,
+ cancellationToken: cancellationToken
+ );
+ }
+ }
+ catch (ConsumeException)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ catch (Exception)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ consumer.Close();
+ consumer.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaProducerService.cs
+
+using xCommons.Extensions;
+using xExceptions.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Configuration;
+using xEventService.Models;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+using System.Threading;
+using xEventService.Constants;
+using System;
+
+namespace xEventService.Providers
+{
+ public class XKafkaProducerService : IXKafkaProducerService
+ {
+ private readonly IProducer producer;
+ private readonly ILogger logger;
+ private readonly XEventServiceConfiguration configuration;
+
+ public XKafkaProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ configuration.Broker != XEventBroker.XKafka
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Producer Configuration ...
+ var config = new ProducerConfig
+ {
+ Acks = Acks.All,
+ BootstrapServers = configuration.Url,
+ MessageSendMaxRetries = configuration.MaxRetries,
+ };
+ producer = new ProducerBuilder(config).Build();
+ }
+
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ topic: request.Topic,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid() &&
+ !topic.IsNullOrEmpty();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ try
+ {
+ //
+ var response = await producer.ProduceAsync(
+ topic: topic,
+ message: new Message
+ {
+ Value = message
+ .ToJSON(camelCase: true)
+ },
+ cancellationToken: cancellationToken
+ );
+
+ //
+ result = true;
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Required Objects ...
+ ///
+ public void Dispose()
+ {
+ //
+ producer.Dispose();
+ GC.SuppressFinalize(this);
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQConsumerServiceBase.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XRabbitMQConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ protected IModel channel;
+ protected IConnection connection;
+ protected AsyncEventingBasicConsumer consumer;
+
+ protected XRabbitMQConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (!configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ SetupConnection();
+ DeclareTopology();
+ consumer = new AsyncEventingBasicConsumer(channel);
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Received += async (model, ea) =>
+ {
+ //
+ try
+ {
+ //
+ var body = ea.Body.ToArray();
+ var json = Encoding.UTF8.GetString(body);
+ var message = json.FromJSON();
+ if (!message.IsNullOrDefault())
+ {
+ //
+ await DoWorkAsync(
+ payload: message,
+ cancellationToken: cancellationToken
+ );
+ }
+
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicAck(
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ catch
+ {
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicNack(
+ requeue: true,
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ };
+
+ //
+ consumer.Shutdown += (model, ea) =>
+ {
+ return Task.CompletedTask;
+ };
+
+ //
+ channel.BasicQos(
+ prefetchSize: 0,
+ global: false,
+ prefetchCount: configuration.RabbitMQ.PrefetchCount
+ );
+
+ //
+ channel.BasicConsume(
+ queue: GetQueueName(),
+ consumer: consumer,
+ autoAck: !configuration.RabbitMQ.ManualAck
+ );
+
+ //
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+
+ ///
+ /// Overriding Stop Action ...
+ ///
+ ///
+ ///
+ public override async Task StopAsync(CancellationToken cancellationToken)
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ connection.Close();
+ connection.Dispose();
+
+ //
+ await base.StopAsync(cancellationToken);
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ channel.Dispose();
+ connection.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+
+ //
+ #region Private ...
+ private string GetQueueName()
+ {
+ //
+ var result = string.Empty;
+ if (!configuration.RabbitMQ.Queue.IsNullOrEmpty())
+ {
+ result = configuration.RabbitMQ.Queue;
+ }
+
+ //
+ if (result.IsNullOrEmpty())
+ {
+ //
+ var digits = Guid.NewGuid().ToString().GetDigits();
+ result = $"{configuration.RabbitMQ.Exchange}[{digits}]";
+ }
+
+ //
+ return result;
+ }
+
+ private void SetupConnection()
+ {
+ var factory = new ConnectionFactory
+ {
+ DispatchConsumersAsync = true,
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ };
+
+ //
+ connection = factory.CreateConnection();
+ channel = connection.CreateModel();
+ }
+
+ private void DeclareTopology()
+ {
+ //
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+
+ //
+ var queueName = GetQueueName();
+
+ //
+ channel.QueueDeclare(
+ arguments: null,
+ queue: queueName,
+ durable: configuration.RabbitMQ.QueueDurable,
+ exclusive: configuration.RabbitMQ.QueueExclusive,
+ autoDelete: configuration.RabbitMQ.QueueAutoDelete
+ );
+
+ //
+ channel.QueueBind(
+ arguments: null,
+ queue: queueName,
+ exchange: configuration.RabbitMQ.Exchange,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQProducerService.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public class XRabbitMQProducerService : IXRabbitMQProducerService
+ {
+ private IModel channel;
+ private IConnection connection;
+ private readonly object connectionLock = new();
+ private readonly ILogger logger;
+ private readonly XEventServiceConfiguration configuration;
+
+ public XRabbitMQProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ !configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Initialize Connection ...
+ CreateConnection();
+ }
+
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ exchange: request.Topic,
+ cancellationToken: cancellationToken,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message to Exchange ...
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ // Normalize Exchange ...
+ if (exchange.IsNullOrEmpty())
+ {
+ exchange = configuration.RabbitMQ.Exchange;
+ }
+
+ //
+ // Normalize Routing Key ...
+ if (routingKey.IsNull())
+ {
+ routingKey = configuration.RabbitMQ.RoutingKey;
+ }
+
+ //
+ try
+ {
+ //
+ // Ensure Connection is Alive ...
+ await EnsureChannelAsync(cancellationToken);
+
+ //
+ // Serialize Message ...
+ var body = Encoding.UTF8.GetBytes(
+ message.ToJSON(camelCase: true)
+ );
+
+ //
+ // Prepare Message Properties ...
+ var properties = channel.CreateBasicProperties();
+ properties.Persistent = true;
+ properties.ContentType = "application/json";
+ properties.Timestamp = new AmqpTimestamp(
+ DateTimeOffset.UtcNow.ToUnixTimeSeconds()
+ );
+ properties.MessageId = Guid.NewGuid().ToString();
+ properties.Type = message.Action;
+ properties.AppId = message.Sender;
+
+ //
+ // Publish Message ...
+ await Task.Run(() =>
+ {
+ //
+ channel.BasicPublish(
+ exchange: exchange,
+ routingKey: routingKey,
+ mandatory: false,
+ basicProperties: properties,
+ body: body
+ );
+ },
+ cancellationToken: cancellationToken);
+
+ //
+ result = true;
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public void Dispose()
+ {
+ //
+ try
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ channel = null;
+
+ //
+ connection.Close();
+ connection.Dispose();
+ connection = null;
+ }
+ catch
+ { }
+
+ //
+ GC.SuppressFinalize(this);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// Create Connection to RabbitMQ Server ...
+ ///
+ private void CreateConnection()
+ {
+ //
+ var factory = new ConnectionFactory
+ {
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ DispatchConsumersAsync = true
+ };
+
+ //
+ connection = factory.CreateConnection();
+ }
+
+ ///
+ /// Check Connection is Alive ...
+ ///
+ private bool IsConnected()
+ {
+ //
+ var result =
+ !connection.IsNullOrDefault() &&
+ connection.IsOpen &&
+ !channel.IsNull() &&
+ channel.IsOpen;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Ensure Connection Established ...
+ ///
+ public async Task EnsureConnectionAsync()
+ {
+ //
+ try
+ {
+ await EnsureChannelAsync();
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Ensure Channel is Open and Exchange/Queue Declared ...
+ ///
+ private async Task EnsureChannelAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ lock (connectionLock)
+ {
+ //
+ // Double Check after Lock ...
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ // Recreate Connection if Dead ...
+ if (connection is null || !connection.IsOpen)
+ {
+ CreateConnection();
+ }
+
+ //
+ // Create Channel ...
+ channel = connection.CreateModel();
+
+ //
+ // Declare Exchange ...
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+ }
+
+ //
+ await Task.CompletedTask;
+ }
+ #endregion
+ }
+}
diff --git a/Documents/GeneratedContext/xDashboard-xEventService-20260819_114243.txt b/Documents/GeneratedContext/xDashboard-xEventService-20260819_114243.txt
new file mode 100644
index 0000000..da129dd
--- /dev/null
+++ b/Documents/GeneratedContext/xDashboard-xEventService-20260819_114243.txt
@@ -0,0 +1,1584 @@
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Configuration\XEventServiceConfiguration.cs
+
+using System.Collections.Generic;
+using RabbitMQ.Client;
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Configuration
+{
+ ///
+ /// a configuration class for describing how to connect rabbit mq server
+ /// and how channels must be declared ...
+ ///
+ public class XEventServiceConfiguration
+ {
+ ///
+ /// Message Broker Address ...
+ ///
+ public string Url { get; set; }
+
+ ///
+ /// Max Retries to Sending Message ...
+ ///
+ public int MaxRetries { get; set; } = 3;
+
+ ///
+ /// Allowed Message Broker ...
+ ///
+ public XEventBroker Broker { get; set; } = XEventBroker.None;
+
+ ///
+ /// RabbitMQ Specific Options ...
+ /// Only used when Broker is XRabbitMQ ...
+ ///
+ public XRabbitMQOptions RabbitMQ { get; set; } = new XRabbitMQOptions();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\ConfigurationNodeNames.cs
+
+namespace xEventService.Constants
+{
+ public partial struct ConfigurationNodeNames {
+ public const string EVENT_SERVICE_NODE_NAME = "EventServiceConfiguration";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventBroker.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available Brokers ...
+ ///
+ public enum XEventBroker
+ {
+ [StringValue("None")]
+ None,
+
+ [StringValue("XKafka")]
+ XKafka,
+
+ [StringValue("XRabbitMQ")]
+ XRabbitMQ
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventServiceConstants.cs
+
+namespace xEventService.Constants
+{
+ public struct XEventServiceConstants
+ {
+ //
+ // Service Extensions Log Tag ...
+ public const string XEventServiceDILogTag = "XEventService";
+
+ //
+ // Defaults ...
+ public const string XDefaultTopic = "XSaherelm";
+ public const string XDefaultConsumerGroup = "XSaherElmGroup";
+
+ //
+ public const string XWildcard = "*";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XRabbitMQExchangeType.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available RabbitMQ Exchange Types ...
+ ///
+ public struct XRabbitMQExchangeTypes
+ {
+ public const string None = "none";
+ public const string Direct = "direct";
+ public const string Fanout = "fanout";
+ public const string Topic = "topic";
+ public const string Headers = "headers";
+ }
+
+ ///
+ /// RabbitMQ Exchange Type Enum ...
+ ///
+ public enum XRabbitMQExchangeType
+ {
+ [StringValue(XRabbitMQExchangeTypes.None)]
+ None,
+
+ [StringValue(XRabbitMQExchangeTypes.Direct)]
+ Direct,
+
+ [StringValue(XRabbitMQExchangeTypes.Fanout)]
+ Fanout,
+
+ [StringValue(XRabbitMQExchangeTypes.Topic)]
+ Topic,
+
+ [StringValue(XRabbitMQExchangeTypes.Headers)]
+ Headers,
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\DI\XDIHelperExtension.cs
+
+using System;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.DI
+{
+ public static partial class XDIHelperExtension
+ {
+ ///
+ /// Extract EventService Configurations ...
+ ///
+ ///
+ ///
+ public static XEventServiceConfiguration GetXEventServiceConfigurations(
+ this IConfiguration source
+ )
+ {
+ //
+ var configSection = source
+ .GetSection(ConfigurationNodeNames.EVENT_SERVICE_NODE_NAME);
+ var result = configSection.Get();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ IConfiguration configuration
+ )
+ {
+ //
+ var config = configuration.GetXEventServiceConfigurations();
+ services.AddXEventServiceConfigurations(config);
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ // Validate ...
+ if (!configuration.IsValid())
+ {
+ //
+ Log("Registration Failed, due Invalid Configuration ...");
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Register Configuration as Singleton ...
+ services.AddSingleton(configuration);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// a LogTag for Service ...
+ ///
+ private static string XLogTag = XEventServiceConstants.XEventServiceDILogTag;
+
+ ///
+ /// print a log in Console ...
+ ///
+ ///
+ private static void Log(string message)
+ {
+ Console.WriteLine($"{XLogTag} => {message}");
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Extensions\XEventServiceExtensions.cs
+
+using System.Linq;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Extensions
+{
+ ///
+ /// Extensions for Event Service ...
+ ///
+ public static class XEventServiceExtensions
+ {
+ ///
+ /// Validate Event Broker ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventBroker source)
+ {
+ //
+ var result =
+ !source.IsNull() &&
+ source != XEventBroker.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Event Service Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventServiceConfiguration source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ source.Broker.IsValid() &&
+ !source.Url.IsNullOrEmpty() &&
+ source.Url.IsValidUrl();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Exchange Type ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQExchangeType source)
+ {
+ //
+ var result = source != XRabbitMQExchangeType.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQOptions source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Exchange.IsNullOrEmpty() &&
+ source.ExchangeType.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Message ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventMessage source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Action.IsNullOrEmpty() &&
+ !source.Sender.IsNullOrEmpty();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Request ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventRequest source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Topic.IsNullOrEmpty() &&
+ source.Message.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Array is Wildcard passed or not ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedWildcard(this string[] source)
+ {
+ //
+ var result =
+ source is not null &&
+ source.Length == 1 &&
+ source.Any(s =>
+ s == XEventServiceConstants.XWildcard);
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate a Collection is Valid for Kafka Consumer ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedCollection(
+ this string[] source
+ )
+ {
+ //
+ var result = source.HasChild();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Host address ...
+ ///
+ ///
+ ///
+ public static string GetRabbitMQHost(this XEventServiceConfiguration source)
+ {
+ //
+ var result = string.Empty;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ result = parts[0];
+ if (result.IsNullOrEmpty() ||
+ !result.IsValidUrl()
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Port Number ...
+ ///
+ ///
+ ///
+ public static int GetRabbitMQPort(this XEventServiceConfiguration source)
+ {
+ //
+ int result = -1;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Try Parse Port as Integer ...
+ int.TryParse(parts[1], out result);
+ if (result <= 0)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXKafkaProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// a Service for Produce Kafka Messages ...
+ ///
+ public interface IXKafkaProducerService : IDisposable
+ {
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXRabbitMQProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public interface IXRabbitMQProducerService : IDisposable
+ {
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ ///
+ /// an Instance of .
+ /// Topic is used as Exchange Name ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message to Default Exchange ...
+ ///
+ ///
+ /// an Instance of ...
+ ///
+ ///
+ /// Target Exchange Name. if empty, uses default from configuration ...
+ ///
+ ///
+ /// Routing Key for message routing ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventMessage.cs
+
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Message Descriptor for Events ...
+ ///
+ public class XEventMessage
+ {
+ ///
+ /// Specified Action for Fire ...
+ ///
+ [JsonRequired]
+ public string Action { get; set; }
+
+ ///
+ /// Specified Action Sender ...
+ ///
+ [JsonRequired]
+ public string Sender { get; set; }
+
+ ///
+ /// Sending Time ...
+ ///
+ [JsonRequired]
+ public DateTime Offset { get; set; }
+
+ ///
+ /// Specified Message ...
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// Metadata for Message ...
+ ///
+ public IDictionary Payload { get; set; } = new Dictionary();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventRequest.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Request for Sending an Event ...
+ ///
+ public class XEventRequest
+ {
+ ///
+ /// Specified Which Kafka Topic ...
+ ///
+ public string Topic { get; set; } = XEventServiceConstants.XDefaultTopic;
+
+ ///
+ /// Specified Kafka Meesage to Send ...
+ ///
+ public XEventMessage Message { get; set; }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XRabbitMQOptions.cs
+
+using RabbitMQ.Client;
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// RabbitMQ-specific connection and messaging options ...
+ ///
+ public class XRabbitMQOptions
+ {
+
+ ///
+ /// Connection Username ...
+ ///
+ public string Username { get; set; } = "guest";
+
+ ///
+ /// Connection Password ...
+ ///
+ public string Password { get; set; } = "guest";
+
+ ///
+ /// Virtual Host ...
+ ///
+ public string VirtualHost { get; set; } = "/";
+
+ ///
+ /// Default Exchange Name ...
+ ///
+ public string Exchange { get; set; } = "x.saherelm.events";
+
+ ///
+ /// Default Exchange Type (fanout, direct, topic, headers) ...
+ ///
+ public XRabbitMQExchangeType ExchangeType { get; set; } = XRabbitMQExchangeType.Fanout;
+
+ ///
+ /// Default Queue Name ...
+ /// if empty, a dynamic queue will be generated ...
+ ///
+ public string Queue { get; set; } = string.Empty;
+
+ ///
+ /// Default Routing Key ...
+ ///
+ public string RoutingKey { get; set; } = string.Empty;
+
+ ///
+ /// Exchange Durable Flag ...
+ ///
+ public bool ExchangeDurable { get; set; } = true;
+
+ ///
+ /// Exchange AutoDelete Flag ...
+ ///
+ public bool ExchangeAutoDelete { get; set; } = false;
+
+ ///
+ /// Queue Durable Flag ...
+ ///
+ public bool QueueDurable { get; set; } = true;
+
+ ///
+ /// Queue Exclusive Flag ...
+ ///
+ public bool QueueExclusive { get; set; } = false;
+
+ ///
+ /// Queue AutoDelete Flag ...
+ ///
+ public bool QueueAutoDelete { get; set; } = false;
+
+ ///
+ /// Enable Automatic Recovery on Connection Loss ...
+ ///
+ public bool AutomaticRecoveryEnabled { get; set; } = true;
+
+ ///
+ /// Network Recovery Interval in Seconds ...
+ ///
+ public int NetworkRecoveryIntervalSeconds { get; set; } = 5;
+
+ ///
+ /// Prefetch Count for Consumer ...
+ ///
+ public ushort PrefetchCount { get; set; } = 1;
+
+ ///
+ /// Enable Manual Acknowledgement ...
+ ///
+ public bool ManualAck { get; set; } = true;
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XBaseEventConsumerBackgroundService.cs
+
+using System;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xCommons.Providers;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// Base Event Consumer Service ...
+ ///
+ public abstract class XBaseEventConsumerBackgroundService : XResumableBackgroundService
+ {
+ ///
+ /// Consumed Topic Messages ...
+ ///
+ protected readonly string topic;
+
+ ///
+ /// Allowed Senders ...
+ ///
+ public readonly string[] allowedSenders;
+
+ ///
+ /// Allowed Actions ...
+ ///
+ public readonly string[] allowedActions;
+
+ ///
+ /// Configuration ...
+ ///
+ protected readonly XEventServiceConfiguration configuration;
+
+ protected XBaseEventConsumerBackgroundService(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ logger: logger,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validate Actions ...
+ if (!allowedActions
+ .IsAllowedCollection() ||
+ !allowedSenders
+ .IsAllowedCollection() ||
+ !configuration.IsValid()
+ )
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ this.topic = topic;
+ this.configuration = configuration;
+ this.allowedActions = allowedActions;
+ this.allowedSenders = allowedSenders;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaConsumerServiceBase.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XKafkaConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ ///
+ /// Kafka Consumer Class ...
+ ///
+ protected readonly IConsumer consumer;
+
+ protected XKafkaConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (configuration.Broker != XEventBroker.XKafka)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Consumer Configuration ...
+ var config = new ConsumerConfig
+ {
+ EnableAutoCommit = false,
+ BootstrapServers = configuration.Url,
+ AutoOffsetReset = AutoOffsetReset.Earliest,
+ GroupId = XEventServiceConstants.XDefaultConsumerGroup,
+ };
+ consumer = new ConsumerBuilder(config).Build();
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Subscribe(topic);
+
+ //
+ // Consuming ...
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ //
+ // Consuming Topic ...
+ var consumeResult = consumer.Consume(cancellationToken);
+ if (consumeResult.IsNullOrDefault() ||
+ !consumeResult.Message.IsNullOrDefault()
+ )
+ {
+ //
+ await DoWorkAsync(
+ payload: consumeResult.Message,
+ cancellationToken: cancellationToken
+ );
+ }
+ }
+ catch (ConsumeException)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ catch (Exception)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ consumer.Close();
+ consumer.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaProducerService.cs
+
+using xCommons.Extensions;
+using xExceptions.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Configuration;
+using xEventService.Models;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+using System.Threading;
+using xEventService.Constants;
+using System;
+
+namespace xEventService.Providers
+{
+ public class XKafkaProducerService : IXKafkaProducerService
+ {
+ private readonly IProducer producer;
+ private readonly ILogger logger;
+ private readonly XEventServiceConfiguration configuration;
+
+ public XKafkaProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ configuration.Broker != XEventBroker.XKafka
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Producer Configuration ...
+ var config = new ProducerConfig
+ {
+ Acks = Acks.All,
+ BootstrapServers = configuration.Url,
+ MessageSendMaxRetries = configuration.MaxRetries,
+ };
+ producer = new ProducerBuilder(config).Build();
+ }
+
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ topic: request.Topic,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid() &&
+ !topic.IsNullOrEmpty();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ try
+ {
+ //
+ var response = await producer.ProduceAsync(
+ topic: topic,
+ message: new Message
+ {
+ Value = message
+ .ToJSON(camelCase: true)
+ },
+ cancellationToken: cancellationToken
+ );
+
+ //
+ result = !response.IsNullOrDefault();
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Required Objects ...
+ ///
+ public void Dispose()
+ {
+ //
+ producer.Dispose();
+ GC.SuppressFinalize(this);
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQConsumerServiceBase.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XRabbitMQConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ protected IModel channel;
+ protected IConnection connection;
+ protected AsyncEventingBasicConsumer consumer;
+
+ protected XRabbitMQConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (!configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ SetupConnection();
+ DeclareTopology();
+ consumer = new AsyncEventingBasicConsumer(channel);
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Received += async (model, ea) =>
+ {
+ //
+ try
+ {
+ //
+ var body = ea.Body.ToArray();
+ var json = Encoding.UTF8.GetString(body);
+ var message = json.FromJSON();
+ if (!message.IsNullOrDefault())
+ {
+ //
+ await DoWorkAsync(
+ payload: message,
+ cancellationToken: cancellationToken
+ );
+ }
+
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicAck(
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ catch
+ {
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicNack(
+ requeue: true,
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ };
+
+ //
+ consumer.Shutdown += (model, ea) =>
+ {
+ return Task.CompletedTask;
+ };
+
+ //
+ channel.BasicQos(
+ prefetchSize: 0,
+ global: false,
+ prefetchCount: configuration.RabbitMQ.PrefetchCount
+ );
+
+ //
+ channel.BasicConsume(
+ queue: GetQueueName(),
+ consumer: consumer,
+ autoAck: !configuration.RabbitMQ.ManualAck
+ );
+
+ //
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+
+ ///
+ /// Overriding Stop Action ...
+ ///
+ ///
+ ///
+ public override async Task StopAsync(CancellationToken cancellationToken)
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ connection.Close();
+ connection.Dispose();
+
+ //
+ await base.StopAsync(cancellationToken);
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ channel.Dispose();
+ connection.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+
+ //
+ #region Private ...
+ private string GetQueueName()
+ {
+ //
+ var result = string.Empty;
+ if (!configuration.RabbitMQ.Queue.IsNullOrEmpty())
+ {
+ result = configuration.RabbitMQ.Queue;
+ }
+
+ //
+ if (result.IsNullOrEmpty())
+ {
+ //
+ var digits = Guid.NewGuid().ToString().GetDigits();
+ result = $"{configuration.RabbitMQ.Exchange}[{digits}]";
+ }
+
+ //
+ return result;
+ }
+
+ private void SetupConnection()
+ {
+ var factory = new ConnectionFactory
+ {
+ DispatchConsumersAsync = true,
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ };
+
+ //
+ connection = factory.CreateConnection();
+ channel = connection.CreateModel();
+ }
+
+ private void DeclareTopology()
+ {
+ //
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+
+ //
+ var queueName = GetQueueName();
+
+ //
+ channel.QueueDeclare(
+ arguments: null,
+ queue: queueName,
+ durable: configuration.RabbitMQ.QueueDurable,
+ exclusive: configuration.RabbitMQ.QueueExclusive,
+ autoDelete: configuration.RabbitMQ.QueueAutoDelete
+ );
+
+ //
+ channel.QueueBind(
+ arguments: null,
+ queue: queueName,
+ exchange: configuration.RabbitMQ.Exchange,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQProducerService.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public class XRabbitMQProducerService : IXRabbitMQProducerService
+ {
+ private IModel channel;
+ private IConnection connection;
+ private readonly object connectionLock = new();
+ private readonly ILogger logger;
+ private readonly XEventServiceConfiguration configuration;
+
+ public XRabbitMQProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ !configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Initialize Connection ...
+ CreateConnection();
+ }
+
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ exchange: request.Topic,
+ cancellationToken: cancellationToken,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message to Exchange ...
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ // Normalize Exchange ...
+ if (exchange.IsNullOrEmpty())
+ {
+ exchange = configuration.RabbitMQ.Exchange;
+ }
+
+ //
+ // Normalize Routing Key ...
+ if (routingKey.IsNull())
+ {
+ routingKey = configuration.RabbitMQ.RoutingKey;
+ }
+
+ //
+ try
+ {
+ //
+ // Ensure Connection is Alive ...
+ await EnsureChannelAsync(cancellationToken);
+
+ //
+ // Serialize Message ...
+ var body = Encoding.UTF8.GetBytes(
+ message.ToJSON(camelCase: true)
+ );
+
+ //
+ // Prepare Message Properties ...
+ var properties = channel.CreateBasicProperties();
+ properties.Persistent = true;
+ properties.ContentType = "application/json";
+ properties.Timestamp = new AmqpTimestamp(
+ DateTimeOffset.UtcNow.ToUnixTimeSeconds()
+ );
+ properties.MessageId = Guid.NewGuid().ToString();
+ properties.Type = message.Action;
+ properties.AppId = message.Sender;
+
+ //
+ // Publish Message ...
+ await Task.Run(() =>
+ {
+ //
+ channel.BasicPublish(
+ exchange: exchange,
+ routingKey: routingKey,
+ mandatory: false,
+ basicProperties: properties,
+ body: body
+ );
+ },
+ cancellationToken: cancellationToken);
+
+ //
+ result = true;
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public void Dispose()
+ {
+ //
+ try
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ channel = null;
+
+ //
+ connection.Close();
+ connection.Dispose();
+ connection = null;
+ }
+ catch
+ { }
+
+ //
+ GC.SuppressFinalize(this);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// Create Connection to RabbitMQ Server ...
+ ///
+ private void CreateConnection()
+ {
+ //
+ var factory = new ConnectionFactory
+ {
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ DispatchConsumersAsync = true
+ };
+
+ //
+ connection = factory.CreateConnection();
+ }
+
+ ///
+ /// Check Connection is Alive ...
+ ///
+ private bool IsConnected()
+ {
+ //
+ var result =
+ !connection.IsNullOrDefault() &&
+ connection.IsOpen &&
+ !channel.IsNull() &&
+ channel.IsOpen;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Ensure Connection Established ...
+ ///
+ private async Task EnsureConnectionAsync()
+ {
+ //
+ try
+ {
+ await EnsureChannelAsync();
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Ensure Channel is Open and Exchange/Queue Declared ...
+ ///
+ private async Task EnsureChannelAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ lock (connectionLock)
+ {
+ //
+ // Double Check after Lock ...
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ // Recreate Connection if Dead ...
+ if (connection is null || !connection.IsOpen)
+ {
+ CreateConnection();
+ }
+
+ //
+ // Create Channel ...
+ channel = connection.CreateModel();
+
+ //
+ // Declare Exchange ...
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+ }
+
+ //
+ await Task.CompletedTask;
+ }
+ #endregion
+ }
+}
diff --git a/Documents/GeneratedContext/xDashboard-xEventService-20260819_114709.txt b/Documents/GeneratedContext/xDashboard-xEventService-20260819_114709.txt
new file mode 100644
index 0000000..da129dd
--- /dev/null
+++ b/Documents/GeneratedContext/xDashboard-xEventService-20260819_114709.txt
@@ -0,0 +1,1584 @@
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Configuration\XEventServiceConfiguration.cs
+
+using System.Collections.Generic;
+using RabbitMQ.Client;
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Configuration
+{
+ ///
+ /// a configuration class for describing how to connect rabbit mq server
+ /// and how channels must be declared ...
+ ///
+ public class XEventServiceConfiguration
+ {
+ ///
+ /// Message Broker Address ...
+ ///
+ public string Url { get; set; }
+
+ ///
+ /// Max Retries to Sending Message ...
+ ///
+ public int MaxRetries { get; set; } = 3;
+
+ ///
+ /// Allowed Message Broker ...
+ ///
+ public XEventBroker Broker { get; set; } = XEventBroker.None;
+
+ ///
+ /// RabbitMQ Specific Options ...
+ /// Only used when Broker is XRabbitMQ ...
+ ///
+ public XRabbitMQOptions RabbitMQ { get; set; } = new XRabbitMQOptions();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\ConfigurationNodeNames.cs
+
+namespace xEventService.Constants
+{
+ public partial struct ConfigurationNodeNames {
+ public const string EVENT_SERVICE_NODE_NAME = "EventServiceConfiguration";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventBroker.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available Brokers ...
+ ///
+ public enum XEventBroker
+ {
+ [StringValue("None")]
+ None,
+
+ [StringValue("XKafka")]
+ XKafka,
+
+ [StringValue("XRabbitMQ")]
+ XRabbitMQ
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventServiceConstants.cs
+
+namespace xEventService.Constants
+{
+ public struct XEventServiceConstants
+ {
+ //
+ // Service Extensions Log Tag ...
+ public const string XEventServiceDILogTag = "XEventService";
+
+ //
+ // Defaults ...
+ public const string XDefaultTopic = "XSaherelm";
+ public const string XDefaultConsumerGroup = "XSaherElmGroup";
+
+ //
+ public const string XWildcard = "*";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XRabbitMQExchangeType.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available RabbitMQ Exchange Types ...
+ ///
+ public struct XRabbitMQExchangeTypes
+ {
+ public const string None = "none";
+ public const string Direct = "direct";
+ public const string Fanout = "fanout";
+ public const string Topic = "topic";
+ public const string Headers = "headers";
+ }
+
+ ///
+ /// RabbitMQ Exchange Type Enum ...
+ ///
+ public enum XRabbitMQExchangeType
+ {
+ [StringValue(XRabbitMQExchangeTypes.None)]
+ None,
+
+ [StringValue(XRabbitMQExchangeTypes.Direct)]
+ Direct,
+
+ [StringValue(XRabbitMQExchangeTypes.Fanout)]
+ Fanout,
+
+ [StringValue(XRabbitMQExchangeTypes.Topic)]
+ Topic,
+
+ [StringValue(XRabbitMQExchangeTypes.Headers)]
+ Headers,
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\DI\XDIHelperExtension.cs
+
+using System;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.DI
+{
+ public static partial class XDIHelperExtension
+ {
+ ///
+ /// Extract EventService Configurations ...
+ ///
+ ///
+ ///
+ public static XEventServiceConfiguration GetXEventServiceConfigurations(
+ this IConfiguration source
+ )
+ {
+ //
+ var configSection = source
+ .GetSection(ConfigurationNodeNames.EVENT_SERVICE_NODE_NAME);
+ var result = configSection.Get();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ IConfiguration configuration
+ )
+ {
+ //
+ var config = configuration.GetXEventServiceConfigurations();
+ services.AddXEventServiceConfigurations(config);
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ // Validate ...
+ if (!configuration.IsValid())
+ {
+ //
+ Log("Registration Failed, due Invalid Configuration ...");
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Register Configuration as Singleton ...
+ services.AddSingleton(configuration);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// a LogTag for Service ...
+ ///
+ private static string XLogTag = XEventServiceConstants.XEventServiceDILogTag;
+
+ ///
+ /// print a log in Console ...
+ ///
+ ///
+ private static void Log(string message)
+ {
+ Console.WriteLine($"{XLogTag} => {message}");
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Extensions\XEventServiceExtensions.cs
+
+using System.Linq;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Extensions
+{
+ ///
+ /// Extensions for Event Service ...
+ ///
+ public static class XEventServiceExtensions
+ {
+ ///
+ /// Validate Event Broker ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventBroker source)
+ {
+ //
+ var result =
+ !source.IsNull() &&
+ source != XEventBroker.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Event Service Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventServiceConfiguration source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ source.Broker.IsValid() &&
+ !source.Url.IsNullOrEmpty() &&
+ source.Url.IsValidUrl();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Exchange Type ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQExchangeType source)
+ {
+ //
+ var result = source != XRabbitMQExchangeType.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQOptions source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Exchange.IsNullOrEmpty() &&
+ source.ExchangeType.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Message ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventMessage source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Action.IsNullOrEmpty() &&
+ !source.Sender.IsNullOrEmpty();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Request ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventRequest source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Topic.IsNullOrEmpty() &&
+ source.Message.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Array is Wildcard passed or not ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedWildcard(this string[] source)
+ {
+ //
+ var result =
+ source is not null &&
+ source.Length == 1 &&
+ source.Any(s =>
+ s == XEventServiceConstants.XWildcard);
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate a Collection is Valid for Kafka Consumer ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedCollection(
+ this string[] source
+ )
+ {
+ //
+ var result = source.HasChild();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Host address ...
+ ///
+ ///
+ ///
+ public static string GetRabbitMQHost(this XEventServiceConfiguration source)
+ {
+ //
+ var result = string.Empty;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ result = parts[0];
+ if (result.IsNullOrEmpty() ||
+ !result.IsValidUrl()
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Port Number ...
+ ///
+ ///
+ ///
+ public static int GetRabbitMQPort(this XEventServiceConfiguration source)
+ {
+ //
+ int result = -1;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Try Parse Port as Integer ...
+ int.TryParse(parts[1], out result);
+ if (result <= 0)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXKafkaProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// a Service for Produce Kafka Messages ...
+ ///
+ public interface IXKafkaProducerService : IDisposable
+ {
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXRabbitMQProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public interface IXRabbitMQProducerService : IDisposable
+ {
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ ///
+ /// an Instance of .
+ /// Topic is used as Exchange Name ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message to Default Exchange ...
+ ///
+ ///
+ /// an Instance of ...
+ ///
+ ///
+ /// Target Exchange Name. if empty, uses default from configuration ...
+ ///
+ ///
+ /// Routing Key for message routing ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventMessage.cs
+
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Message Descriptor for Events ...
+ ///
+ public class XEventMessage
+ {
+ ///
+ /// Specified Action for Fire ...
+ ///
+ [JsonRequired]
+ public string Action { get; set; }
+
+ ///
+ /// Specified Action Sender ...
+ ///
+ [JsonRequired]
+ public string Sender { get; set; }
+
+ ///
+ /// Sending Time ...
+ ///
+ [JsonRequired]
+ public DateTime Offset { get; set; }
+
+ ///
+ /// Specified Message ...
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// Metadata for Message ...
+ ///
+ public IDictionary Payload { get; set; } = new Dictionary();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventRequest.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Request for Sending an Event ...
+ ///
+ public class XEventRequest
+ {
+ ///
+ /// Specified Which Kafka Topic ...
+ ///
+ public string Topic { get; set; } = XEventServiceConstants.XDefaultTopic;
+
+ ///
+ /// Specified Kafka Meesage to Send ...
+ ///
+ public XEventMessage Message { get; set; }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XRabbitMQOptions.cs
+
+using RabbitMQ.Client;
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// RabbitMQ-specific connection and messaging options ...
+ ///
+ public class XRabbitMQOptions
+ {
+
+ ///
+ /// Connection Username ...
+ ///
+ public string Username { get; set; } = "guest";
+
+ ///
+ /// Connection Password ...
+ ///
+ public string Password { get; set; } = "guest";
+
+ ///
+ /// Virtual Host ...
+ ///
+ public string VirtualHost { get; set; } = "/";
+
+ ///
+ /// Default Exchange Name ...
+ ///
+ public string Exchange { get; set; } = "x.saherelm.events";
+
+ ///
+ /// Default Exchange Type (fanout, direct, topic, headers) ...
+ ///
+ public XRabbitMQExchangeType ExchangeType { get; set; } = XRabbitMQExchangeType.Fanout;
+
+ ///
+ /// Default Queue Name ...
+ /// if empty, a dynamic queue will be generated ...
+ ///
+ public string Queue { get; set; } = string.Empty;
+
+ ///
+ /// Default Routing Key ...
+ ///
+ public string RoutingKey { get; set; } = string.Empty;
+
+ ///
+ /// Exchange Durable Flag ...
+ ///
+ public bool ExchangeDurable { get; set; } = true;
+
+ ///
+ /// Exchange AutoDelete Flag ...
+ ///
+ public bool ExchangeAutoDelete { get; set; } = false;
+
+ ///
+ /// Queue Durable Flag ...
+ ///
+ public bool QueueDurable { get; set; } = true;
+
+ ///
+ /// Queue Exclusive Flag ...
+ ///
+ public bool QueueExclusive { get; set; } = false;
+
+ ///
+ /// Queue AutoDelete Flag ...
+ ///
+ public bool QueueAutoDelete { get; set; } = false;
+
+ ///
+ /// Enable Automatic Recovery on Connection Loss ...
+ ///
+ public bool AutomaticRecoveryEnabled { get; set; } = true;
+
+ ///
+ /// Network Recovery Interval in Seconds ...
+ ///
+ public int NetworkRecoveryIntervalSeconds { get; set; } = 5;
+
+ ///
+ /// Prefetch Count for Consumer ...
+ ///
+ public ushort PrefetchCount { get; set; } = 1;
+
+ ///
+ /// Enable Manual Acknowledgement ...
+ ///
+ public bool ManualAck { get; set; } = true;
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XBaseEventConsumerBackgroundService.cs
+
+using System;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xCommons.Providers;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// Base Event Consumer Service ...
+ ///
+ public abstract class XBaseEventConsumerBackgroundService : XResumableBackgroundService
+ {
+ ///
+ /// Consumed Topic Messages ...
+ ///
+ protected readonly string topic;
+
+ ///
+ /// Allowed Senders ...
+ ///
+ public readonly string[] allowedSenders;
+
+ ///
+ /// Allowed Actions ...
+ ///
+ public readonly string[] allowedActions;
+
+ ///
+ /// Configuration ...
+ ///
+ protected readonly XEventServiceConfiguration configuration;
+
+ protected XBaseEventConsumerBackgroundService(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ logger: logger,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validate Actions ...
+ if (!allowedActions
+ .IsAllowedCollection() ||
+ !allowedSenders
+ .IsAllowedCollection() ||
+ !configuration.IsValid()
+ )
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ this.topic = topic;
+ this.configuration = configuration;
+ this.allowedActions = allowedActions;
+ this.allowedSenders = allowedSenders;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaConsumerServiceBase.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XKafkaConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ ///
+ /// Kafka Consumer Class ...
+ ///
+ protected readonly IConsumer consumer;
+
+ protected XKafkaConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (configuration.Broker != XEventBroker.XKafka)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Consumer Configuration ...
+ var config = new ConsumerConfig
+ {
+ EnableAutoCommit = false,
+ BootstrapServers = configuration.Url,
+ AutoOffsetReset = AutoOffsetReset.Earliest,
+ GroupId = XEventServiceConstants.XDefaultConsumerGroup,
+ };
+ consumer = new ConsumerBuilder(config).Build();
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Subscribe(topic);
+
+ //
+ // Consuming ...
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ //
+ // Consuming Topic ...
+ var consumeResult = consumer.Consume(cancellationToken);
+ if (consumeResult.IsNullOrDefault() ||
+ !consumeResult.Message.IsNullOrDefault()
+ )
+ {
+ //
+ await DoWorkAsync(
+ payload: consumeResult.Message,
+ cancellationToken: cancellationToken
+ );
+ }
+ }
+ catch (ConsumeException)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ catch (Exception)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ consumer.Close();
+ consumer.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaProducerService.cs
+
+using xCommons.Extensions;
+using xExceptions.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Configuration;
+using xEventService.Models;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+using System.Threading;
+using xEventService.Constants;
+using System;
+
+namespace xEventService.Providers
+{
+ public class XKafkaProducerService : IXKafkaProducerService
+ {
+ private readonly IProducer producer;
+ private readonly ILogger logger;
+ private readonly XEventServiceConfiguration configuration;
+
+ public XKafkaProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ configuration.Broker != XEventBroker.XKafka
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Producer Configuration ...
+ var config = new ProducerConfig
+ {
+ Acks = Acks.All,
+ BootstrapServers = configuration.Url,
+ MessageSendMaxRetries = configuration.MaxRetries,
+ };
+ producer = new ProducerBuilder(config).Build();
+ }
+
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ topic: request.Topic,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid() &&
+ !topic.IsNullOrEmpty();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ try
+ {
+ //
+ var response = await producer.ProduceAsync(
+ topic: topic,
+ message: new Message
+ {
+ Value = message
+ .ToJSON(camelCase: true)
+ },
+ cancellationToken: cancellationToken
+ );
+
+ //
+ result = !response.IsNullOrDefault();
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Required Objects ...
+ ///
+ public void Dispose()
+ {
+ //
+ producer.Dispose();
+ GC.SuppressFinalize(this);
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQConsumerServiceBase.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XRabbitMQConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ protected IModel channel;
+ protected IConnection connection;
+ protected AsyncEventingBasicConsumer consumer;
+
+ protected XRabbitMQConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (!configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ SetupConnection();
+ DeclareTopology();
+ consumer = new AsyncEventingBasicConsumer(channel);
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Received += async (model, ea) =>
+ {
+ //
+ try
+ {
+ //
+ var body = ea.Body.ToArray();
+ var json = Encoding.UTF8.GetString(body);
+ var message = json.FromJSON();
+ if (!message.IsNullOrDefault())
+ {
+ //
+ await DoWorkAsync(
+ payload: message,
+ cancellationToken: cancellationToken
+ );
+ }
+
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicAck(
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ catch
+ {
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicNack(
+ requeue: true,
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ };
+
+ //
+ consumer.Shutdown += (model, ea) =>
+ {
+ return Task.CompletedTask;
+ };
+
+ //
+ channel.BasicQos(
+ prefetchSize: 0,
+ global: false,
+ prefetchCount: configuration.RabbitMQ.PrefetchCount
+ );
+
+ //
+ channel.BasicConsume(
+ queue: GetQueueName(),
+ consumer: consumer,
+ autoAck: !configuration.RabbitMQ.ManualAck
+ );
+
+ //
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+
+ ///
+ /// Overriding Stop Action ...
+ ///
+ ///
+ ///
+ public override async Task StopAsync(CancellationToken cancellationToken)
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ connection.Close();
+ connection.Dispose();
+
+ //
+ await base.StopAsync(cancellationToken);
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ channel.Dispose();
+ connection.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+
+ //
+ #region Private ...
+ private string GetQueueName()
+ {
+ //
+ var result = string.Empty;
+ if (!configuration.RabbitMQ.Queue.IsNullOrEmpty())
+ {
+ result = configuration.RabbitMQ.Queue;
+ }
+
+ //
+ if (result.IsNullOrEmpty())
+ {
+ //
+ var digits = Guid.NewGuid().ToString().GetDigits();
+ result = $"{configuration.RabbitMQ.Exchange}[{digits}]";
+ }
+
+ //
+ return result;
+ }
+
+ private void SetupConnection()
+ {
+ var factory = new ConnectionFactory
+ {
+ DispatchConsumersAsync = true,
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ };
+
+ //
+ connection = factory.CreateConnection();
+ channel = connection.CreateModel();
+ }
+
+ private void DeclareTopology()
+ {
+ //
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+
+ //
+ var queueName = GetQueueName();
+
+ //
+ channel.QueueDeclare(
+ arguments: null,
+ queue: queueName,
+ durable: configuration.RabbitMQ.QueueDurable,
+ exclusive: configuration.RabbitMQ.QueueExclusive,
+ autoDelete: configuration.RabbitMQ.QueueAutoDelete
+ );
+
+ //
+ channel.QueueBind(
+ arguments: null,
+ queue: queueName,
+ exchange: configuration.RabbitMQ.Exchange,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQProducerService.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public class XRabbitMQProducerService : IXRabbitMQProducerService
+ {
+ private IModel channel;
+ private IConnection connection;
+ private readonly object connectionLock = new();
+ private readonly ILogger logger;
+ private readonly XEventServiceConfiguration configuration;
+
+ public XRabbitMQProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ !configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Initialize Connection ...
+ CreateConnection();
+ }
+
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ exchange: request.Topic,
+ cancellationToken: cancellationToken,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message to Exchange ...
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ // Normalize Exchange ...
+ if (exchange.IsNullOrEmpty())
+ {
+ exchange = configuration.RabbitMQ.Exchange;
+ }
+
+ //
+ // Normalize Routing Key ...
+ if (routingKey.IsNull())
+ {
+ routingKey = configuration.RabbitMQ.RoutingKey;
+ }
+
+ //
+ try
+ {
+ //
+ // Ensure Connection is Alive ...
+ await EnsureChannelAsync(cancellationToken);
+
+ //
+ // Serialize Message ...
+ var body = Encoding.UTF8.GetBytes(
+ message.ToJSON(camelCase: true)
+ );
+
+ //
+ // Prepare Message Properties ...
+ var properties = channel.CreateBasicProperties();
+ properties.Persistent = true;
+ properties.ContentType = "application/json";
+ properties.Timestamp = new AmqpTimestamp(
+ DateTimeOffset.UtcNow.ToUnixTimeSeconds()
+ );
+ properties.MessageId = Guid.NewGuid().ToString();
+ properties.Type = message.Action;
+ properties.AppId = message.Sender;
+
+ //
+ // Publish Message ...
+ await Task.Run(() =>
+ {
+ //
+ channel.BasicPublish(
+ exchange: exchange,
+ routingKey: routingKey,
+ mandatory: false,
+ basicProperties: properties,
+ body: body
+ );
+ },
+ cancellationToken: cancellationToken);
+
+ //
+ result = true;
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public void Dispose()
+ {
+ //
+ try
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ channel = null;
+
+ //
+ connection.Close();
+ connection.Dispose();
+ connection = null;
+ }
+ catch
+ { }
+
+ //
+ GC.SuppressFinalize(this);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// Create Connection to RabbitMQ Server ...
+ ///
+ private void CreateConnection()
+ {
+ //
+ var factory = new ConnectionFactory
+ {
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ DispatchConsumersAsync = true
+ };
+
+ //
+ connection = factory.CreateConnection();
+ }
+
+ ///
+ /// Check Connection is Alive ...
+ ///
+ private bool IsConnected()
+ {
+ //
+ var result =
+ !connection.IsNullOrDefault() &&
+ connection.IsOpen &&
+ !channel.IsNull() &&
+ channel.IsOpen;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Ensure Connection Established ...
+ ///
+ private async Task EnsureConnectionAsync()
+ {
+ //
+ try
+ {
+ await EnsureChannelAsync();
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Ensure Channel is Open and Exchange/Queue Declared ...
+ ///
+ private async Task EnsureChannelAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ lock (connectionLock)
+ {
+ //
+ // Double Check after Lock ...
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ // Recreate Connection if Dead ...
+ if (connection is null || !connection.IsOpen)
+ {
+ CreateConnection();
+ }
+
+ //
+ // Create Channel ...
+ channel = connection.CreateModel();
+
+ //
+ // Declare Exchange ...
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+ }
+
+ //
+ await Task.CompletedTask;
+ }
+ #endregion
+ }
+}
diff --git a/Documents/GeneratedContext/xDashboard-xEventService-20260819_115225.txt b/Documents/GeneratedContext/xDashboard-xEventService-20260819_115225.txt
new file mode 100644
index 0000000..161b9f3
--- /dev/null
+++ b/Documents/GeneratedContext/xDashboard-xEventService-20260819_115225.txt
@@ -0,0 +1,1584 @@
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Configuration\XEventServiceConfiguration.cs
+
+using System.Collections.Generic;
+using RabbitMQ.Client;
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Configuration
+{
+ ///
+ /// a configuration class for describing how to connect rabbit mq server
+ /// and how channels must be declared ...
+ ///
+ public class XEventServiceConfiguration
+ {
+ ///
+ /// Message Broker Address ...
+ ///
+ public string Url { get; set; }
+
+ ///
+ /// Max Retries to Sending Message ...
+ ///
+ public int MaxRetries { get; set; } = 3;
+
+ ///
+ /// Allowed Message Broker ...
+ ///
+ public XEventBroker Broker { get; set; } = XEventBroker.None;
+
+ ///
+ /// RabbitMQ Specific Options ...
+ /// Only used when Broker is XRabbitMQ ...
+ ///
+ public XRabbitMQOptions RabbitMQ { get; set; } = new XRabbitMQOptions();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\ConfigurationNodeNames.cs
+
+namespace xEventService.Constants
+{
+ public partial struct ConfigurationNodeNames {
+ public const string EVENT_SERVICE_NODE_NAME = "EventServiceConfiguration";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventBroker.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available Brokers ...
+ ///
+ public enum XEventBroker
+ {
+ [StringValue("None")]
+ None,
+
+ [StringValue("XKafka")]
+ XKafka,
+
+ [StringValue("XRabbitMQ")]
+ XRabbitMQ
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventServiceConstants.cs
+
+namespace xEventService.Constants
+{
+ public struct XEventServiceConstants
+ {
+ //
+ // Service Extensions Log Tag ...
+ public const string XEventServiceDILogTag = "XEventService";
+
+ //
+ // Defaults ...
+ public const string XDefaultTopic = "XSaherelm";
+ public const string XDefaultConsumerGroup = "XSaherElmGroup";
+
+ //
+ public const string XWildcard = "*";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XRabbitMQExchangeType.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available RabbitMQ Exchange Types ...
+ ///
+ public struct XRabbitMQExchangeTypes
+ {
+ public const string None = "none";
+ public const string Direct = "direct";
+ public const string Fanout = "fanout";
+ public const string Topic = "topic";
+ public const string Headers = "headers";
+ }
+
+ ///
+ /// RabbitMQ Exchange Type Enum ...
+ ///
+ public enum XRabbitMQExchangeType
+ {
+ [StringValue(XRabbitMQExchangeTypes.None)]
+ None,
+
+ [StringValue(XRabbitMQExchangeTypes.Direct)]
+ Direct,
+
+ [StringValue(XRabbitMQExchangeTypes.Fanout)]
+ Fanout,
+
+ [StringValue(XRabbitMQExchangeTypes.Topic)]
+ Topic,
+
+ [StringValue(XRabbitMQExchangeTypes.Headers)]
+ Headers,
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\DI\XDIHelperExtension.cs
+
+using System;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.DI
+{
+ public static partial class XDIHelperExtension
+ {
+ ///
+ /// Extract EventService Configurations ...
+ ///
+ ///
+ ///
+ public static XEventServiceConfiguration GetXEventServiceConfigurations(
+ this IConfiguration source
+ )
+ {
+ //
+ var configSection = source
+ .GetSection(ConfigurationNodeNames.EVENT_SERVICE_NODE_NAME);
+ var result = configSection.Get();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ IConfiguration configuration
+ )
+ {
+ //
+ var config = configuration.GetXEventServiceConfigurations();
+ services.AddXEventServiceConfigurations(config);
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ // Validate ...
+ if (!configuration.IsValid())
+ {
+ //
+ Log("Registration Failed, due Invalid Configuration ...");
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Register Configuration as Singleton ...
+ services.AddSingleton(configuration);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// a LogTag for Service ...
+ ///
+ private static string XLogTag = XEventServiceConstants.XEventServiceDILogTag;
+
+ ///
+ /// print a log in Console ...
+ ///
+ ///
+ private static void Log(string message)
+ {
+ Console.WriteLine($"{XLogTag} => {message}");
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Extensions\XEventServiceExtensions.cs
+
+using System.Linq;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Extensions
+{
+ ///
+ /// Extensions for Event Service ...
+ ///
+ public static class XEventServiceExtensions
+ {
+ ///
+ /// Validate Event Broker ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventBroker source)
+ {
+ //
+ var result =
+ !source.IsNull() &&
+ source != XEventBroker.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Event Service Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventServiceConfiguration source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ source.Broker.IsValid() &&
+ !source.Url.IsNullOrEmpty() &&
+ source.Url.IsValidUrl();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Exchange Type ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQExchangeType source)
+ {
+ //
+ var result = source != XRabbitMQExchangeType.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQOptions source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Exchange.IsNullOrEmpty() &&
+ source.ExchangeType.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Message ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventMessage source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Action.IsNullOrEmpty() &&
+ !source.Sender.IsNullOrEmpty();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Request ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventRequest source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Topic.IsNullOrEmpty() &&
+ source.Message.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Array is Wildcard passed or not ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedWildcard(this string[] source)
+ {
+ //
+ var result =
+ source is not null &&
+ source.Length == 1 &&
+ source.Any(s =>
+ s == XEventServiceConstants.XWildcard);
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate a Collection is Valid for Kafka Consumer ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedCollection(
+ this string[] source
+ )
+ {
+ //
+ var result = source.HasChild();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Host address ...
+ ///
+ ///
+ ///
+ public static string GetRabbitMQHost(this XEventServiceConfiguration source)
+ {
+ //
+ var result = string.Empty;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ result = parts[0];
+ if (result.IsNullOrEmpty() ||
+ !result.IsValidUrl()
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Port Number ...
+ ///
+ ///
+ ///
+ public static int GetRabbitMQPort(this XEventServiceConfiguration source)
+ {
+ //
+ int result = -1;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Try Parse Port as Integer ...
+ int.TryParse(parts[1], out result);
+ if (result <= 0)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXKafkaProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// a Service for Produce Kafka Messages ...
+ ///
+ public interface IXKafkaProducerService : IDisposable
+ {
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXRabbitMQProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public interface IXRabbitMQProducerService : IDisposable
+ {
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ ///
+ /// an Instance of .
+ /// Topic is used as Exchange Name ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message to Default Exchange ...
+ ///
+ ///
+ /// an Instance of ...
+ ///
+ ///
+ /// Target Exchange Name. if empty, uses default from configuration ...
+ ///
+ ///
+ /// Routing Key for message routing ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventMessage.cs
+
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Message Descriptor for Events ...
+ ///
+ public class XEventMessage
+ {
+ ///
+ /// Specified Action for Fire ...
+ ///
+ [JsonRequired]
+ public string Action { get; set; }
+
+ ///
+ /// Specified Action Sender ...
+ ///
+ [JsonRequired]
+ public string Sender { get; set; }
+
+ ///
+ /// Sending Time ...
+ ///
+ [JsonRequired]
+ public DateTime Offset { get; set; }
+
+ ///
+ /// Specified Message ...
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// Metadata for Message ...
+ ///
+ public IDictionary Payload { get; set; } = new Dictionary();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventRequest.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Request for Sending an Event ...
+ ///
+ public class XEventRequest
+ {
+ ///
+ /// Specified Which Kafka Topic ...
+ ///
+ public string Topic { get; set; } = XEventServiceConstants.XDefaultTopic;
+
+ ///
+ /// Specified Kafka Meesage to Send ...
+ ///
+ public XEventMessage Message { get; set; }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XRabbitMQOptions.cs
+
+using RabbitMQ.Client;
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// RabbitMQ-specific connection and messaging options ...
+ ///
+ public class XRabbitMQOptions
+ {
+
+ ///
+ /// Connection Username ...
+ ///
+ public string Username { get; set; } = "guest";
+
+ ///
+ /// Connection Password ...
+ ///
+ public string Password { get; set; } = "guest";
+
+ ///
+ /// Virtual Host ...
+ ///
+ public string VirtualHost { get; set; } = "/";
+
+ ///
+ /// Default Exchange Name ...
+ ///
+ public string Exchange { get; set; } = "x.saherelm.events";
+
+ ///
+ /// Default Exchange Type (fanout, direct, topic, headers) ...
+ ///
+ public XRabbitMQExchangeType ExchangeType { get; set; } = XRabbitMQExchangeType.Fanout;
+
+ ///
+ /// Default Queue Name ...
+ /// if empty, a dynamic queue will be generated ...
+ ///
+ public string Queue { get; set; } = string.Empty;
+
+ ///
+ /// Default Routing Key ...
+ ///
+ public string RoutingKey { get; set; } = string.Empty;
+
+ ///
+ /// Exchange Durable Flag ...
+ ///
+ public bool ExchangeDurable { get; set; } = true;
+
+ ///
+ /// Exchange AutoDelete Flag ...
+ ///
+ public bool ExchangeAutoDelete { get; set; } = false;
+
+ ///
+ /// Queue Durable Flag ...
+ ///
+ public bool QueueDurable { get; set; } = true;
+
+ ///
+ /// Queue Exclusive Flag ...
+ ///
+ public bool QueueExclusive { get; set; } = false;
+
+ ///
+ /// Queue AutoDelete Flag ...
+ ///
+ public bool QueueAutoDelete { get; set; } = false;
+
+ ///
+ /// Enable Automatic Recovery on Connection Loss ...
+ ///
+ public bool AutomaticRecoveryEnabled { get; set; } = true;
+
+ ///
+ /// Network Recovery Interval in Seconds ...
+ ///
+ public int NetworkRecoveryIntervalSeconds { get; set; } = 5;
+
+ ///
+ /// Prefetch Count for Consumer ...
+ ///
+ public ushort PrefetchCount { get; set; } = 1;
+
+ ///
+ /// Enable Manual Acknowledgement ...
+ ///
+ public bool ManualAck { get; set; } = true;
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XBaseEventConsumerBackgroundService.cs
+
+using System;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xCommons.Providers;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// Base Event Consumer Service ...
+ ///
+ public abstract class XBaseEventConsumerBackgroundService : XResumableBackgroundService
+ {
+ ///
+ /// Consumed Topic Messages ...
+ ///
+ protected readonly string topic;
+
+ ///
+ /// Allowed Senders ...
+ ///
+ public readonly string[] allowedSenders;
+
+ ///
+ /// Allowed Actions ...
+ ///
+ public readonly string[] allowedActions;
+
+ ///
+ /// Configuration ...
+ ///
+ protected readonly XEventServiceConfiguration configuration;
+
+ protected XBaseEventConsumerBackgroundService(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ logger: logger,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validate Actions ...
+ if (!allowedActions
+ .IsAllowedCollection() ||
+ !allowedSenders
+ .IsAllowedCollection() ||
+ !configuration.IsValid()
+ )
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ this.topic = topic;
+ this.configuration = configuration;
+ this.allowedActions = allowedActions;
+ this.allowedSenders = allowedSenders;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaConsumerServiceBase.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XKafkaConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ ///
+ /// Kafka Consumer Class ...
+ ///
+ protected readonly IConsumer consumer;
+
+ protected XKafkaConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (configuration.Broker != XEventBroker.XKafka)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Consumer Configuration ...
+ var config = new ConsumerConfig
+ {
+ EnableAutoCommit = false,
+ BootstrapServers = configuration.Url,
+ AutoOffsetReset = AutoOffsetReset.Earliest,
+ GroupId = XEventServiceConstants.XDefaultConsumerGroup,
+ };
+ consumer = new ConsumerBuilder(config).Build();
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Subscribe(topic);
+
+ //
+ // Consuming ...
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ //
+ // Consuming Topic ...
+ var consumeResult = consumer.Consume(cancellationToken);
+ if (!consumeResult.IsNullOrDefault() &&
+ !consumeResult.Message.IsNullOrDefault()
+ )
+ {
+ //
+ await DoWorkAsync(
+ payload: consumeResult.Message,
+ cancellationToken: cancellationToken
+ );
+ }
+ }
+ catch (ConsumeException)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ catch (Exception)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ consumer.Close();
+ consumer.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaProducerService.cs
+
+using xCommons.Extensions;
+using xExceptions.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Configuration;
+using xEventService.Models;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+using System.Threading;
+using xEventService.Constants;
+using System;
+
+namespace xEventService.Providers
+{
+ public class XKafkaProducerService : IXKafkaProducerService
+ {
+ private readonly IProducer producer;
+ private readonly ILogger logger;
+ private readonly XEventServiceConfiguration configuration;
+
+ public XKafkaProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ configuration.Broker != XEventBroker.XKafka
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Producer Configuration ...
+ var config = new ProducerConfig
+ {
+ Acks = Acks.All,
+ BootstrapServers = configuration.Url,
+ MessageSendMaxRetries = configuration.MaxRetries,
+ };
+ producer = new ProducerBuilder(config).Build();
+ }
+
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ topic: request.Topic,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid() &&
+ !topic.IsNullOrEmpty();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ try
+ {
+ //
+ var response = await producer.ProduceAsync(
+ topic: topic,
+ message: new Message
+ {
+ Value = message
+ .ToJSON(camelCase: true)
+ },
+ cancellationToken: cancellationToken
+ );
+
+ //
+ result = !response.IsNullOrDefault();
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Required Objects ...
+ ///
+ public void Dispose()
+ {
+ //
+ producer.Dispose();
+ GC.SuppressFinalize(this);
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQConsumerServiceBase.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XRabbitMQConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ protected IModel channel;
+ protected IConnection connection;
+ protected AsyncEventingBasicConsumer consumer;
+
+ protected XRabbitMQConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (!configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ SetupConnection();
+ DeclareTopology();
+ consumer = new AsyncEventingBasicConsumer(channel);
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Received += async (model, ea) =>
+ {
+ //
+ try
+ {
+ //
+ var body = ea.Body.ToArray();
+ var json = Encoding.UTF8.GetString(body);
+ var message = json.FromJSON();
+ if (!message.IsNullOrDefault())
+ {
+ //
+ await DoWorkAsync(
+ payload: message,
+ cancellationToken: cancellationToken
+ );
+ }
+
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicAck(
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ catch
+ {
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicNack(
+ requeue: true,
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ };
+
+ //
+ consumer.Shutdown += (model, ea) =>
+ {
+ return Task.CompletedTask;
+ };
+
+ //
+ channel.BasicQos(
+ prefetchSize: 0,
+ global: false,
+ prefetchCount: configuration.RabbitMQ.PrefetchCount
+ );
+
+ //
+ channel.BasicConsume(
+ queue: GetQueueName(),
+ consumer: consumer,
+ autoAck: !configuration.RabbitMQ.ManualAck
+ );
+
+ //
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+
+ ///
+ /// Overriding Stop Action ...
+ ///
+ ///
+ ///
+ public override async Task StopAsync(CancellationToken cancellationToken)
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ connection.Close();
+ connection.Dispose();
+
+ //
+ await base.StopAsync(cancellationToken);
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ channel.Dispose();
+ connection.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+
+ //
+ #region Private ...
+ private string GetQueueName()
+ {
+ //
+ var result = string.Empty;
+ if (!configuration.RabbitMQ.Queue.IsNullOrEmpty())
+ {
+ result = configuration.RabbitMQ.Queue;
+ }
+
+ //
+ if (result.IsNullOrEmpty())
+ {
+ //
+ var digits = Guid.NewGuid().ToString().GetDigits();
+ result = $"{configuration.RabbitMQ.Exchange}[{digits}]";
+ }
+
+ //
+ return result;
+ }
+
+ private void SetupConnection()
+ {
+ var factory = new ConnectionFactory
+ {
+ DispatchConsumersAsync = true,
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ };
+
+ //
+ connection = factory.CreateConnection();
+ channel = connection.CreateModel();
+ }
+
+ private void DeclareTopology()
+ {
+ //
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+
+ //
+ var queueName = GetQueueName();
+
+ //
+ channel.QueueDeclare(
+ arguments: null,
+ queue: queueName,
+ durable: configuration.RabbitMQ.QueueDurable,
+ exclusive: configuration.RabbitMQ.QueueExclusive,
+ autoDelete: configuration.RabbitMQ.QueueAutoDelete
+ );
+
+ //
+ channel.QueueBind(
+ arguments: null,
+ queue: queueName,
+ exchange: configuration.RabbitMQ.Exchange,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQProducerService.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public class XRabbitMQProducerService : IXRabbitMQProducerService
+ {
+ private IModel channel;
+ private IConnection connection;
+ private readonly object connectionLock = new();
+ private readonly ILogger logger;
+ private readonly XEventServiceConfiguration configuration;
+
+ public XRabbitMQProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ !configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Initialize Connection ...
+ CreateConnection();
+ }
+
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ exchange: request.Topic,
+ cancellationToken: cancellationToken,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message to Exchange ...
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ // Normalize Exchange ...
+ if (exchange.IsNullOrEmpty())
+ {
+ exchange = configuration.RabbitMQ.Exchange;
+ }
+
+ //
+ // Normalize Routing Key ...
+ if (routingKey.IsNull())
+ {
+ routingKey = configuration.RabbitMQ.RoutingKey;
+ }
+
+ //
+ try
+ {
+ //
+ // Ensure Connection is Alive ...
+ await EnsureChannelAsync(cancellationToken);
+
+ //
+ // Serialize Message ...
+ var body = Encoding.UTF8.GetBytes(
+ message.ToJSON(camelCase: true)
+ );
+
+ //
+ // Prepare Message Properties ...
+ var properties = channel.CreateBasicProperties();
+ properties.Persistent = true;
+ properties.ContentType = "application/json";
+ properties.Timestamp = new AmqpTimestamp(
+ DateTimeOffset.UtcNow.ToUnixTimeSeconds()
+ );
+ properties.MessageId = Guid.NewGuid().ToString();
+ properties.Type = message.Action;
+ properties.AppId = message.Sender;
+
+ //
+ // Publish Message ...
+ await Task.Run(() =>
+ {
+ //
+ channel.BasicPublish(
+ exchange: exchange,
+ routingKey: routingKey,
+ mandatory: false,
+ basicProperties: properties,
+ body: body
+ );
+ },
+ cancellationToken: cancellationToken);
+
+ //
+ result = true;
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public void Dispose()
+ {
+ //
+ try
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ channel = null;
+
+ //
+ connection.Close();
+ connection.Dispose();
+ connection = null;
+ }
+ catch
+ { }
+
+ //
+ GC.SuppressFinalize(this);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// Create Connection to RabbitMQ Server ...
+ ///
+ private void CreateConnection()
+ {
+ //
+ var factory = new ConnectionFactory
+ {
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ DispatchConsumersAsync = true
+ };
+
+ //
+ connection = factory.CreateConnection();
+ }
+
+ ///
+ /// Check Connection is Alive ...
+ ///
+ private bool IsConnected()
+ {
+ //
+ var result =
+ !connection.IsNullOrDefault() &&
+ connection.IsOpen &&
+ !channel.IsNull() &&
+ channel.IsOpen;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Ensure Connection Established ...
+ ///
+ private async Task EnsureConnectionAsync()
+ {
+ //
+ try
+ {
+ await EnsureChannelAsync();
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Ensure Channel is Open and Exchange/Queue Declared ...
+ ///
+ private async Task EnsureChannelAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ lock (connectionLock)
+ {
+ //
+ // Double Check after Lock ...
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ // Recreate Connection if Dead ...
+ if (connection is null || !connection.IsOpen)
+ {
+ CreateConnection();
+ }
+
+ //
+ // Create Channel ...
+ channel = connection.CreateModel();
+
+ //
+ // Declare Exchange ...
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+ }
+
+ //
+ await Task.CompletedTask;
+ }
+ #endregion
+ }
+}
diff --git a/Documents/GeneratedContext/xDashboard-xEventService-20260819_115418.txt b/Documents/GeneratedContext/xDashboard-xEventService-20260819_115418.txt
new file mode 100644
index 0000000..ab046d9
--- /dev/null
+++ b/Documents/GeneratedContext/xDashboard-xEventService-20260819_115418.txt
@@ -0,0 +1,1581 @@
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Configuration\XEventServiceConfiguration.cs
+
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Configuration
+{
+ ///
+ /// a configuration class for describing how to connect rabbit mq server
+ /// and how channels must be declared ...
+ ///
+ public class XEventServiceConfiguration
+ {
+ ///
+ /// Message Broker Address ...
+ ///
+ public string Url { get; set; }
+
+ ///
+ /// Max Retries to Sending Message ...
+ ///
+ public int MaxRetries { get; set; } = 3;
+
+ ///
+ /// Allowed Message Broker ...
+ ///
+ public XEventBroker Broker { get; set; } = XEventBroker.None;
+
+ ///
+ /// RabbitMQ Specific Options ...
+ /// Only used when Broker is XRabbitMQ ...
+ ///
+ public XRabbitMQOptions RabbitMQ { get; set; } = new XRabbitMQOptions();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\ConfigurationNodeNames.cs
+
+namespace xEventService.Constants
+{
+ public partial struct ConfigurationNodeNames {
+ public const string EVENT_SERVICE_NODE_NAME = "EventServiceConfiguration";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventBroker.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available Brokers ...
+ ///
+ public enum XEventBroker
+ {
+ [StringValue("None")]
+ None,
+
+ [StringValue("XKafka")]
+ XKafka,
+
+ [StringValue("XRabbitMQ")]
+ XRabbitMQ
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventServiceConstants.cs
+
+namespace xEventService.Constants
+{
+ public struct XEventServiceConstants
+ {
+ //
+ // Service Extensions Log Tag ...
+ public const string XEventServiceDILogTag = "XEventService";
+
+ //
+ // Defaults ...
+ public const string XDefaultTopic = "XSaherelm";
+ public const string XDefaultConsumerGroup = "XSaherElmGroup";
+
+ //
+ public const string XWildcard = "*";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XRabbitMQExchangeType.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available RabbitMQ Exchange Types ...
+ ///
+ public struct XRabbitMQExchangeTypes
+ {
+ public const string None = "none";
+ public const string Direct = "direct";
+ public const string Fanout = "fanout";
+ public const string Topic = "topic";
+ public const string Headers = "headers";
+ }
+
+ ///
+ /// RabbitMQ Exchange Type Enum ...
+ ///
+ public enum XRabbitMQExchangeType
+ {
+ [StringValue(XRabbitMQExchangeTypes.None)]
+ None,
+
+ [StringValue(XRabbitMQExchangeTypes.Direct)]
+ Direct,
+
+ [StringValue(XRabbitMQExchangeTypes.Fanout)]
+ Fanout,
+
+ [StringValue(XRabbitMQExchangeTypes.Topic)]
+ Topic,
+
+ [StringValue(XRabbitMQExchangeTypes.Headers)]
+ Headers,
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\DI\XDIHelperExtension.cs
+
+using System;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.DI
+{
+ public static partial class XDIHelperExtension
+ {
+ ///
+ /// Extract EventService Configurations ...
+ ///
+ ///
+ ///
+ public static XEventServiceConfiguration GetXEventServiceConfigurations(
+ this IConfiguration source
+ )
+ {
+ //
+ var configSection = source
+ .GetSection(ConfigurationNodeNames.EVENT_SERVICE_NODE_NAME);
+ var result = configSection.Get();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ IConfiguration configuration
+ )
+ {
+ //
+ var config = configuration.GetXEventServiceConfigurations();
+ services.AddXEventServiceConfigurations(config);
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ // Validate ...
+ if (!configuration.IsValid())
+ {
+ //
+ Log("Registration Failed, due Invalid Configuration ...");
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Register Configuration as Singleton ...
+ services.AddSingleton(configuration);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// a LogTag for Service ...
+ ///
+ private static string XLogTag = XEventServiceConstants.XEventServiceDILogTag;
+
+ ///
+ /// print a log in Console ...
+ ///
+ ///
+ private static void Log(string message)
+ {
+ Console.WriteLine($"{XLogTag} => {message}");
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Extensions\XEventServiceExtensions.cs
+
+using System.Linq;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Extensions
+{
+ ///
+ /// Extensions for Event Service ...
+ ///
+ public static class XEventServiceExtensions
+ {
+ ///
+ /// Validate Event Broker ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventBroker source)
+ {
+ //
+ var result =
+ !source.IsNull() &&
+ source != XEventBroker.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Event Service Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventServiceConfiguration source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ source.Broker.IsValid() &&
+ !source.Url.IsNullOrEmpty() &&
+ source.Url.IsValidUrl();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Exchange Type ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQExchangeType source)
+ {
+ //
+ var result = source != XRabbitMQExchangeType.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQOptions source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Exchange.IsNullOrEmpty() &&
+ source.ExchangeType.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Message ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventMessage source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Action.IsNullOrEmpty() &&
+ !source.Sender.IsNullOrEmpty();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Request ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventRequest source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Topic.IsNullOrEmpty() &&
+ source.Message.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Array is Wildcard passed or not ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedWildcard(this string[] source)
+ {
+ //
+ var result =
+ source is not null &&
+ source.Length == 1 &&
+ source.Any(s =>
+ s == XEventServiceConstants.XWildcard);
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate a Collection is Valid for Kafka Consumer ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedCollection(
+ this string[] source
+ )
+ {
+ //
+ var result = source.HasChild();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Host address ...
+ ///
+ ///
+ ///
+ public static string GetRabbitMQHost(this XEventServiceConfiguration source)
+ {
+ //
+ var result = string.Empty;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ result = parts[0];
+ if (result.IsNullOrEmpty() ||
+ !result.IsValidUrl()
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Port Number ...
+ ///
+ ///
+ ///
+ public static int GetRabbitMQPort(this XEventServiceConfiguration source)
+ {
+ //
+ int result = -1;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Try Parse Port as Integer ...
+ int.TryParse(parts[1], out result);
+ if (result <= 0)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXKafkaProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// a Service for Produce Kafka Messages ...
+ ///
+ public interface IXKafkaProducerService : IDisposable
+ {
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXRabbitMQProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public interface IXRabbitMQProducerService : IDisposable
+ {
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ ///
+ /// an Instance of .
+ /// Topic is used as Exchange Name ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message to Default Exchange ...
+ ///
+ ///
+ /// an Instance of ...
+ ///
+ ///
+ /// Target Exchange Name. if empty, uses default from configuration ...
+ ///
+ ///
+ /// Routing Key for message routing ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventMessage.cs
+
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Message Descriptor for Events ...
+ ///
+ public class XEventMessage
+ {
+ ///
+ /// Specified Action for Fire ...
+ ///
+ [JsonRequired]
+ public string Action { get; set; }
+
+ ///
+ /// Specified Action Sender ...
+ ///
+ [JsonRequired]
+ public string Sender { get; set; }
+
+ ///
+ /// Sending Time ...
+ ///
+ [JsonRequired]
+ public DateTime Offset { get; set; }
+
+ ///
+ /// Specified Message ...
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// Metadata for Message ...
+ ///
+ public IDictionary Payload { get; set; } = new Dictionary();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventRequest.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Request for Sending an Event ...
+ ///
+ public class XEventRequest
+ {
+ ///
+ /// Specified Which Kafka Topic ...
+ ///
+ public string Topic { get; set; } = XEventServiceConstants.XDefaultTopic;
+
+ ///
+ /// Specified Kafka Meesage to Send ...
+ ///
+ public XEventMessage Message { get; set; }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XRabbitMQOptions.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// RabbitMQ-specific connection and messaging options ...
+ ///
+ public class XRabbitMQOptions
+ {
+
+ ///
+ /// Connection Username ...
+ ///
+ public string Username { get; set; } = "guest";
+
+ ///
+ /// Connection Password ...
+ ///
+ public string Password { get; set; } = "guest";
+
+ ///
+ /// Virtual Host ...
+ ///
+ public string VirtualHost { get; set; } = "/";
+
+ ///
+ /// Default Exchange Name ...
+ ///
+ public string Exchange { get; set; } = "x.saherelm.events";
+
+ ///
+ /// Default Exchange Type (fanout, direct, topic, headers) ...
+ ///
+ public XRabbitMQExchangeType ExchangeType { get; set; } = XRabbitMQExchangeType.Fanout;
+
+ ///
+ /// Default Queue Name ...
+ /// if empty, a dynamic queue will be generated ...
+ ///
+ public string Queue { get; set; } = string.Empty;
+
+ ///
+ /// Default Routing Key ...
+ ///
+ public string RoutingKey { get; set; } = string.Empty;
+
+ ///
+ /// Exchange Durable Flag ...
+ ///
+ public bool ExchangeDurable { get; set; } = true;
+
+ ///
+ /// Exchange AutoDelete Flag ...
+ ///
+ public bool ExchangeAutoDelete { get; set; } = false;
+
+ ///
+ /// Queue Durable Flag ...
+ ///
+ public bool QueueDurable { get; set; } = true;
+
+ ///
+ /// Queue Exclusive Flag ...
+ ///
+ public bool QueueExclusive { get; set; } = false;
+
+ ///
+ /// Queue AutoDelete Flag ...
+ ///
+ public bool QueueAutoDelete { get; set; } = false;
+
+ ///
+ /// Enable Automatic Recovery on Connection Loss ...
+ ///
+ public bool AutomaticRecoveryEnabled { get; set; } = true;
+
+ ///
+ /// Network Recovery Interval in Seconds ...
+ ///
+ public int NetworkRecoveryIntervalSeconds { get; set; } = 5;
+
+ ///
+ /// Prefetch Count for Consumer ...
+ ///
+ public ushort PrefetchCount { get; set; } = 1;
+
+ ///
+ /// Enable Manual Acknowledgement ...
+ ///
+ public bool ManualAck { get; set; } = true;
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XBaseEventConsumerBackgroundService.cs
+
+using System;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xCommons.Providers;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// Base Event Consumer Service ...
+ ///
+ public abstract class XBaseEventConsumerBackgroundService : XResumableBackgroundService
+ {
+ ///
+ /// Consumed Topic Messages ...
+ ///
+ protected readonly string topic;
+
+ ///
+ /// Allowed Senders ...
+ ///
+ public readonly string[] allowedSenders;
+
+ ///
+ /// Allowed Actions ...
+ ///
+ public readonly string[] allowedActions;
+
+ ///
+ /// Configuration ...
+ ///
+ protected readonly XEventServiceConfiguration configuration;
+
+ protected XBaseEventConsumerBackgroundService(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ logger: logger,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validate Actions ...
+ if (!allowedActions
+ .IsAllowedCollection() ||
+ !allowedSenders
+ .IsAllowedCollection() ||
+ !configuration.IsValid()
+ )
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ this.topic = topic;
+ this.configuration = configuration;
+ this.allowedActions = allowedActions;
+ this.allowedSenders = allowedSenders;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaConsumerServiceBase.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XKafkaConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ ///
+ /// Kafka Consumer Class ...
+ ///
+ protected readonly IConsumer consumer;
+
+ protected XKafkaConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (configuration.Broker != XEventBroker.XKafka)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Consumer Configuration ...
+ var config = new ConsumerConfig
+ {
+ EnableAutoCommit = false,
+ BootstrapServers = configuration.Url,
+ AutoOffsetReset = AutoOffsetReset.Earliest,
+ GroupId = XEventServiceConstants.XDefaultConsumerGroup,
+ };
+ consumer = new ConsumerBuilder(config).Build();
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Subscribe(topic);
+
+ //
+ // Consuming ...
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ //
+ // Consuming Topic ...
+ var consumeResult = consumer.Consume(cancellationToken);
+ if (!consumeResult.IsNullOrDefault() &&
+ !consumeResult.Message.IsNullOrDefault()
+ )
+ {
+ //
+ await DoWorkAsync(
+ payload: consumeResult.Message,
+ cancellationToken: cancellationToken
+ );
+ }
+ }
+ catch (ConsumeException)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ catch (Exception)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ consumer.Close();
+ consumer.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaProducerService.cs
+
+using xCommons.Extensions;
+using xExceptions.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Configuration;
+using xEventService.Models;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+using System.Threading;
+using xEventService.Constants;
+using System;
+
+namespace xEventService.Providers
+{
+ public class XKafkaProducerService : IXKafkaProducerService
+ {
+ private readonly IProducer producer;
+ private readonly ILogger logger;
+ private readonly XEventServiceConfiguration configuration;
+
+ public XKafkaProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ configuration.Broker != XEventBroker.XKafka
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Producer Configuration ...
+ var config = new ProducerConfig
+ {
+ Acks = Acks.All,
+ BootstrapServers = configuration.Url,
+ MessageSendMaxRetries = configuration.MaxRetries,
+ };
+ producer = new ProducerBuilder(config).Build();
+ }
+
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ topic: request.Topic,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid() &&
+ !topic.IsNullOrEmpty();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ try
+ {
+ //
+ var response = await producer.ProduceAsync(
+ topic: topic,
+ message: new Message
+ {
+ Value = message
+ .ToJSON(camelCase: true)
+ },
+ cancellationToken: cancellationToken
+ );
+
+ //
+ result = !response.IsNullOrDefault();
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Required Objects ...
+ ///
+ public void Dispose()
+ {
+ //
+ producer.Dispose();
+ GC.SuppressFinalize(this);
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQConsumerServiceBase.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XRabbitMQConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ protected IModel channel;
+ protected IConnection connection;
+ protected AsyncEventingBasicConsumer consumer;
+
+ protected XRabbitMQConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (!configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ SetupConnection();
+ DeclareTopology();
+ consumer = new AsyncEventingBasicConsumer(channel);
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Received += async (model, ea) =>
+ {
+ //
+ try
+ {
+ //
+ var body = ea.Body.ToArray();
+ var json = Encoding.UTF8.GetString(body);
+ var message = json.FromJSON();
+ if (!message.IsNullOrDefault())
+ {
+ //
+ await DoWorkAsync(
+ payload: message,
+ cancellationToken: cancellationToken
+ );
+ }
+
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicAck(
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ catch
+ {
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicNack(
+ requeue: true,
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ };
+
+ //
+ consumer.Shutdown += (model, ea) =>
+ {
+ return Task.CompletedTask;
+ };
+
+ //
+ channel.BasicQos(
+ prefetchSize: 0,
+ global: false,
+ prefetchCount: configuration.RabbitMQ.PrefetchCount
+ );
+
+ //
+ channel.BasicConsume(
+ queue: GetQueueName(),
+ consumer: consumer,
+ autoAck: !configuration.RabbitMQ.ManualAck
+ );
+
+ //
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+
+ ///
+ /// Overriding Stop Action ...
+ ///
+ ///
+ ///
+ public override async Task StopAsync(CancellationToken cancellationToken)
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ connection.Close();
+ connection.Dispose();
+
+ //
+ await base.StopAsync(cancellationToken);
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ channel.Dispose();
+ connection.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+
+ //
+ #region Private ...
+ private string GetQueueName()
+ {
+ //
+ var result = string.Empty;
+ if (!configuration.RabbitMQ.Queue.IsNullOrEmpty())
+ {
+ result = configuration.RabbitMQ.Queue;
+ }
+
+ //
+ if (result.IsNullOrEmpty())
+ {
+ //
+ var digits = Guid.NewGuid().ToString().GetDigits();
+ result = $"{configuration.RabbitMQ.Exchange}[{digits}]";
+ }
+
+ //
+ return result;
+ }
+
+ private void SetupConnection()
+ {
+ var factory = new ConnectionFactory
+ {
+ DispatchConsumersAsync = true,
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ };
+
+ //
+ connection = factory.CreateConnection();
+ channel = connection.CreateModel();
+ }
+
+ private void DeclareTopology()
+ {
+ //
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+
+ //
+ var queueName = GetQueueName();
+
+ //
+ channel.QueueDeclare(
+ arguments: null,
+ queue: queueName,
+ durable: configuration.RabbitMQ.QueueDurable,
+ exclusive: configuration.RabbitMQ.QueueExclusive,
+ autoDelete: configuration.RabbitMQ.QueueAutoDelete
+ );
+
+ //
+ channel.QueueBind(
+ arguments: null,
+ queue: queueName,
+ exchange: configuration.RabbitMQ.Exchange,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQProducerService.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public class XRabbitMQProducerService : IXRabbitMQProducerService
+ {
+ private IModel channel;
+ private IConnection connection;
+ private readonly object connectionLock = new();
+ private readonly ILogger logger;
+ private readonly XEventServiceConfiguration configuration;
+
+ public XRabbitMQProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ !configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Initialize Connection ...
+ CreateConnection();
+ }
+
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ exchange: request.Topic,
+ cancellationToken: cancellationToken,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message to Exchange ...
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ // Normalize Exchange ...
+ if (exchange.IsNullOrEmpty())
+ {
+ exchange = configuration.RabbitMQ.Exchange;
+ }
+
+ //
+ // Normalize Routing Key ...
+ if (routingKey.IsNull())
+ {
+ routingKey = configuration.RabbitMQ.RoutingKey;
+ }
+
+ //
+ try
+ {
+ //
+ // Ensure Connection is Alive ...
+ await EnsureChannelAsync(cancellationToken);
+
+ //
+ // Serialize Message ...
+ var body = Encoding.UTF8.GetBytes(
+ message.ToJSON(camelCase: true)
+ );
+
+ //
+ // Prepare Message Properties ...
+ var properties = channel.CreateBasicProperties();
+ properties.Persistent = true;
+ properties.ContentType = "application/json";
+ properties.Timestamp = new AmqpTimestamp(
+ DateTimeOffset.UtcNow.ToUnixTimeSeconds()
+ );
+ properties.MessageId = Guid.NewGuid().ToString();
+ properties.Type = message.Action;
+ properties.AppId = message.Sender;
+
+ //
+ // Publish Message ...
+ await Task.Run(() =>
+ {
+ //
+ channel.BasicPublish(
+ exchange: exchange,
+ routingKey: routingKey,
+ mandatory: false,
+ basicProperties: properties,
+ body: body
+ );
+ },
+ cancellationToken: cancellationToken);
+
+ //
+ result = true;
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public void Dispose()
+ {
+ //
+ try
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ channel = null;
+
+ //
+ connection.Close();
+ connection.Dispose();
+ connection = null;
+ }
+ catch
+ { }
+
+ //
+ GC.SuppressFinalize(this);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// Create Connection to RabbitMQ Server ...
+ ///
+ private void CreateConnection()
+ {
+ //
+ var factory = new ConnectionFactory
+ {
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ DispatchConsumersAsync = true
+ };
+
+ //
+ connection = factory.CreateConnection();
+ }
+
+ ///
+ /// Check Connection is Alive ...
+ ///
+ private bool IsConnected()
+ {
+ //
+ var result =
+ !connection.IsNullOrDefault() &&
+ connection.IsOpen &&
+ !channel.IsNull() &&
+ channel.IsOpen;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Ensure Connection Established ...
+ ///
+ private async Task EnsureConnectionAsync()
+ {
+ //
+ try
+ {
+ await EnsureChannelAsync();
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Ensure Channel is Open and Exchange/Queue Declared ...
+ ///
+ private async Task EnsureChannelAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ lock (connectionLock)
+ {
+ //
+ // Double Check after Lock ...
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ // Recreate Connection if Dead ...
+ if (connection is null || !connection.IsOpen)
+ {
+ CreateConnection();
+ }
+
+ //
+ // Create Channel ...
+ channel = connection.CreateModel();
+
+ //
+ // Declare Exchange ...
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+ }
+
+ //
+ await Task.CompletedTask;
+ }
+ #endregion
+ }
+}
diff --git a/Documents/GeneratedContext/xDashboard-xEventService-20260819_115821.txt b/Documents/GeneratedContext/xDashboard-xEventService-20260819_115821.txt
new file mode 100644
index 0000000..6ad2f64
--- /dev/null
+++ b/Documents/GeneratedContext/xDashboard-xEventService-20260819_115821.txt
@@ -0,0 +1,1578 @@
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Configuration\XEventServiceConfiguration.cs
+
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Configuration
+{
+ ///
+ /// a configuration class for describing how to connect rabbit mq server
+ /// and how channels must be declared ...
+ ///
+ public class XEventServiceConfiguration
+ {
+ ///
+ /// Message Broker Address ...
+ ///
+ public string Url { get; set; }
+
+ ///
+ /// Max Retries to Sending Message ...
+ ///
+ public int MaxRetries { get; set; } = 3;
+
+ ///
+ /// Allowed Message Broker ...
+ ///
+ public XEventBroker Broker { get; set; } = XEventBroker.None;
+
+ ///
+ /// RabbitMQ Specific Options ...
+ /// Only used when Broker is XRabbitMQ ...
+ ///
+ public XRabbitMQOptions RabbitMQ { get; set; } = new XRabbitMQOptions();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\ConfigurationNodeNames.cs
+
+namespace xEventService.Constants
+{
+ public partial struct ConfigurationNodeNames {
+ public const string EVENT_SERVICE_NODE_NAME = "EventServiceConfiguration";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventBroker.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available Brokers ...
+ ///
+ public enum XEventBroker
+ {
+ [StringValue("None")]
+ None,
+
+ [StringValue("XKafka")]
+ XKafka,
+
+ [StringValue("XRabbitMQ")]
+ XRabbitMQ
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventServiceConstants.cs
+
+namespace xEventService.Constants
+{
+ public struct XEventServiceConstants
+ {
+ //
+ // Service Extensions Log Tag ...
+ public const string XEventServiceDILogTag = "XEventService";
+
+ //
+ // Defaults ...
+ public const string XDefaultTopic = "XSaherelm";
+ public const string XDefaultConsumerGroup = "XSaherElmGroup";
+
+ //
+ public const string XWildcard = "*";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XRabbitMQExchangeType.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available RabbitMQ Exchange Types ...
+ ///
+ public struct XRabbitMQExchangeTypes
+ {
+ public const string None = "none";
+ public const string Direct = "direct";
+ public const string Fanout = "fanout";
+ public const string Topic = "topic";
+ public const string Headers = "headers";
+ }
+
+ ///
+ /// RabbitMQ Exchange Type Enum ...
+ ///
+ public enum XRabbitMQExchangeType
+ {
+ [StringValue(XRabbitMQExchangeTypes.None)]
+ None,
+
+ [StringValue(XRabbitMQExchangeTypes.Direct)]
+ Direct,
+
+ [StringValue(XRabbitMQExchangeTypes.Fanout)]
+ Fanout,
+
+ [StringValue(XRabbitMQExchangeTypes.Topic)]
+ Topic,
+
+ [StringValue(XRabbitMQExchangeTypes.Headers)]
+ Headers,
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\DI\XDIHelperExtension.cs
+
+using System;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.DI
+{
+ public static partial class XDIHelperExtension
+ {
+ ///
+ /// Extract EventService Configurations ...
+ ///
+ ///
+ ///
+ public static XEventServiceConfiguration GetXEventServiceConfigurations(
+ this IConfiguration source
+ )
+ {
+ //
+ var configSection = source
+ .GetSection(ConfigurationNodeNames.EVENT_SERVICE_NODE_NAME);
+ var result = configSection.Get();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ IConfiguration configuration
+ )
+ {
+ //
+ var config = configuration.GetXEventServiceConfigurations();
+ services.AddXEventServiceConfigurations(config);
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ // Validate ...
+ if (!configuration.IsValid())
+ {
+ //
+ Log("Registration Failed, due Invalid Configuration ...");
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Register Configuration as Singleton ...
+ services.AddSingleton(configuration);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// a LogTag for Service ...
+ ///
+ private static string XLogTag = XEventServiceConstants.XEventServiceDILogTag;
+
+ ///
+ /// print a log in Console ...
+ ///
+ ///
+ private static void Log(string message)
+ {
+ Console.WriteLine($"{XLogTag} => {message}");
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Extensions\XEventServiceExtensions.cs
+
+using System.Linq;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Extensions
+{
+ ///
+ /// Extensions for Event Service ...
+ ///
+ public static class XEventServiceExtensions
+ {
+ ///
+ /// Validate Event Broker ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventBroker source)
+ {
+ //
+ var result =
+ !source.IsNull() &&
+ source != XEventBroker.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Event Service Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventServiceConfiguration source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ source.Broker.IsValid() &&
+ !source.Url.IsNullOrEmpty() &&
+ source.Url.IsValidUrl();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Exchange Type ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQExchangeType source)
+ {
+ //
+ var result = source != XRabbitMQExchangeType.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQOptions source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Exchange.IsNullOrEmpty() &&
+ source.ExchangeType.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Message ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventMessage source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Action.IsNullOrEmpty() &&
+ !source.Sender.IsNullOrEmpty();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Request ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventRequest source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Topic.IsNullOrEmpty() &&
+ source.Message.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Array is Wildcard passed or not ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedWildcard(this string[] source)
+ {
+ //
+ var result =
+ source is not null &&
+ source.Length == 1 &&
+ source.Any(s =>
+ s == XEventServiceConstants.XWildcard);
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate a Collection is Valid for Kafka Consumer ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedCollection(
+ this string[] source
+ )
+ {
+ //
+ var result = source.HasChild();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Host address ...
+ ///
+ ///
+ ///
+ public static string GetRabbitMQHost(this XEventServiceConfiguration source)
+ {
+ //
+ var result = string.Empty;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ result = parts[0];
+ if (result.IsNullOrEmpty() ||
+ !result.IsValidUrl()
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Port Number ...
+ ///
+ ///
+ ///
+ public static int GetRabbitMQPort(this XEventServiceConfiguration source)
+ {
+ //
+ int result = -1;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Try Parse Port as Integer ...
+ int.TryParse(parts[1], out result);
+ if (result <= 0)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXKafkaProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// a Service for Produce Kafka Messages ...
+ ///
+ public interface IXKafkaProducerService : IDisposable
+ {
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXRabbitMQProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public interface IXRabbitMQProducerService : IDisposable
+ {
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ ///
+ /// an Instance of .
+ /// Topic is used as Exchange Name ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message to Default Exchange ...
+ ///
+ ///
+ /// an Instance of ...
+ ///
+ ///
+ /// Target Exchange Name. if empty, uses default from configuration ...
+ ///
+ ///
+ /// Routing Key for message routing ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventMessage.cs
+
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Message Descriptor for Events ...
+ ///
+ public class XEventMessage
+ {
+ ///
+ /// Specified Action for Fire ...
+ ///
+ [JsonRequired]
+ public string Action { get; set; }
+
+ ///
+ /// Specified Action Sender ...
+ ///
+ [JsonRequired]
+ public string Sender { get; set; }
+
+ ///
+ /// Sending Time ...
+ ///
+ [JsonRequired]
+ public DateTime Offset { get; set; }
+
+ ///
+ /// Specified Message ...
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// Metadata for Message ...
+ ///
+ public IDictionary Payload { get; set; } = new Dictionary();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventRequest.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Request for Sending an Event ...
+ ///
+ public class XEventRequest
+ {
+ ///
+ /// Specified Which Kafka Topic ...
+ ///
+ public string Topic { get; set; } = XEventServiceConstants.XDefaultTopic;
+
+ ///
+ /// Specified Kafka Meesage to Send ...
+ ///
+ public XEventMessage Message { get; set; }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XRabbitMQOptions.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// RabbitMQ-specific connection and messaging options ...
+ ///
+ public class XRabbitMQOptions
+ {
+
+ ///
+ /// Connection Username ...
+ ///
+ public string Username { get; set; } = "guest";
+
+ ///
+ /// Connection Password ...
+ ///
+ public string Password { get; set; } = "guest";
+
+ ///
+ /// Virtual Host ...
+ ///
+ public string VirtualHost { get; set; } = "/";
+
+ ///
+ /// Default Exchange Name ...
+ ///
+ public string Exchange { get; set; } = "x.saherelm.events";
+
+ ///
+ /// Default Exchange Type (fanout, direct, topic, headers) ...
+ ///
+ public XRabbitMQExchangeType ExchangeType { get; set; } = XRabbitMQExchangeType.Fanout;
+
+ ///
+ /// Default Queue Name ...
+ /// if empty, a dynamic queue will be generated ...
+ ///
+ public string Queue { get; set; } = string.Empty;
+
+ ///
+ /// Default Routing Key ...
+ ///
+ public string RoutingKey { get; set; } = string.Empty;
+
+ ///
+ /// Exchange Durable Flag ...
+ ///
+ public bool ExchangeDurable { get; set; } = true;
+
+ ///
+ /// Exchange AutoDelete Flag ...
+ ///
+ public bool ExchangeAutoDelete { get; set; } = false;
+
+ ///
+ /// Queue Durable Flag ...
+ ///
+ public bool QueueDurable { get; set; } = true;
+
+ ///
+ /// Queue Exclusive Flag ...
+ ///
+ public bool QueueExclusive { get; set; } = false;
+
+ ///
+ /// Queue AutoDelete Flag ...
+ ///
+ public bool QueueAutoDelete { get; set; } = false;
+
+ ///
+ /// Enable Automatic Recovery on Connection Loss ...
+ ///
+ public bool AutomaticRecoveryEnabled { get; set; } = true;
+
+ ///
+ /// Network Recovery Interval in Seconds ...
+ ///
+ public int NetworkRecoveryIntervalSeconds { get; set; } = 5;
+
+ ///
+ /// Prefetch Count for Consumer ...
+ ///
+ public ushort PrefetchCount { get; set; } = 1;
+
+ ///
+ /// Enable Manual Acknowledgement ...
+ ///
+ public bool ManualAck { get; set; } = true;
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XBaseEventConsumerBackgroundService.cs
+
+using System;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xCommons.Providers;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// Base Event Consumer Service ...
+ ///
+ public abstract class XBaseEventConsumerBackgroundService : XResumableBackgroundService
+ {
+ ///
+ /// Consumed Topic Messages ...
+ ///
+ protected readonly string topic;
+
+ ///
+ /// Allowed Senders ...
+ ///
+ public readonly string[] allowedSenders;
+
+ ///
+ /// Allowed Actions ...
+ ///
+ public readonly string[] allowedActions;
+
+ ///
+ /// Configuration ...
+ ///
+ protected readonly XEventServiceConfiguration configuration;
+
+ protected XBaseEventConsumerBackgroundService(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ logger: logger,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validate Actions ...
+ if (!allowedActions
+ .IsAllowedCollection() ||
+ !allowedSenders
+ .IsAllowedCollection() ||
+ !configuration.IsValid()
+ )
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ this.topic = topic;
+ this.configuration = configuration;
+ this.allowedActions = allowedActions;
+ this.allowedSenders = allowedSenders;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaConsumerServiceBase.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XKafkaConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ ///
+ /// Kafka Consumer Class ...
+ ///
+ protected readonly IConsumer consumer;
+
+ protected XKafkaConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (configuration.Broker != XEventBroker.XKafka)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Consumer Configuration ...
+ var config = new ConsumerConfig
+ {
+ EnableAutoCommit = false,
+ BootstrapServers = configuration.Url,
+ AutoOffsetReset = AutoOffsetReset.Earliest,
+ GroupId = XEventServiceConstants.XDefaultConsumerGroup,
+ };
+ consumer = new ConsumerBuilder(config).Build();
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Subscribe(topic);
+
+ //
+ // Consuming ...
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ //
+ // Consuming Topic ...
+ var consumeResult = consumer.Consume(cancellationToken);
+ if (!consumeResult.IsNullOrDefault() &&
+ !consumeResult.Message.IsNullOrDefault()
+ )
+ {
+ //
+ await DoWorkAsync(
+ payload: consumeResult.Message,
+ cancellationToken: cancellationToken
+ );
+ }
+ }
+ catch (ConsumeException)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ catch (Exception)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ consumer.Close();
+ consumer.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaProducerService.cs
+
+using xCommons.Extensions;
+using xExceptions.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Configuration;
+using xEventService.Models;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+using System.Threading;
+using xEventService.Constants;
+using System;
+
+namespace xEventService.Providers
+{
+ public class XKafkaProducerService : IXKafkaProducerService
+ {
+ private readonly IProducer producer;
+ private readonly ILogger logger;
+ private readonly XEventServiceConfiguration configuration;
+
+ public XKafkaProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ configuration.Broker != XEventBroker.XKafka
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Producer Configuration ...
+ var config = new ProducerConfig
+ {
+ Acks = Acks.All,
+ BootstrapServers = configuration.Url,
+ MessageSendMaxRetries = configuration.MaxRetries,
+ };
+ producer = new ProducerBuilder(config).Build();
+ }
+
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ topic: request.Topic,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid() &&
+ !topic.IsNullOrEmpty();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ try
+ {
+ //
+ var response = await producer.ProduceAsync(
+ topic: topic,
+ message: new Message
+ {
+ Value = message
+ .ToJSON(camelCase: true)
+ },
+ cancellationToken: cancellationToken
+ );
+
+ //
+ result = !response.IsNullOrDefault();
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Required Objects ...
+ ///
+ public void Dispose()
+ {
+ //
+ producer.Dispose();
+ GC.SuppressFinalize(this);
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQConsumerServiceBase.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XRabbitMQConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ protected IModel channel;
+ protected IConnection connection;
+ protected AsyncEventingBasicConsumer consumer;
+
+ protected XRabbitMQConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (!configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ SetupConnection();
+ DeclareTopology();
+ consumer = new AsyncEventingBasicConsumer(channel);
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Received += async (model, ea) =>
+ {
+ //
+ try
+ {
+ //
+ var body = ea.Body.ToArray();
+ var json = Encoding.UTF8.GetString(body);
+ var message = json.FromJSON();
+ if (!message.IsNullOrDefault())
+ {
+ //
+ await DoWorkAsync(
+ payload: message,
+ cancellationToken: cancellationToken
+ );
+ }
+
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicAck(
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ catch
+ {
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicNack(
+ requeue: true,
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ };
+
+ //
+ consumer.Shutdown += (model, ea) =>
+ {
+ return Task.CompletedTask;
+ };
+
+ //
+ channel.BasicQos(
+ prefetchSize: 0,
+ global: false,
+ prefetchCount: configuration.RabbitMQ.PrefetchCount
+ );
+
+ //
+ channel.BasicConsume(
+ queue: GetQueueName(),
+ consumer: consumer,
+ autoAck: !configuration.RabbitMQ.ManualAck
+ );
+
+ //
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+
+ ///
+ /// Overriding Stop Action ...
+ ///
+ ///
+ ///
+ public override async Task StopAsync(CancellationToken cancellationToken)
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ connection.Close();
+ connection.Dispose();
+
+ //
+ await base.StopAsync(cancellationToken);
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ channel.Dispose();
+ connection.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+
+ //
+ #region Private ...
+ private string GetQueueName()
+ {
+ //
+ var result = string.Empty;
+ if (!configuration.RabbitMQ.Queue.IsNullOrEmpty())
+ {
+ result = configuration.RabbitMQ.Queue;
+ }
+
+ //
+ if (result.IsNullOrEmpty())
+ {
+ //
+ var digits = Guid.NewGuid().ToString().GetDigits();
+ result = $"{configuration.RabbitMQ.Exchange}[{digits}]";
+ }
+
+ //
+ return result;
+ }
+
+ private void SetupConnection()
+ {
+ var factory = new ConnectionFactory
+ {
+ DispatchConsumersAsync = true,
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ };
+
+ //
+ connection = factory.CreateConnection();
+ channel = connection.CreateModel();
+ }
+
+ private void DeclareTopology()
+ {
+ //
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+
+ //
+ var queueName = GetQueueName();
+
+ //
+ channel.QueueDeclare(
+ arguments: null,
+ queue: queueName,
+ durable: configuration.RabbitMQ.QueueDurable,
+ exclusive: configuration.RabbitMQ.QueueExclusive,
+ autoDelete: configuration.RabbitMQ.QueueAutoDelete
+ );
+
+ //
+ channel.QueueBind(
+ arguments: null,
+ queue: queueName,
+ exchange: configuration.RabbitMQ.Exchange,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQProducerService.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public class XRabbitMQProducerService : IXRabbitMQProducerService
+ {
+ private IModel channel;
+ private IConnection connection;
+ private readonly object connectionLock = new();
+ private readonly ILogger logger;
+ private readonly XEventServiceConfiguration configuration;
+
+ public XRabbitMQProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ !configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Initialize Connection ...
+ CreateConnection();
+ }
+
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ exchange: request.Topic,
+ cancellationToken: cancellationToken,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message to Exchange ...
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ // Normalize Exchange ...
+ if (exchange.IsNullOrEmpty())
+ {
+ exchange = configuration.RabbitMQ.Exchange;
+ }
+
+ //
+ // Normalize Routing Key ...
+ if (routingKey.IsNull())
+ {
+ routingKey = configuration.RabbitMQ.RoutingKey;
+ }
+
+ //
+ try
+ {
+ //
+ // Ensure Connection is Alive ...
+ await EnsureChannelAsync(cancellationToken);
+
+ //
+ // Serialize Message ...
+ var body = Encoding.UTF8.GetBytes(
+ message.ToJSON(camelCase: true)
+ );
+
+ //
+ // Prepare Message Properties ...
+ var properties = channel.CreateBasicProperties();
+ properties.Persistent = true;
+ properties.ContentType = "application/json";
+ properties.Timestamp = new AmqpTimestamp(
+ DateTimeOffset.UtcNow.ToUnixTimeSeconds()
+ );
+ properties.MessageId = Guid.NewGuid().ToString();
+ properties.Type = message.Action;
+ properties.AppId = message.Sender;
+
+ //
+ // Publish Message ...
+ await Task.Run(() =>
+ {
+ //
+ channel.BasicPublish(
+ exchange: exchange,
+ routingKey: routingKey,
+ mandatory: false,
+ basicProperties: properties,
+ body: body
+ );
+ },
+ cancellationToken: cancellationToken);
+
+ //
+ result = true;
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public void Dispose()
+ {
+ //
+ try
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ channel = null;
+
+ //
+ connection.Close();
+ connection.Dispose();
+ connection = null;
+ }
+ catch
+ { }
+
+ //
+ GC.SuppressFinalize(this);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// Create Connection to RabbitMQ Server ...
+ ///
+ private void CreateConnection()
+ {
+ //
+ var factory = new ConnectionFactory
+ {
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ DispatchConsumersAsync = true
+ };
+
+ //
+ connection = factory.CreateConnection();
+ }
+
+ ///
+ /// Check Connection is Alive ...
+ ///
+ private bool IsConnected()
+ {
+ //
+ var result =
+ !connection.IsNullOrDefault() &&
+ connection.IsOpen &&
+ !channel.IsNull() &&
+ channel.IsOpen;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Ensure Connection Established ...
+ ///
+ private async Task EnsureConnectionAsync()
+ {
+ //
+ try
+ {
+ await EnsureChannelAsync();
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Ensure Channel is Open and Exchange/Queue Declared ...
+ ///
+ private async Task EnsureChannelAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ lock (connectionLock)
+ {
+ //
+ // Double Check after Lock ...
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ // Recreate Connection if Dead ...
+ if (connection is null || !connection.IsOpen)
+ {
+ CreateConnection();
+ }
+
+ //
+ // Create Channel ...
+ channel = connection.CreateModel();
+
+ //
+ // Declare Exchange ...
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+ }
+ }
+ #endregion
+ }
+}
diff --git a/Documents/GeneratedContext/xDashboard-xEventService-20260819_143307.txt b/Documents/GeneratedContext/xDashboard-xEventService-20260819_143307.txt
new file mode 100644
index 0000000..6ced644
--- /dev/null
+++ b/Documents/GeneratedContext/xDashboard-xEventService-20260819_143307.txt
@@ -0,0 +1,2211 @@
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Configuration\XEventServiceConfiguration.cs
+
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Configuration
+{
+ ///
+ /// a configuration class for describing how to connect rabbit mq server
+ /// and how channels must be declared ...
+ ///
+ public class XEventServiceConfiguration
+ {
+ ///
+ /// Message Broker Address ...
+ ///
+ public string Url { get; set; }
+
+ ///
+ /// Max Retries to Sending Message ...
+ ///
+ public int MaxRetries { get; set; } = 3;
+
+ ///
+ /// Allowed Message Broker ...
+ ///
+ public XEventBroker Broker { get; set; } = XEventBroker.None;
+
+ ///
+ /// ZeroMQ Specific Options ...
+ /// Only used when Broker is XZeroMQ ...
+ ///
+ public XZeroMQOptions ZeroMQ { get; set; } = new XZeroMQOptions();
+
+ ///
+ /// RabbitMQ Specific Options ...
+ /// Only used when Broker is XRabbitMQ ...
+ ///
+ public XRabbitMQOptions RabbitMQ { get; set; } = new XRabbitMQOptions();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\ConfigurationNodeNames.cs
+
+namespace xEventService.Constants
+{
+ public partial struct ConfigurationNodeNames {
+ public const string EVENT_SERVICE_NODE_NAME = "EventServiceConfiguration";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventBroker.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available Brokers ...
+ ///
+ public enum XEventBroker
+ {
+ [StringValue(XEventServiceConstants.None)]
+ None,
+
+ [StringValue(XEventServiceConstants.XKafka)]
+ XKafka,
+
+ [StringValue(XEventServiceConstants.XZeroMQ)]
+ XZeroMQ,
+
+ [StringValue(XEventServiceConstants.XRabbitMQ)]
+ XRabbitMQ,
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventServiceConstants.cs
+
+namespace xEventService.Constants
+{
+ public struct XEventServiceConstants
+ {
+ //
+ // Service Extensions Log Tag ...
+ public const string XEventServiceDILogTag = "XEventService";
+
+ //
+ // Defaults ...
+ public const string XDefaultTopic = "XSaherelm";
+ public const string XDefaultConsumerGroup = "XSaherElmGroup";
+
+ //
+ public const string None = "None";
+ public const string XKafka = "XKafka";
+ public const string PubSub = "PubSub";
+ public const string XZeroMQ = "XZeroMQ";
+ public const string PushPull = "PushPull";
+ public const string XRabbitMQ = "XRabbitMQ";
+
+ //
+ public const string XWildcard = "*";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XRabbitMQExchangeType.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available RabbitMQ Exchange Types ...
+ ///
+ public struct XRabbitMQExchangeTypes
+ {
+ public const string None = "none";
+ public const string Direct = "direct";
+ public const string Fanout = "fanout";
+ public const string Topic = "topic";
+ public const string Headers = "headers";
+ }
+
+ ///
+ /// RabbitMQ Exchange Type Enum ...
+ ///
+ public enum XRabbitMQExchangeType
+ {
+ [StringValue(XRabbitMQExchangeTypes.None)]
+ None,
+
+ [StringValue(XRabbitMQExchangeTypes.Direct)]
+ Direct,
+
+ [StringValue(XRabbitMQExchangeTypes.Fanout)]
+ Fanout,
+
+ [StringValue(XRabbitMQExchangeTypes.Topic)]
+ Topic,
+
+ [StringValue(XRabbitMQExchangeTypes.Headers)]
+ Headers,
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XZeroMQPattern.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ public enum XZeroMQPattern
+ {
+ [StringValue(XEventServiceConstants.None)]
+ None,
+
+ [StringValue(XEventServiceConstants.PubSub)]
+ PubSub,
+
+ [StringValue(XEventServiceConstants.PushPull)]
+ PushPull
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\DI\XDIHelperExtension.cs
+
+using System;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.DI
+{
+ public static partial class XDIHelperExtension
+ {
+ ///
+ /// Extract EventService Configurations ...
+ ///
+ ///
+ ///
+ public static XEventServiceConfiguration GetXEventServiceConfigurations(
+ this IConfiguration source
+ )
+ {
+ //
+ var configSection = source
+ .GetSection(ConfigurationNodeNames.EVENT_SERVICE_NODE_NAME);
+ var result = configSection.Get();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ IConfiguration configuration
+ )
+ {
+ //
+ var config = configuration.GetXEventServiceConfigurations();
+ services.AddXEventServiceConfigurations(config);
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ // Validate ...
+ if (!configuration.IsValid())
+ {
+ //
+ Log("Registration Failed, due Invalid Configuration ...");
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Register Configuration as Singleton ...
+ services.AddSingleton(configuration);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// a LogTag for Service ...
+ ///
+ private static string XLogTag = XEventServiceConstants.XEventServiceDILogTag;
+
+ ///
+ /// print a log in Console ...
+ ///
+ ///
+ private static void Log(string message)
+ {
+ Console.WriteLine($"{XLogTag} => {message}");
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Extensions\XEventServiceExtensions.cs
+
+using System.Linq;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Extensions
+{
+ ///
+ /// Extensions for Event Service ...
+ ///
+ public static class XEventServiceExtensions
+ {
+ ///
+ /// Validate Event Broker ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventBroker source)
+ {
+ //
+ var result =
+ !source.IsNull() &&
+ source != XEventBroker.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Event Service Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventServiceConfiguration source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ source.Broker.IsValid() &&
+ !source.Url.IsNullOrEmpty() &&
+ source.Url.IsValidUrl();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Exchange Type ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQExchangeType source)
+ {
+ //
+ var result = source != XRabbitMQExchangeType.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQOptions source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Exchange.IsNullOrEmpty() &&
+ source.ExchangeType.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Message ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventMessage source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Action.IsNullOrEmpty() &&
+ !source.Sender.IsNullOrEmpty();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Request ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventRequest source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Topic.IsNullOrEmpty() &&
+ source.Message.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate ZeroMQ Pattern ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XZeroMQPattern source)
+ {
+ //
+ var result = source != XZeroMQPattern.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate ZeroMQ Options ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XZeroMQOptions source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ source.Pattern.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Array is Wildcard passed or not ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedWildcard(this string[] source)
+ {
+ //
+ var result =
+ source is not null &&
+ source.Length == 1 &&
+ source.Any(s =>
+ s == XEventServiceConstants.XWildcard);
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate a Collection is Valid for Kafka Consumer ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedCollection(
+ this string[] source
+ )
+ {
+ //
+ var result = source.HasChild();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Sender ...
+ ///
+ ///
+ ///
+ ///
+ public static bool IsValidSender(
+ this XEventMessage source,
+ string[] allowedSenders
+ )
+ {
+ //
+ // Validate Source and Collection ...
+ var result =
+ source.IsValid() &&
+ allowedSenders
+ .IsAllowedCollection();
+ if (!result)
+ {
+ return result;
+ }
+
+ //
+ // Validate Sender ...
+ result =
+ allowedSenders.IsAllowedWildcard() ||
+ allowedSenders.Any(x =>
+ x.Equals(
+ source.Sender,
+ System.StringComparison.InvariantCultureIgnoreCase));
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Action ...
+ ///
+ ///
+ ///
+ ///
+ public static bool IsValidAction(
+ this XEventMessage source,
+ string[] allowedActions
+ )
+ {
+ //
+ // Validate Source and Collection ...
+ var result =
+ source.IsValid() &&
+ allowedActions
+ .IsAllowedCollection();
+ if (!result)
+ {
+ return result;
+ }
+
+ //
+ // Validate Action ...
+ result =
+ allowedActions.IsAllowedWildcard() ||
+ allowedActions.Any(x =>
+ x.Equals(
+ source.Action,
+ System.StringComparison.InvariantCultureIgnoreCase));
+
+ //
+ return result;
+ }
+
+ ///
+ /// Completely Validate a Message, based on providing:
+ /// - Allowed Actions
+ /// - Allowed Senders
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool IsValid(
+ this XEventMessage source,
+ string[] allowedSenders,
+ string[] allowedActions
+ )
+ {
+ //
+ var result =
+ source.IsValid() &&
+ source.IsValidAction(allowedActions) &&
+ source.IsValidSender(allowedSenders);
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Host address ...
+ ///
+ ///
+ ///
+ public static string GetRabbitMQHost(this XEventServiceConfiguration source)
+ {
+ //
+ var result = string.Empty;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ result = parts[0];
+ if (result.IsNullOrEmpty() ||
+ !result.IsValidUrl()
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Port Number ...
+ ///
+ ///
+ ///
+ public static int GetRabbitMQPort(this XEventServiceConfiguration source)
+ {
+ //
+ int result = -1;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Try Parse Port as Integer ...
+ int.TryParse(parts[1], out result);
+ if (result <= 0)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXKafkaProducerService.cs
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// a Service for Produce Kafka Messages ...
+ ///
+ public interface IXKafkaProducerService : IXProducerServiceBase
+ { }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXProducerServiceBase.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ public interface IXProducerServiceBase : IDisposable
+ {
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXRabbitMQProducerService.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public interface IXRabbitMQProducerService : IXProducerServiceBase
+ {
+ ///
+ /// Sending a Message to Default Exchange ...
+ ///
+ ///
+ /// an Instance of ...
+ ///
+ ///
+ /// Target Exchange Name. if empty, uses default from configuration ...
+ ///
+ ///
+ /// Routing Key for message routing ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXZeroMQProducerService.cs
+
+namespace xEventService.Interfaces
+{
+ public interface IXZeroMQProducerService : IXProducerServiceBase
+ { }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventMessage.cs
+
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Message Descriptor for Events ...
+ ///
+ public class XEventMessage
+ {
+ ///
+ /// Specified Action for Fire ...
+ ///
+ [JsonRequired]
+ public string Action { get; set; }
+
+ ///
+ /// Specified Action Sender ...
+ ///
+ [JsonRequired]
+ public string Sender { get; set; }
+
+ ///
+ /// Sending Time ...
+ ///
+ [JsonRequired]
+ public DateTime Offset { get; set; }
+
+ ///
+ /// Specified Message ...
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// Metadata for Message ...
+ ///
+ public IDictionary Payload { get; set; } = new Dictionary();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventRequest.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Request for Sending an Event ...
+ ///
+ public class XEventRequest
+ {
+ ///
+ /// Specified Which Kafka Topic ...
+ ///
+ public string Topic { get; set; } = XEventServiceConstants.XDefaultTopic;
+
+ ///
+ /// Specified Kafka Meesage to Send ...
+ ///
+ public XEventMessage Message { get; set; }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XRabbitMQOptions.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// RabbitMQ-specific connection and messaging options ...
+ ///
+ public class XRabbitMQOptions
+ {
+
+ ///
+ /// Connection Username ...
+ ///
+ public string Username { get; set; } = "guest";
+
+ ///
+ /// Connection Password ...
+ ///
+ public string Password { get; set; } = "guest";
+
+ ///
+ /// Virtual Host ...
+ ///
+ public string VirtualHost { get; set; } = "/";
+
+ ///
+ /// Default Exchange Name ...
+ ///
+ public string Exchange { get; set; } = "x.saherelm.events";
+
+ ///
+ /// Default Exchange Type (fanout, direct, topic, headers) ...
+ ///
+ public XRabbitMQExchangeType ExchangeType { get; set; } = XRabbitMQExchangeType.Fanout;
+
+ ///
+ /// Default Queue Name ...
+ /// if empty, a dynamic queue will be generated ...
+ ///
+ public string Queue { get; set; } = string.Empty;
+
+ ///
+ /// Default Routing Key ...
+ ///
+ public string RoutingKey { get; set; } = string.Empty;
+
+ ///
+ /// Exchange Durable Flag ...
+ ///
+ public bool ExchangeDurable { get; set; } = true;
+
+ ///
+ /// Exchange AutoDelete Flag ...
+ ///
+ public bool ExchangeAutoDelete { get; set; } = false;
+
+ ///
+ /// Queue Durable Flag ...
+ ///
+ public bool QueueDurable { get; set; } = true;
+
+ ///
+ /// Queue Exclusive Flag ...
+ ///
+ public bool QueueExclusive { get; set; } = false;
+
+ ///
+ /// Queue AutoDelete Flag ...
+ ///
+ public bool QueueAutoDelete { get; set; } = false;
+
+ ///
+ /// Enable Automatic Recovery on Connection Loss ...
+ ///
+ public bool AutomaticRecoveryEnabled { get; set; } = true;
+
+ ///
+ /// Network Recovery Interval in Seconds ...
+ ///
+ public int NetworkRecoveryIntervalSeconds { get; set; } = 5;
+
+ ///
+ /// Prefetch Count for Consumer ...
+ ///
+ public ushort PrefetchCount { get; set; } = 1;
+
+ ///
+ /// Enable Manual Acknowledgement ...
+ ///
+ public bool ManualAck { get; set; } = true;
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XZeroMQOptions.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ public class XZeroMQOptions
+ {
+ ///
+ /// Socket Pattern (PubSub, PushPull) ...
+ ///
+ public XZeroMQPattern Pattern { get; set; } = XZeroMQPattern.PubSub;
+
+ ///
+ /// Bind or Connect ...
+ /// True for Server (Bind), False for Client (Connect) ...
+ ///
+ public bool IsServer { get; set; } = false;
+
+ ///
+ /// High Water Mark for Sending ...
+ ///
+ public int SendHighWaterMark { get; set; } = 1000;
+
+ ///
+ /// High Water Mark for Receiving ...
+ ///
+ public int ReceiveHighWaterMark { get; set; } = 1000;
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XBaseEventConsumerBackgroundService.cs
+
+using System;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xCommons.Providers;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// Base Event Consumer Service ...
+ ///
+ public abstract class XBaseEventConsumerBackgroundService : XResumableBackgroundService
+ {
+ ///
+ /// Consumed Topic Messages ...
+ ///
+ protected readonly string topic;
+
+ ///
+ /// Allowed Senders ...
+ ///
+ public readonly string[] allowedSenders;
+
+ ///
+ /// Allowed Actions ...
+ ///
+ public readonly string[] allowedActions;
+
+ ///
+ /// Configuration ...
+ ///
+ protected readonly XEventServiceConfiguration configuration;
+
+ protected XBaseEventConsumerBackgroundService(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ logger: logger,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validate Actions ...
+ if (!allowedActions
+ .IsAllowedCollection() ||
+ !allowedSenders
+ .IsAllowedCollection() ||
+ !configuration.IsValid()
+ )
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ this.topic = topic;
+ this.configuration = configuration;
+ this.allowedActions = allowedActions;
+ this.allowedSenders = allowedSenders;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XBaseEventProducerService.cs
+
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ public abstract class XBaseEventProducerService
+ {
+ protected readonly XEventServiceConfiguration configuration;
+ protected readonly ILogger logger;
+
+ protected XBaseEventProducerService(
+ XEventServiceConfiguration configuration,
+ ILogger logger
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaConsumerServiceBase.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XKafkaConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ ///
+ /// Kafka Consumer Class ...
+ ///
+ protected readonly IConsumer consumer;
+
+ protected XKafkaConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (configuration.Broker != XEventBroker.XKafka)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Consumer Configuration ...
+ var config = new ConsumerConfig
+ {
+ EnableAutoCommit = false,
+ BootstrapServers = configuration.Url,
+ AutoOffsetReset = AutoOffsetReset.Earliest,
+ GroupId = XEventServiceConstants.XDefaultConsumerGroup,
+ };
+ consumer = new ConsumerBuilder(config).Build();
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Subscribe(topic);
+
+ //
+ // Consuming ...
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ //
+ // Consuming Topic ...
+ var consumeResult = consumer.Consume(cancellationToken);
+ if (!consumeResult.IsNullOrDefault() &&
+ !consumeResult.Message.IsNullOrDefault()
+ )
+ {
+ //
+ await DoWorkAsync(
+ payload: consumeResult.Message,
+ cancellationToken: cancellationToken
+ );
+ }
+ }
+ catch (ConsumeException)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ catch (Exception)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public override void Dispose()
+ {
+ //
+ consumer.Close();
+ consumer.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaProducerService.cs
+
+using xCommons.Extensions;
+using xExceptions.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Configuration;
+using xEventService.Models;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+using System.Threading;
+using xEventService.Constants;
+using System;
+
+namespace xEventService.Providers
+{
+ public class XKafkaProducerService : XBaseEventProducerService, IXKafkaProducerService
+ {
+ private readonly IProducer producer;
+
+ public XKafkaProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ ) : base(configuration, logger)
+ {
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ configuration.Broker != XEventBroker.XKafka
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Producer Configuration ...
+ var config = new ProducerConfig
+ {
+ Acks = Acks.All,
+ BootstrapServers = configuration.Url,
+ MessageSendMaxRetries = configuration.MaxRetries,
+ };
+ producer = new ProducerBuilder(config).Build();
+ }
+
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ topic: request.Topic,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid() &&
+ !topic.IsNullOrEmpty();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ try
+ {
+ //
+ var response = await producer.ProduceAsync(
+ topic: topic,
+ message: new Message
+ {
+ Value = message
+ .ToJSON(camelCase: true)
+ },
+ cancellationToken: cancellationToken
+ );
+
+ //
+ result = !response.IsNullOrDefault();
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Required Objects ...
+ ///
+ public void Dispose()
+ {
+ //
+ producer.Dispose();
+ GC.SuppressFinalize(this);
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQConsumerServiceBase.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XRabbitMQConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ //
+ protected IModel channel;
+ protected IConnection connection;
+ protected AsyncEventingBasicConsumer consumer;
+
+ protected XRabbitMQConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (!configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ SetupConnection();
+ DeclareTopology();
+ consumer = new AsyncEventingBasicConsumer(channel);
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Received += async (model, ea) =>
+ {
+ //
+ try
+ {
+ //
+ var body = ea.Body.ToArray();
+ var json = Encoding.UTF8.GetString(body);
+ var message = json.FromJSON();
+ if (!message.IsNullOrDefault())
+ {
+ //
+ await DoWorkAsync(
+ payload: message,
+ cancellationToken: cancellationToken
+ );
+ }
+
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicAck(
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ catch
+ {
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicNack(
+ requeue: true,
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ };
+
+ //
+ consumer.Shutdown += (model, ea) =>
+ {
+ return Task.CompletedTask;
+ };
+
+ //
+ channel.BasicQos(
+ prefetchSize: 0,
+ global: false,
+ prefetchCount: configuration.RabbitMQ.PrefetchCount
+ );
+
+ //
+ channel.BasicConsume(
+ queue: GetQueueName(),
+ consumer: consumer,
+ autoAck: !configuration.RabbitMQ.ManualAck
+ );
+
+ //
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+
+ ///
+ /// Overriding Stop Action ...
+ ///
+ ///
+ ///
+ public override async Task StopAsync(CancellationToken cancellationToken)
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ connection.Close();
+ connection.Dispose();
+
+ //
+ await base.StopAsync(cancellationToken);
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ channel.Dispose();
+ connection.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+
+ //
+ #region Private ...
+ private string GetQueueName()
+ {
+ //
+ var result = string.Empty;
+ if (!configuration.RabbitMQ.Queue.IsNullOrEmpty())
+ {
+ result = configuration.RabbitMQ.Queue;
+ }
+
+ //
+ if (result.IsNullOrEmpty())
+ {
+ //
+ var digits = Guid.NewGuid().ToString().GetDigits();
+ result = $"{configuration.RabbitMQ.Exchange}[{digits}]";
+ }
+
+ //
+ return result;
+ }
+
+ private void SetupConnection()
+ {
+ var factory = new ConnectionFactory
+ {
+ DispatchConsumersAsync = true,
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ };
+
+ //
+ connection = factory.CreateConnection();
+ channel = connection.CreateModel();
+ }
+
+ private void DeclareTopology()
+ {
+ //
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+
+ //
+ var queueName = GetQueueName();
+
+ //
+ channel.QueueDeclare(
+ arguments: null,
+ queue: queueName,
+ durable: configuration.RabbitMQ.QueueDurable,
+ exclusive: configuration.RabbitMQ.QueueExclusive,
+ autoDelete: configuration.RabbitMQ.QueueAutoDelete
+ );
+
+ //
+ channel.QueueBind(
+ arguments: null,
+ queue: queueName,
+ exchange: configuration.RabbitMQ.Exchange,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQProducerService.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public class XRabbitMQProducerService : XBaseEventProducerService, IXRabbitMQProducerService
+ {
+ private IModel channel;
+ private IConnection connection;
+ private readonly object connectionLock = new();
+
+ public XRabbitMQProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ ) : base(configuration, logger)
+ {
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ !configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Initialize Connection ...
+ CreateConnection();
+ }
+
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ ///
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ exchange: request.Topic,
+ cancellationToken: cancellationToken,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message to Topic ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ var result = await SendMessageAsync(
+ request: new XEventRequest
+ {
+ Topic = topic,
+ Message = message
+ },
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message to Exchange ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ // Normalize Exchange ...
+ if (exchange.IsNullOrEmpty())
+ {
+ exchange = configuration.RabbitMQ.Exchange;
+ }
+
+ //
+ // Normalize Routing Key ...
+ if (routingKey.IsNull())
+ {
+ routingKey = configuration.RabbitMQ.RoutingKey;
+ }
+
+ //
+ try
+ {
+ //
+ // Ensure Connection is Alive ...
+ await EnsureChannelAsync(cancellationToken);
+
+ //
+ // Serialize Message ...
+ var body = Encoding.UTF8.GetBytes(
+ message.ToJSON(camelCase: true)
+ );
+
+ //
+ // Prepare Message Properties ...
+ var properties = channel.CreateBasicProperties();
+ properties.Persistent = true;
+ properties.ContentType = "application/json";
+ properties.Timestamp = new AmqpTimestamp(
+ DateTimeOffset.UtcNow.ToUnixTimeSeconds()
+ );
+ properties.MessageId = Guid.NewGuid().ToString();
+ properties.Type = message.Action;
+ properties.AppId = message.Sender;
+
+ //
+ // Publish Message ...
+ await Task.Run(() =>
+ {
+ //
+ channel.BasicPublish(
+ exchange: exchange,
+ routingKey: routingKey,
+ mandatory: false,
+ basicProperties: properties,
+ body: body
+ );
+ },
+ cancellationToken: cancellationToken);
+
+ //
+ result = true;
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public void Dispose()
+ {
+ //
+ try
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ channel = null;
+
+ //
+ connection.Close();
+ connection.Dispose();
+ connection = null;
+ }
+ catch
+ { }
+
+ //
+ GC.SuppressFinalize(this);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// Create Connection to RabbitMQ Server ...
+ ///
+ private void CreateConnection()
+ {
+ //
+ var factory = new ConnectionFactory
+ {
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ DispatchConsumersAsync = true
+ };
+
+ //
+ connection = factory.CreateConnection();
+ }
+
+ ///
+ /// Check Connection is Alive ...
+ ///
+ private bool IsConnected()
+ {
+ //
+ var result =
+ !connection.IsNullOrDefault() &&
+ connection.IsOpen &&
+ !channel.IsNull() &&
+ channel.IsOpen;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Ensure Connection Established ...
+ ///
+ private async Task EnsureConnectionAsync()
+ {
+ //
+ try
+ {
+ await EnsureChannelAsync();
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Ensure Channel is Open and Exchange/Queue Declared ...
+ ///
+ private async Task EnsureChannelAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ lock (connectionLock)
+ {
+ //
+ // Double Check after Lock ...
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ // Recreate Connection if Dead ...
+ if (connection is null || !connection.IsOpen)
+ {
+ CreateConnection();
+ }
+
+ //
+ // Create Channel ...
+ channel = connection.CreateModel();
+
+ //
+ // Declare Exchange ...
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+ }
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XZeroMQConsumerServiceBase.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using NetMQ;
+using NetMQ.Sockets;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract ZeroMQ Consumer Service ...
+ ///
+ public abstract class XZeroMQConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ //
+ private NetMQSocket socket;
+
+ protected XZeroMQConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (configuration.Broker != XEventBroker.XKafka)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Initialize ...
+ InitializeSocket();
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ await Task.Yield();
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ //
+ try
+ {
+ //
+ string receivedTopic = string.Empty;
+ byte[] body = null;
+
+ if (configuration.ZeroMQ.Pattern == XZeroMQPattern.PubSub)
+ {
+ //
+ if (socket.TryReceiveFrameString(TimeSpan.FromMilliseconds(100), out receivedTopic))
+ {
+ //
+ if (socket.TryReceiveFrameBytes(TimeSpan.FromMilliseconds(100), out body))
+ {
+ // Process
+ }
+ }
+ }
+ else
+ {
+ if (socket.TryReceiveFrameBytes(TimeSpan.FromMilliseconds(100), out body))
+ {
+ // Process
+ }
+ }
+
+ if (body != null && body.Length > 0)
+ {
+ //
+ var json = Encoding.UTF8.GetString(body);
+ var message = json.FromJSON();
+ if (!message.IsNullOrDefault())
+ {
+ //
+ await DoWorkAsync(
+ payload: message,
+ cancellationToken: cancellationToken
+ );
+ }
+ }
+ else
+ {
+ await Task.Delay(10, cancellationToken);
+ }
+ }
+ catch (Exception)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+ }
+
+ public override async Task StopAsync(CancellationToken cancellationToken)
+ {
+ //
+ socket.Close();
+ await base.StopAsync(cancellationToken);
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public override void Dispose()
+ {
+ //
+ socket.Dispose();
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+
+ //
+ #region Private ...
+ private void InitializeSocket()
+ {
+ //
+ if (configuration.ZeroMQ.Pattern == XZeroMQPattern.PubSub)
+ {
+ //
+ socket = new SubscriberSocket();
+ socket.Options.ReceiveHighWatermark = configuration.ZeroMQ.ReceiveHighWaterMark;
+
+ //
+ // Subscribe to specific topic or all
+ if (topic.IsNullOrEmpty() || topic == XEventServiceConstants.XWildcard)
+ {
+ ((SubscriberSocket)socket).SubscribeToAnyTopic();
+ }
+ else
+ {
+ ((SubscriberSocket)socket).Subscribe(topic);
+ }
+ }
+ else
+ {
+ //
+ socket = new PullSocket();
+ socket.Options.ReceiveHighWatermark = configuration.ZeroMQ.ReceiveHighWaterMark;
+ }
+
+ //
+ if (configuration.ZeroMQ.IsServer)
+ {
+ socket.Bind(configuration.Url);
+ }
+ else
+ {
+ socket.Connect(configuration.Url);
+ }
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XZeroMQProducerService.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using NetMQ;
+using NetMQ.Sockets;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ public class XZeroMQProducerService : XBaseEventProducerService, IXZeroMQProducerService
+ {
+ //
+ private NetMQSocket socket;
+ private readonly object socketLock = new();
+
+ public XZeroMQProducerService(
+ XEventServiceConfiguration configuration,
+ ILogger logger
+ ) : base(configuration, logger)
+ {
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ !configuration.ZeroMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XZeroMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ InitializeSocket();
+ }
+
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ ///
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ topic: request.Topic,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message to Topic ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ var result = message.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ // Normalize Topic ...
+ if (topic.IsNullOrEmpty())
+ {
+ topic = XEventServiceConstants.XDefaultTopic;
+ }
+
+ //
+ try
+ {
+ //
+ var json = message.ToJSON(camelCase: true);
+ var body = Encoding.UTF8.GetBytes(json);
+ await Task.Run(() =>
+ {
+ //
+ lock (socketLock)
+ {
+ //
+ if (configuration.ZeroMQ.Pattern == XZeroMQPattern.PubSub)
+ {
+ socket.SendMoreFrame(topic).SendFrame(body);
+ }
+ else
+ {
+ socket.SendFrame(body);
+ }
+ }
+ }, cancellationToken);
+
+ //
+ result = true;
+ }
+ catch (Exception)
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public void Dispose()
+ {
+ //
+ try
+ {
+ //
+ lock (socketLock)
+ {
+ //
+ if (socket != null)
+ {
+ //
+ socket.Close();
+ socket.Dispose();
+ socket = null;
+ }
+ }
+ }
+ catch { }
+
+ //
+ GC.SuppressFinalize(this);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// Initialize Socket ...
+ ///
+ private void InitializeSocket()
+ {
+ //
+ lock (socketLock)
+ {
+ //
+ if (configuration.ZeroMQ.Pattern == XZeroMQPattern.PubSub)
+ {
+ //
+ socket = new PublisherSocket();
+ socket.Options.SendHighWatermark = configuration.ZeroMQ.SendHighWaterMark;
+ }
+ else
+ {
+ //
+ socket = new PushSocket();
+ socket.Options.SendHighWatermark = configuration.ZeroMQ.SendHighWaterMark;
+ }
+
+ //
+ if (configuration.ZeroMQ.IsServer)
+ {
+ socket.Bind(configuration.Url);
+ }
+ else
+ {
+ socket.Connect(configuration.Url);
+ }
+ }
+ }
+ #endregion
+ }
+}
diff --git a/Documents/GeneratedContext/xDashboard-xEventService-20260819_145103.txt b/Documents/GeneratedContext/xDashboard-xEventService-20260819_145103.txt
new file mode 100644
index 0000000..1f68c4f
--- /dev/null
+++ b/Documents/GeneratedContext/xDashboard-xEventService-20260819_145103.txt
@@ -0,0 +1,2243 @@
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Configuration\XEventServiceConfiguration.cs
+
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Configuration
+{
+ ///
+ /// a configuration class for describing how to connect rabbit mq server
+ /// and how channels must be declared ...
+ ///
+ public class XEventServiceConfiguration
+ {
+ ///
+ /// Message Broker Address ...
+ ///
+ public string Url { get; set; }
+
+ ///
+ /// Max Retries to Sending Message ...
+ ///
+ public int MaxRetries { get; set; } = 3;
+
+ ///
+ /// Allowed Message Broker ...
+ ///
+ public XEventBroker Broker { get; set; } = XEventBroker.None;
+
+ ///
+ /// ZeroMQ Specific Options ...
+ /// Only used when Broker is XZeroMQ ...
+ ///
+ public XZeroMQOptions ZeroMQ { get; set; } = new XZeroMQOptions();
+
+ ///
+ /// RabbitMQ Specific Options ...
+ /// Only used when Broker is XRabbitMQ ...
+ ///
+ public XRabbitMQOptions RabbitMQ { get; set; } = new XRabbitMQOptions();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\ConfigurationNodeNames.cs
+
+namespace xEventService.Constants
+{
+ public partial struct ConfigurationNodeNames {
+ public const string EVENT_SERVICE_NODE_NAME = "EventServiceConfiguration";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventBroker.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available Brokers ...
+ ///
+ public enum XEventBroker
+ {
+ [StringValue(XEventServiceConstants.None)]
+ None,
+
+ [StringValue(XEventServiceConstants.XKafka)]
+ XKafka,
+
+ [StringValue(XEventServiceConstants.XZeroMQ)]
+ XZeroMQ,
+
+ [StringValue(XEventServiceConstants.XRabbitMQ)]
+ XRabbitMQ,
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XEventServiceConstants.cs
+
+namespace xEventService.Constants
+{
+ public struct XEventServiceConstants
+ {
+ //
+ // Service Extensions Log Tag ...
+ public const string XEventServiceDILogTag = "XEventService";
+
+ //
+ // Defaults ...
+ public const string XDefaultTopic = "XSaherelm";
+ public const string XDefaultConsumerGroup = "XSaherElmGroup";
+
+ //
+ public const string None = "None";
+ public const string XKafka = "XKafka";
+ public const string PubSub = "PubSub";
+ public const string XZeroMQ = "XZeroMQ";
+ public const string PushPull = "PushPull";
+ public const string XRabbitMQ = "XRabbitMQ";
+
+ //
+ public const string XWildcard = "*";
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XRabbitMQExchangeType.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ ///
+ /// Available RabbitMQ Exchange Types ...
+ ///
+ public struct XRabbitMQExchangeTypes
+ {
+ public const string None = "none";
+ public const string Direct = "direct";
+ public const string Fanout = "fanout";
+ public const string Topic = "topic";
+ public const string Headers = "headers";
+ }
+
+ ///
+ /// RabbitMQ Exchange Type Enum ...
+ ///
+ public enum XRabbitMQExchangeType
+ {
+ [StringValue(XRabbitMQExchangeTypes.None)]
+ None,
+
+ [StringValue(XRabbitMQExchangeTypes.Direct)]
+ Direct,
+
+ [StringValue(XRabbitMQExchangeTypes.Fanout)]
+ Fanout,
+
+ [StringValue(XRabbitMQExchangeTypes.Topic)]
+ Topic,
+
+ [StringValue(XRabbitMQExchangeTypes.Headers)]
+ Headers,
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Constants\XZeroMQPattern.cs
+
+using xExceptions.Attributes;
+
+namespace xEventService.Constants
+{
+ public enum XZeroMQPattern
+ {
+ [StringValue(XEventServiceConstants.None)]
+ None,
+
+ [StringValue(XEventServiceConstants.PubSub)]
+ PubSub,
+
+ [StringValue(XEventServiceConstants.PushPull)]
+ PushPull
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\DI\XDIHelperExtension.cs
+
+using System;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.DI
+{
+ public static partial class XDIHelperExtension
+ {
+ ///
+ /// Extract EventService Configurations ...
+ ///
+ ///
+ ///
+ public static XEventServiceConfiguration GetXEventServiceConfigurations(
+ this IConfiguration source
+ )
+ {
+ //
+ var configSection = source
+ .GetSection(ConfigurationNodeNames.EVENT_SERVICE_NODE_NAME);
+ var result = configSection.Get();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ IConfiguration configuration
+ )
+ {
+ //
+ var config = configuration.GetXEventServiceConfigurations();
+ services.AddXEventServiceConfigurations(config);
+ }
+
+ ///
+ /// Register Event Service Configurations ...
+ ///
+ ///
+ ///
+ public static void AddXEventServiceConfigurations(
+ this IServiceCollection services,
+ XEventServiceConfiguration configuration
+ )
+ {
+ //
+ // Validate ...
+ if (!configuration.IsValid())
+ {
+ //
+ Log("Registration Failed, due Invalid Configuration ...");
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Register Configuration as Singleton ...
+ services.AddSingleton(configuration);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// a LogTag for Service ...
+ ///
+ private static string XLogTag = XEventServiceConstants.XEventServiceDILogTag;
+
+ ///
+ /// print a log in Console ...
+ ///
+ ///
+ private static void Log(string message)
+ {
+ Console.WriteLine($"{XLogTag} => {message}");
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Extensions\XEventServiceExtensions.cs
+
+using System.Linq;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Extensions
+{
+ ///
+ /// Extensions for Event Service ...
+ ///
+ public static class XEventServiceExtensions
+ {
+ ///
+ /// Validate Event Broker ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventBroker source)
+ {
+ //
+ var result =
+ !source.IsNull() &&
+ source != XEventBroker.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Event Service Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventServiceConfiguration source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ source.Broker.IsValid() &&
+ !source.Url.IsNullOrEmpty() &&
+ source.Url.IsValidUrl();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Exchange Type ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQExchangeType source)
+ {
+ //
+ var result = source != XRabbitMQExchangeType.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate RabbitMQ Configuration ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XRabbitMQOptions source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Exchange.IsNullOrEmpty() &&
+ source.ExchangeType.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Message ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventMessage source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Action.IsNullOrEmpty() &&
+ !source.Sender.IsNullOrEmpty();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Event Request ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XEventRequest source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Topic.IsNullOrEmpty() &&
+ source.Message.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate ZeroMQ Pattern ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XZeroMQPattern source)
+ {
+ //
+ var result = source != XZeroMQPattern.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate ZeroMQ Options ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XZeroMQOptions source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ source.Pattern.IsValid();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate an Array is Wildcard passed or not ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedWildcard(this string[] source)
+ {
+ //
+ var result =
+ source is not null &&
+ source.Length == 1 &&
+ source.Any(s =>
+ s == XEventServiceConstants.XWildcard);
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate a Collection is Valid for Kafka Consumer ...
+ ///
+ ///
+ ///
+ public static bool IsAllowedCollection(
+ this string[] source
+ )
+ {
+ //
+ var result = source.HasChild();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Sender ...
+ ///
+ ///
+ ///
+ ///
+ public static bool IsValidSender(
+ this XEventMessage source,
+ string[] allowedSenders
+ )
+ {
+ //
+ // Validate Source and Collection ...
+ var result =
+ source.IsValid() &&
+ allowedSenders
+ .IsAllowedCollection();
+ if (!result)
+ {
+ return result;
+ }
+
+ //
+ // Validate Sender ...
+ result =
+ allowedSenders.IsAllowedWildcard() ||
+ allowedSenders.Any(x =>
+ x.Equals(
+ source.Sender,
+ System.StringComparison.InvariantCultureIgnoreCase));
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Action ...
+ ///
+ ///
+ ///
+ ///
+ public static bool IsValidAction(
+ this XEventMessage source,
+ string[] allowedActions
+ )
+ {
+ //
+ // Validate Source and Collection ...
+ var result =
+ source.IsValid() &&
+ allowedActions
+ .IsAllowedCollection();
+ if (!result)
+ {
+ return result;
+ }
+
+ //
+ // Validate Action ...
+ result =
+ allowedActions.IsAllowedWildcard() ||
+ allowedActions.Any(x =>
+ x.Equals(
+ source.Action,
+ System.StringComparison.InvariantCultureIgnoreCase));
+
+ //
+ return result;
+ }
+
+ ///
+ /// Completely Validate a Message, based on providing:
+ /// - Allowed Actions
+ /// - Allowed Senders
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool IsValid(
+ this XEventMessage source,
+ string[] allowedSenders,
+ string[] allowedActions
+ )
+ {
+ //
+ var result =
+ source.IsValid() &&
+ source.IsValidAction(allowedActions) &&
+ source.IsValidSender(allowedSenders);
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Host address ...
+ ///
+ ///
+ ///
+ public static string GetRabbitMQHost(this XEventServiceConfiguration source)
+ {
+ //
+ var result = string.Empty;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ result = parts[0];
+ if (result.IsNullOrEmpty() ||
+ !result.IsValidUrl()
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Parse Url and Extract Port Number ...
+ ///
+ ///
+ ///
+ public static int GetRabbitMQPort(this XEventServiceConfiguration source)
+ {
+ //
+ int result = -1;
+
+ //
+ if (!source.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ var parts = source.Url.Split(':');
+ if (parts.Length != 2)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Try Parse Port as Integer ...
+ int.TryParse(parts[1], out result);
+ if (result <= 0)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ return result;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXKafkaProducerService.cs
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// a Service for Produce Kafka Messages ...
+ ///
+ public interface IXKafkaProducerService : IXProducerServiceBase
+ { }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXProducerServiceBase.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Constants;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ public interface IXProducerServiceBase : IDisposable
+ {
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXRabbitMQProducerService.cs
+
+using System.Threading;
+using System.Threading.Tasks;
+using xEventService.Models;
+
+namespace xEventService.Interfaces
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public interface IXRabbitMQProducerService : IXProducerServiceBase
+ {
+ ///
+ /// Sending a Message to Default Exchange ...
+ ///
+ ///
+ /// an Instance of ...
+ ///
+ ///
+ /// Target Exchange Name. if empty, uses default from configuration ...
+ ///
+ ///
+ /// Routing Key for message routing ...
+ ///
+ ///
+ ///
+ Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Interfaces\IXZeroMQProducerService.cs
+
+namespace xEventService.Interfaces
+{
+ public interface IXZeroMQProducerService : IXProducerServiceBase
+ { }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventMessage.cs
+
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Message Descriptor for Events ...
+ ///
+ public class XEventMessage
+ {
+ ///
+ /// Specified Action for Fire ...
+ ///
+ [JsonRequired]
+ public string Action { get; set; }
+
+ ///
+ /// Specified Action Sender ...
+ ///
+ [JsonRequired]
+ public string Sender { get; set; }
+
+ ///
+ /// Sending Time ...
+ ///
+ [JsonRequired]
+ public DateTime Offset { get; set; }
+
+ ///
+ /// Specified Message ...
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// Metadata for Message ...
+ ///
+ public IDictionary Payload { get; set; } = new Dictionary();
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XEventRequest.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// a Request for Sending an Event ...
+ ///
+ public class XEventRequest
+ {
+ ///
+ /// Specified Which Kafka Topic ...
+ ///
+ public string Topic { get; set; } = XEventServiceConstants.XDefaultTopic;
+
+ ///
+ /// Specified Kafka Meesage to Send ...
+ ///
+ public XEventMessage Message { get; set; }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XRabbitMQOptions.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ ///
+ /// RabbitMQ-specific connection and messaging options ...
+ ///
+ public class XRabbitMQOptions
+ {
+
+ ///
+ /// Connection Username ...
+ ///
+ public string Username { get; set; } = "guest";
+
+ ///
+ /// Connection Password ...
+ ///
+ public string Password { get; set; } = "guest";
+
+ ///
+ /// Virtual Host ...
+ ///
+ public string VirtualHost { get; set; } = "/";
+
+ ///
+ /// Default Exchange Name ...
+ ///
+ public string Exchange { get; set; } = "x.saherelm.events";
+
+ ///
+ /// Default Exchange Type (fanout, direct, topic, headers) ...
+ ///
+ public XRabbitMQExchangeType ExchangeType { get; set; } = XRabbitMQExchangeType.Fanout;
+
+ ///
+ /// Default Queue Name ...
+ /// if empty, a dynamic queue will be generated ...
+ ///
+ public string Queue { get; set; } = string.Empty;
+
+ ///
+ /// Default Routing Key ...
+ ///
+ public string RoutingKey { get; set; } = string.Empty;
+
+ ///
+ /// Exchange Durable Flag ...
+ ///
+ public bool ExchangeDurable { get; set; } = true;
+
+ ///
+ /// Exchange AutoDelete Flag ...
+ ///
+ public bool ExchangeAutoDelete { get; set; } = false;
+
+ ///
+ /// Queue Durable Flag ...
+ ///
+ public bool QueueDurable { get; set; } = true;
+
+ ///
+ /// Queue Exclusive Flag ...
+ ///
+ public bool QueueExclusive { get; set; } = false;
+
+ ///
+ /// Queue AutoDelete Flag ...
+ ///
+ public bool QueueAutoDelete { get; set; } = false;
+
+ ///
+ /// Enable Automatic Recovery on Connection Loss ...
+ ///
+ public bool AutomaticRecoveryEnabled { get; set; } = true;
+
+ ///
+ /// Network Recovery Interval in Seconds ...
+ ///
+ public int NetworkRecoveryIntervalSeconds { get; set; } = 5;
+
+ ///
+ /// Prefetch Count for Consumer ...
+ ///
+ public ushort PrefetchCount { get; set; } = 1;
+
+ ///
+ /// Enable Manual Acknowledgement ...
+ ///
+ public bool ManualAck { get; set; } = true;
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Models\XZeroMQOptions.cs
+
+using xEventService.Constants;
+
+namespace xEventService.Models
+{
+ public class XZeroMQOptions
+ {
+ ///
+ /// Socket Pattern (PubSub, PushPull) ...
+ ///
+ public XZeroMQPattern Pattern { get; set; } = XZeroMQPattern.PubSub;
+
+ ///
+ /// Bind or Connect ...
+ /// True for Server (Bind), False for Client (Connect) ...
+ ///
+ public bool IsServer { get; set; } = false;
+
+ ///
+ /// High Water Mark for Sending ...
+ ///
+ public int SendHighWaterMark { get; set; } = 1000;
+
+ ///
+ /// High Water Mark for Receiving ...
+ ///
+ public int ReceiveHighWaterMark { get; set; } = 1000;
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XBaseEventConsumerBackgroundService.cs
+
+using System;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xCommons.Providers;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// Base Event Consumer Service ...
+ ///
+ public abstract class XBaseEventConsumerBackgroundService : XResumableBackgroundService
+ {
+ ///
+ /// Consumed Topic Messages ...
+ ///
+ protected readonly string topic;
+
+ ///
+ /// Allowed Senders ...
+ ///
+ protected readonly string[] allowedSenders;
+
+ ///
+ /// Allowed Actions ...
+ ///
+ protected readonly string[] allowedActions;
+
+ ///
+ /// Configuration ...
+ ///
+ protected readonly XEventServiceConfiguration configuration;
+
+ protected XBaseEventConsumerBackgroundService(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(serviceProvider, logger)
+ {
+ //
+ // Validate Actions ...
+ if (!allowedActions
+ .IsAllowedCollection() ||
+ !allowedSenders
+ .IsAllowedCollection() ||
+ !configuration.IsValid()
+ )
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ this.topic = topic;
+ this.configuration = configuration;
+ this.allowedActions = allowedActions;
+ this.allowedSenders = allowedSenders;
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XBaseEventProducerService.cs
+
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Extensions;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ public abstract class XBaseEventProducerService
+ {
+ protected readonly XEventServiceConfiguration configuration;
+ protected readonly ILogger logger;
+
+ protected XBaseEventProducerService(
+ XEventServiceConfiguration configuration,
+ ILogger logger
+ )
+ {
+ //
+ this.logger = logger;
+ this.configuration = configuration;
+
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaConsumerServiceBase.cs
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XKafkaConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ ///
+ /// Kafka Consumer Class ...
+ ///
+ protected readonly IConsumer consumer;
+
+ protected XKafkaConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (configuration.Broker != XEventBroker.XKafka)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Consumer Configuration ...
+ var config = new ConsumerConfig
+ {
+ EnableAutoCommit = false,
+ BootstrapServers = configuration.Url,
+ AutoOffsetReset = AutoOffsetReset.Earliest,
+ GroupId = XEventServiceConstants.XDefaultConsumerGroup,
+ };
+ consumer = new ConsumerBuilder(config).Build();
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Subscribe(topic);
+
+ //
+ // Consuming ...
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ //
+ // Consuming Topic ...
+ var consumeResult = consumer.Consume(cancellationToken);
+ if (!consumeResult.IsNullOrDefault() &&
+ !consumeResult.Message.IsNullOrDefault()
+ )
+ {
+ //
+ var message = consumeResult
+ .Message
+ .Value
+ .FromJSON();
+ if (message.IsValid(
+ allowedSenders: allowedSenders,
+ allowedActions: allowedActions)
+ )
+ {
+ //
+ await DoWorkAsync(
+ payload: consumeResult.Message,
+ cancellationToken: cancellationToken
+ );
+ }
+ }
+ }
+ catch (ConsumeException)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ catch (Exception)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public override void Dispose()
+ {
+ //
+ if (consumer != null)
+ {
+ consumer.Close();
+ consumer.Dispose();
+ }
+
+ //
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XKafkaProducerService.cs
+
+using xCommons.Extensions;
+using xExceptions.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Configuration;
+using xEventService.Models;
+using Confluent.Kafka;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+using System.Threading;
+using xEventService.Constants;
+using System;
+
+namespace xEventService.Providers
+{
+ public class XKafkaProducerService : XBaseEventProducerService, IXKafkaProducerService
+ {
+ private readonly IProducer producer;
+
+ public XKafkaProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ ) : base(configuration, logger)
+ {
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ configuration.Broker != XEventBroker.XKafka
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Prepare Kafka Producer Configuration ...
+ var config = new ProducerConfig
+ {
+ Acks = Acks.All,
+ BootstrapServers = configuration.Url,
+ MessageSendMaxRetries = configuration.MaxRetries,
+ };
+ producer = new ProducerBuilder(config).Build();
+ }
+
+ ///
+ /// Sending Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ topic: request.Topic,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message ...
+ ///
+ /// an Instance of to Provides Kafka Messaging requirement ...
+ /// Topic for Sending Message ...
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid() &&
+ !topic.IsNullOrEmpty();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ try
+ {
+ //
+ var response = await producer.ProduceAsync(
+ topic: topic,
+ message: new Message
+ {
+ Value = message
+ .ToJSON(camelCase: true)
+ },
+ cancellationToken: cancellationToken
+ );
+
+ //
+ result = !response.IsNullOrDefault();
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Required Objects ...
+ ///
+ public void Dispose()
+ {
+ //
+ if (producer != null)
+ {
+ producer.Dispose();
+ }
+
+ //
+ GC.SuppressFinalize(this);
+ }
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQConsumerServiceBase.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract Kafka Consumer Service ...
+ ///
+ public abstract class XRabbitMQConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ //
+ protected IModel channel;
+ protected IConnection connection;
+ protected AsyncEventingBasicConsumer consumer;
+
+ protected XRabbitMQConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (!configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ SetupConnection();
+ DeclareTopology();
+ consumer = new AsyncEventingBasicConsumer(channel);
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // For Fix Blocking Synchronous ...
+ await Task.Yield();
+ using var cts = new CancellationTokenSource();
+
+ //
+ // Subscribe to Topic ...
+ consumer.Received += async (model, ea) =>
+ {
+ //
+ try
+ {
+ //
+ var body = ea.Body.ToArray();
+ var json = Encoding.UTF8.GetString(body);
+ var message = json.FromJSON();
+ if (!message.IsNullOrDefault() &&
+ message.IsValid(
+ allowedSenders: allowedSenders,
+ allowedActions: allowedActions)
+ )
+ {
+ //
+ await DoWorkAsync(
+ payload: message,
+ cancellationToken: cancellationToken
+ );
+ }
+
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicAck(
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ catch
+ {
+ //
+ if (configuration.RabbitMQ.ManualAck)
+ {
+ //
+ channel.BasicNack(
+ requeue: true,
+ multiple: false,
+ deliveryTag: ea.DeliveryTag
+ );
+ }
+ }
+ };
+
+ //
+ consumer.Shutdown += (model, ea) =>
+ {
+ return Task.CompletedTask;
+ };
+
+ //
+ channel.BasicQos(
+ prefetchSize: 0,
+ global: false,
+ prefetchCount: configuration.RabbitMQ.PrefetchCount
+ );
+
+ //
+ channel.BasicConsume(
+ queue: GetQueueName(),
+ consumer: consumer,
+ autoAck: !configuration.RabbitMQ.ManualAck
+ );
+
+ //
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+
+ ///
+ /// Overriding Stop Action ...
+ ///
+ ///
+ ///
+ public override async Task StopAsync(CancellationToken cancellationToken)
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ connection.Close();
+ connection.Dispose();
+
+ //
+ await base.StopAsync(cancellationToken);
+ }
+
+ ///
+ /// Dispose Implementation ...
+ ///
+ public override void Dispose()
+ {
+ //
+ if (channel != null)
+ {
+ channel.Dispose();
+ }
+
+ //
+ if (connection != null)
+ {
+ connection.Dispose();
+ }
+
+ //
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+
+ //
+ #region Private ...
+ private string GetQueueName()
+ {
+ //
+ var result = string.Empty;
+ if (!configuration.RabbitMQ.Queue.IsNullOrEmpty())
+ {
+ result = configuration.RabbitMQ.Queue;
+ }
+
+ //
+ if (result.IsNullOrEmpty())
+ {
+ //
+ var digits = Guid.NewGuid().ToString().GetDigits();
+ result = $"{configuration.RabbitMQ.Exchange}[{digits}]";
+ }
+
+ //
+ return result;
+ }
+
+ private void SetupConnection()
+ {
+ var factory = new ConnectionFactory
+ {
+ DispatchConsumersAsync = true,
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ };
+
+ //
+ connection = factory.CreateConnection();
+ channel = connection.CreateModel();
+ }
+
+ private void DeclareTopology()
+ {
+ //
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+
+ //
+ var queueName = GetQueueName();
+
+ //
+ channel.QueueDeclare(
+ arguments: null,
+ queue: queueName,
+ durable: configuration.RabbitMQ.QueueDurable,
+ exclusive: configuration.RabbitMQ.QueueExclusive,
+ autoDelete: configuration.RabbitMQ.QueueAutoDelete
+ );
+
+ //
+ channel.QueueBind(
+ arguments: null,
+ queue: queueName,
+ exchange: configuration.RabbitMQ.Exchange,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XRabbitMQProducerService.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using RabbitMQ.Client;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// RabbitMQ Producer Service Implementation ...
+ ///
+ public class XRabbitMQProducerService : XBaseEventProducerService, IXRabbitMQProducerService
+ {
+ private IModel channel;
+ private IConnection connection;
+ private readonly object connectionLock = new();
+
+ public XRabbitMQProducerService(
+ ILogger logger,
+ XEventServiceConfiguration configuration
+ ) : base(configuration, logger)
+ {
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ !configuration.RabbitMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XRabbitMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Initialize Connection ...
+ CreateConnection();
+ }
+
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ ///
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ exchange: request.Topic,
+ cancellationToken: cancellationToken,
+ routingKey: configuration.RabbitMQ.RoutingKey
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message to Topic ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ var result = await SendMessageAsync(
+ request: new XEventRequest
+ {
+ Topic = topic,
+ Message = message
+ },
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message to Exchange ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string exchange = null,
+ string routingKey = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var result = message.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ // Normalize Exchange ...
+ if (exchange.IsNullOrEmpty())
+ {
+ exchange = configuration.RabbitMQ.Exchange;
+ }
+
+ //
+ // Normalize Routing Key ...
+ if (routingKey.IsNull())
+ {
+ routingKey = configuration.RabbitMQ.RoutingKey;
+ }
+
+ //
+ try
+ {
+ //
+ // Ensure Connection is Alive ...
+ EnsureChannelAsync();
+
+ //
+ // Serialize Message ...
+ var body = Encoding.UTF8.GetBytes(
+ message.ToJSON(camelCase: true)
+ );
+
+ //
+ // Prepare Message Properties ...
+ var properties = channel.CreateBasicProperties();
+ properties.Persistent = true;
+ properties.ContentType = "application/json";
+ properties.Timestamp = new AmqpTimestamp(
+ DateTimeOffset.UtcNow.ToUnixTimeSeconds()
+ );
+ properties.MessageId = Guid.NewGuid().ToString();
+ properties.Type = message.Action;
+ properties.AppId = message.Sender;
+
+ //
+ // Publish Message ...
+ await Task.Run(() =>
+ {
+ //
+ channel.BasicPublish(
+ exchange: exchange,
+ routingKey: routingKey,
+ mandatory: false,
+ basicProperties: properties,
+ body: body
+ );
+ },
+ cancellationToken: cancellationToken);
+
+ //
+ result = true;
+ }
+ catch
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public void Dispose()
+ {
+ //
+ try
+ {
+ //
+ if (channel != null)
+ {
+ //
+ channel.Close();
+ channel.Dispose();
+ channel = null;
+ }
+
+ //
+ if (connection != null)
+ {
+ //
+ connection.Close();
+ connection.Dispose();
+ connection = null;
+ }
+ }
+ catch
+ { }
+
+ //
+ GC.SuppressFinalize(this);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// Create Connection to RabbitMQ Server ...
+ ///
+ private void CreateConnection()
+ {
+ //
+ var factory = new ConnectionFactory
+ {
+ Port = configuration.GetRabbitMQPort(),
+ HostName = configuration.GetRabbitMQHost(),
+ UserName = configuration.RabbitMQ.Username,
+ Password = configuration.RabbitMQ.Password,
+ VirtualHost = configuration.RabbitMQ.VirtualHost,
+ AutomaticRecoveryEnabled = configuration.RabbitMQ.AutomaticRecoveryEnabled,
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(
+ configuration.RabbitMQ.NetworkRecoveryIntervalSeconds
+ ),
+ DispatchConsumersAsync = true
+ };
+
+ //
+ connection = factory.CreateConnection();
+ }
+
+ ///
+ /// Check Connection is Alive ...
+ ///
+ private bool IsConnected()
+ {
+ //
+ var result =
+ !connection.IsNullOrDefault() &&
+ connection.IsOpen &&
+ !channel.IsNull() &&
+ channel.IsOpen;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Ensure Channel is Open and Exchange/Queue Declared ...
+ ///
+ private void EnsureChannelAsync()
+ {
+ //
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ lock (connectionLock)
+ {
+ //
+ // Double Check after Lock ...
+ if (IsConnected())
+ {
+ return;
+ }
+
+ //
+ // Recreate Connection if Dead ...
+ if (connection is null || !connection.IsOpen)
+ {
+ CreateConnection();
+ }
+
+ //
+ // Create Channel ...
+ channel = connection.CreateModel();
+
+ //
+ // Declare Exchange ...
+ channel.ExchangeDeclare(
+ arguments: null,
+ exchange: configuration.RabbitMQ.Exchange,
+ durable: configuration.RabbitMQ.ExchangeDurable,
+ autoDelete: configuration.RabbitMQ.ExchangeAutoDelete,
+ type: configuration.RabbitMQ.ExchangeType.GetStringValue()
+ );
+ }
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XZeroMQConsumerServiceBase.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using NetMQ;
+using NetMQ.Sockets;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ ///
+ /// an Abstract ZeroMQ Consumer Service ...
+ ///
+ public abstract class XZeroMQConsumerServiceBase : XBaseEventConsumerBackgroundService
+ {
+ //
+ private NetMQSocket socket;
+
+ protected XZeroMQConsumerServiceBase(
+ string[] allowedActions,
+ string[] allowedSenders,
+ IServiceProvider serviceProvider,
+ XEventServiceConfiguration configuration,
+ ILogger logger,
+ string topic = XEventServiceConstants.XDefaultTopic
+ ) : base(
+ topic: topic,
+ logger: logger,
+ configuration: configuration,
+ allowedActions: allowedActions,
+ allowedSenders: allowedSenders,
+ serviceProvider: serviceProvider
+ )
+ {
+ //
+ // Validating Configuration ...
+ if (configuration.Broker != XEventBroker.XKafka)
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ // Initialize ...
+ InitializeSocket();
+ }
+
+ ///
+ /// Default Action Execution for Background Services ...
+ ///
+ ///
+ ///
+ protected override async Task ExecuteAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ await Task.Yield();
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ //
+ try
+ {
+ //
+ string receivedTopic = string.Empty;
+ byte[] body = null;
+
+ if (configuration.ZeroMQ.Pattern == XZeroMQPattern.PubSub)
+ {
+ //
+ if (socket.TryReceiveFrameString(TimeSpan.FromMilliseconds(100), out receivedTopic))
+ {
+ //
+ if (socket.TryReceiveFrameBytes(TimeSpan.FromMilliseconds(100), out body))
+ {
+ // Process
+ }
+ }
+ }
+ else
+ {
+ if (socket.TryReceiveFrameBytes(TimeSpan.FromMilliseconds(100), out body))
+ {
+ // Process
+ }
+ }
+
+ if (body != null && body.Length > 0)
+ {
+ //
+ var json = Encoding.UTF8.GetString(body);
+ var message = json.FromJSON();
+ if (!message.IsNullOrDefault() &&
+ message.IsValid(
+ allowedSenders: allowedSenders,
+ allowedActions: allowedActions)
+ )
+ {
+ //
+ await DoWorkAsync(
+ payload: message,
+ cancellationToken: cancellationToken
+ );
+ }
+ }
+ else
+ {
+ await Task.Delay(10, cancellationToken);
+ }
+ }
+ catch (Exception)
+ {
+ await Task.Delay(1000, cancellationToken);
+ }
+ }
+ }
+
+ public override async Task StopAsync(CancellationToken cancellationToken)
+ {
+ //
+ socket.Close();
+ await base.StopAsync(cancellationToken);
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public override void Dispose()
+ {
+ //
+ if (socket != null)
+ {
+ socket.Dispose();
+ }
+
+ //
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+
+ //
+ #region Private ...
+ private void InitializeSocket()
+ {
+ //
+ if (configuration.ZeroMQ.Pattern == XZeroMQPattern.PubSub)
+ {
+ //
+ socket = new SubscriberSocket();
+ socket.Options.ReceiveHighWatermark = configuration.ZeroMQ.ReceiveHighWaterMark;
+
+ //
+ // Subscribe to specific topic or all
+ if (topic.IsNullOrEmpty() || topic == XEventServiceConstants.XWildcard)
+ {
+ ((SubscriberSocket)socket).SubscribeToAnyTopic();
+ }
+ else
+ {
+ ((SubscriberSocket)socket).Subscribe(topic);
+ }
+ }
+ else
+ {
+ //
+ socket = new PullSocket();
+ socket.Options.ReceiveHighWatermark = configuration.ZeroMQ.ReceiveHighWaterMark;
+ }
+
+ //
+ if (configuration.ZeroMQ.IsServer)
+ {
+ socket.Bind(configuration.Url);
+ }
+ else
+ {
+ socket.Connect(configuration.Url);
+ }
+ }
+ #endregion
+ }
+}
+
+### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xEventService\Providers\XZeroMQProducerService.cs
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using NetMQ;
+using NetMQ.Sockets;
+using xCommons.Extensions;
+using xEventService.Configuration;
+using xEventService.Constants;
+using xEventService.Extensions;
+using xEventService.Interfaces;
+using xEventService.Models;
+using xExceptions.Constants;
+
+namespace xEventService.Providers
+{
+ public class XZeroMQProducerService : XBaseEventProducerService, IXZeroMQProducerService
+ {
+ //
+ private NetMQSocket socket;
+ private readonly object socketLock = new();
+
+ public XZeroMQProducerService(
+ XEventServiceConfiguration configuration,
+ ILogger logger
+ ) : base(configuration, logger)
+ {
+ //
+ // Validate Configurations ...
+ if (!configuration.IsValid() ||
+ !configuration.ZeroMQ.IsValid() ||
+ configuration.Broker != XEventBroker.XZeroMQ
+ )
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ InitializeSocket();
+ }
+
+ ///
+ /// Sending Message with Full Request Descriptor ...
+ ///
+ ///
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventRequest request,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ var result = request.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ result = await SendMessageAsync(
+ message: request.Message,
+ topic: request.Topic,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
+
+ ///
+ /// Sending a Message to Topic ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task SendMessageAsync(
+ XEventMessage message,
+ string topic = XEventServiceConstants.XDefaultTopic,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ var result = message.IsValid();
+ if (!result)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ // Normalize Topic ...
+ if (topic.IsNullOrEmpty())
+ {
+ topic = XEventServiceConstants.XDefaultTopic;
+ }
+
+ //
+ try
+ {
+ //
+ var json = message.ToJSON(camelCase: true);
+ var body = Encoding.UTF8.GetBytes(json);
+ await Task.Run(() =>
+ {
+ //
+ lock (socketLock)
+ {
+ //
+ if (configuration.ZeroMQ.Pattern == XZeroMQPattern.PubSub)
+ {
+ socket.SendMoreFrame(topic).SendFrame(body);
+ }
+ else
+ {
+ socket.SendFrame(body);
+ }
+ }
+ }, cancellationToken);
+
+ //
+ result = true;
+ }
+ catch (Exception)
+ {
+ result = false;
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Dispose Object ...
+ ///
+ public void Dispose()
+ {
+ //
+ try
+ {
+ //
+ lock (socketLock)
+ {
+ //
+ if (socket != null)
+ {
+ //
+ socket.Close();
+ socket.Dispose();
+ socket = null;
+ }
+ }
+ }
+ catch { }
+
+ //
+ GC.SuppressFinalize(this);
+ }
+
+ //
+ #region Private ...
+ ///
+ /// Initialize Socket ...
+ ///
+ private void InitializeSocket()
+ {
+ //
+ lock (socketLock)
+ {
+ //
+ if (configuration.ZeroMQ.Pattern == XZeroMQPattern.PubSub)
+ {
+ //
+ socket = new PublisherSocket();
+ socket.Options.SendHighWatermark = configuration.ZeroMQ.SendHighWaterMark;
+ }
+ else
+ {
+ //
+ socket = new PushSocket();
+ socket.Options.SendHighWatermark = configuration.ZeroMQ.SendHighWaterMark;
+ }
+
+ //
+ if (configuration.ZeroMQ.IsServer)
+ {
+ socket.Bind(configuration.Url);
+ }
+ else
+ {
+ socket.Connect(configuration.Url);
+ }
+ }
+ }
+ #endregion
+ }
+}
diff --git a/Documents/versions.txt b/Documents/versions.txt
new file mode 100644
index 0000000..6bb81a3
--- /dev/null
+++ b/Documents/versions.txt
@@ -0,0 +1,6 @@
+npm install -g @angular/cli@12.0.4
+npm install -g cordova @ionic/cli@6.6.0
+
+npm install -g --allow-scripts=@angular/cli
+
+curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash && source ~/.profile && nvm install v14.17.3
\ No newline at end of file
diff --git a/Modules/xCommons b/Modules/xCommons
index 32dd5dd..4697937 160000
--- a/Modules/xCommons
+++ b/Modules/xCommons
@@ -1 +1 @@
-Subproject commit 32dd5dd3c11baeb0cf51ec53cc5407a1be6ba1b8
+Subproject commit 469793770977fbb6f8c58e09c8c7a30781056d0b
diff --git a/Modules/xEventService b/Modules/xEventService
index b20c2f8..e05420b 160000
--- a/Modules/xEventService
+++ b/Modules/xEventService
@@ -1 +1 @@
-Subproject commit b20c2f8db7fc11bec7edb76811999eb1643bbc6c
+Subproject commit e05420b1aaa49654b281d52004f1de78a99bcded