### Install CacheAdvance Package Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/docs/src/app/page.md Use npm to install the CacheAdvance library. This is the first step before configuring or using the library. ```shell npm install @tailwindlabs/cache-advance ``` -------------------------------- ### Install npm Dependencies Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/docs/README.md Run this command in your terminal to install the necessary npm dependencies for the project. ```bash npm install ``` -------------------------------- ### Run Development Server Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/docs/README.md Execute this command to start the Next.js development server. The site will be accessible at http://localhost:3000. ```bash npm run dev ``` -------------------------------- ### Add Oragon.RabbitMQ Packages Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Install the core Oragon.RabbitMQ library and the System.Text.Json serializer using the .NET CLI. ```bash dotnet add package Oragon.RabbitMQ dotnet add package Oragon.RabbitMQ.Serializer.SystemTextJson ``` -------------------------------- ### Add Oragon.RabbitMQ.AspireClient Package Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Install the Oragon.RabbitMQ.AspireClient package for .NET Aspire integration with RabbitMQ.Client 7.x. ```bash dotnet add package Oragon.RabbitMQ.AspireClient ``` -------------------------------- ### Configure RabbitMQ Client Settings via Configuration Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/src/Oragon.RabbitMQ.AspireClient/README.md Example appsettings.json demonstrating how to disable health checks for the RabbitMQ client using configuration providers. ```json { "Aspire": { "RabbitMQ": { "Client": { "DisableHealthChecks": true } } } } ``` -------------------------------- ### Configure CacheAdvance Library Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/docs/src/app/page.md Create a configuration file to define the caching strategy and engine options. This example sets a 'predictive' strategy with specific CPU and backup settings. ```javascript // cache-advance.config.js export default { strategy: 'predictive', engine: { cpus: 12, backups: ['./storage/cache.wtf'], }, } ``` -------------------------------- ### RabbitMQ Connection String Configuration Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/src/Oragon.RabbitMQ.AspireClient/README.md Example of a ConnectionStrings configuration section in appsettings.json to define a RabbitMQ connection string. ```json { "ConnectionStrings": { "myConnection": "amqp://username:password@localhost:5672" } } ``` -------------------------------- ### Wait for RabbitMQ Broker Startup Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Blocks application startup until the RabbitMQ broker is reachable, using exponential backoff. Useful for standalone or Docker Compose setups. ```csharp var app = builder.Build(); // Wait for broker before mapping queues (standalone / Docker Compose setup) await app.Services.WaitRabbitMQAsync(); // With keyed connection await app.Services.WaitRabbitMQAsync(keyedServiceKey: "orders-broker"); app.MapQueue("orders", ([FromServices] OrderService svc, OrderCreated msg) => svc.HandleAsync(msg)); app.Run(); ``` -------------------------------- ### MapQueue for RabbitMQ Consumers Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md This snippet demonstrates how to map a RabbitMQ queue for consumption using the familiar MapQueue() pattern, analogous to ASP.NET Core's MapPost(). It shows the basic setup for handling messages from a queue. ```csharp // ASP.NET Core — HTTP app.MapPost("/orders", ([FromServices] OrderService svc, [FromBody] OrderCreated msg) => svc.HandleAsync(msg)); // Oragon.RabbitMQ — AMQP app.MapQueue("orders", ([FromServices] OrderService svc, [FromBody] OrderCreated msg) => svc.HandleAsync(msg)); ``` -------------------------------- ### AddAmqpSerializer (System.Text.Json) Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Registers the System.Text.Json serializer as the IAmqpSerializer singleton. Supports keyed registration for multiple serializer setups. ```APIDOC ## AddAmqpSerializer (System.Text.Json) ### Description Registers the System.Text.Json serializer as the `IAmqpSerializer` singleton. Supports keyed registration for multi-serializer setups. ### Usage ```csharp // Default (unnamed) serializer builder.Services.AddAmqpSerializer(options: JsonSerializerOptions.Web); // Named serializer builder.Services.AddAmqpSerializer(key: "compact-json", options: new JsonSerializerOptions { WriteIndented = false }); // Wire a specific queue to the named serializer app.MapQueue("compact-events", ([FromBody] MyEvent evt) => HandleAsync(evt)) .WithSerializer(sp => sp.GetRequiredKeyedService("compact-json")); ``` ``` -------------------------------- ### Retrieve IConnection from DI Container Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/src/Oragon.RabbitMQ.AspireClient/README.md Example of how to inject and use the IConnection instance in a Web API controller. ```csharp private readonly IConnection _connection; public ProductsController(IConnection connection) { _connection = connection; } ``` -------------------------------- ### Configure RabbitMQ Consumer with ConsumerDescriptor Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Use the fluent ConsumerDescriptor to configure prefetch, concurrency, error handling, topology, and connection settings for a RabbitMQ consumer. The descriptor is immutable once the consumer starts. ```csharp app.MapQueue("purchase", ([FromServices] IPurchaseService svc, [FromBody] PurchaseRequest req) => svc.Purchase(req)) // Pull up to 100 messages from the broker at once .WithPrefetch(100) // Process up to 8 messages concurrently on this consumer .WithDispatchConcurrency(8) // Tag this consumer for easier identification in RabbitMQ Management UI .WithConsumerTag("purchase-worker-1") // Declare topology (exchanges/queues/bindings) at startup .WithTopology(async (channel, ct) => { await channel.QueueDeclareAsync("purchase-dlq", durable: true, exclusive: false, autoDelete: false, arguments: null, cancellationToken: ct); await channel.QueueDeclareAsync("purchase", durable: true, exclusive: false, autoDelete: false, arguments: new Dictionary { { "x-dead-letter-exchange", "" }, { "x-dead-letter-routing-key", "purchase-dlq" } }, cancellationToken: ct); }) // On handler exception: nack and requeue (retry) .WhenProcessFail((ctx, ex) => { ctx.LogException(ex); return AmqpResults.Nack(requeue: true); }) // On deserialization failure: reject without requeue (send to DLQ) .WhenSerializationFail((ctx, ex) => AmqpResults.Reject(requeue: false)) // Use a named/keyed connection for multi-broker setups .WithConnection((sp, ct) => Task.FromResult(sp.GetRequiredKeyedService("orders-broker"))); ``` -------------------------------- ### Build All Projects Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Builds all projects in the solution using the solution XML format. ```bash dotnet build ./Oragon.RabbitMQ.slnx ``` -------------------------------- ### Build Solution in Container Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Builds the entire solution within the running build environment container. ```bash # Build dotnet build ./Oragon.RabbitMQ.slnx ``` -------------------------------- ### Configure RabbitMQ Consumer Infrastructure Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Sets up the necessary services for Oragon.RabbitMQ consumer functionality, including serializer and connection factory configuration. ```csharp using Oragon.RabbitMQ; using Oragon.RabbitMQ.Serializer; using RabbitMQ.Client; var builder = Host.CreateApplicationBuilder(args); // 1. Register consumer infrastructure builder.AddRabbitMQConsumer(); // 2. Register serializer builder.Services.AddAmqpSerializer(options: JsonSerializerOptions.Default); // 3. Register RabbitMQ connection builder.Services.AddSingleton(sp => new ConnectionFactory() { Uri = new Uri("amqp://guest:guest@localhost:5672"), DispatchConsumersAsync = true }); builder.Services.AddSingleton(sp => sp.GetRequiredService().CreateConnectionAsync().GetAwaiter().GetResult()); // 4. Register your service builder.Services.AddSingleton(); // singleton, scoped or transient var app = builder.Build(); ``` -------------------------------- ### Run Unit Tests Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Executes unit tests using the xUnit framework. ```bash dotnet test ./tests/Oragon.RabbitMQ.UnitTests/Oragon.RabbitMQ.UnitTests.csproj ``` -------------------------------- ### Configure RabbitMQ Consumer with Fluent Methods Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Use this snippet to map a queue and configure consumer behavior like prefetch count, dispatch concurrency, and custom failure handling. ```csharp app.MapQueue("orders", ([FromServices] OrderService svc, OrderCreated msg) => svc.HandleAsync(msg)) .WithPrefetch(100) .WithDispatchConcurrency(8) .WhenProcessFail((ctx, ex) => AmqpResults.Nack(requeue: true)); ``` -------------------------------- ### WaitRabbitMQAsync Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Blocks application startup until the RabbitMQ broker is reachable, using an exponential-backoff Polly pipeline. ```APIDOC ## WaitRabbitMQAsync ### Description Blocks startup until the RabbitMQ broker is reachable. Polls the broker using an exponential-backoff Polly pipeline (max 10 retries, 5 s initial delay, 30 s per-attempt timeout). Useful in non-Aspire setups or integration tests. ### Usage ```csharp var app = builder.Build(); // Wait for broker before mapping queues (standalone / Docker Compose setup) await app.Services.WaitRabbitMQAsync(); // With keyed connection await app.Services.WaitRabbitMQAsync(keyedServiceKey: "orders-broker"); app.MapQueue("orders", ([FromServices] OrderService svc, OrderCreated msg) => svc.HandleAsync(msg)); app.Run(); ``` ``` -------------------------------- ### Configure RabbitMQ Client with .NET Aspire Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Integrates Oragon.RabbitMQ client support into a .NET Aspire application using the provided extension method. ```csharp builder.AddRabbitMQClient("rabbitmq"); ``` -------------------------------- ### ConsumerServer StartAsync Thread Safety Issues Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md This code demonstrates potential thread safety violations within the StartAsync method of ConsumerServer. It highlights issues with List thread safety, fire-and-forget tasks without tracking, lack of memory barrier for IsReadOnly, and potential race conditions when accessing the Consumers property. ```csharp public async Task StartAsync(CancellationToken cancellationToken) { this.IsReadOnly = true; // No memory barrier foreach (IConsumerDescriptor consumer in this.ConsumerDescriptors) { this.internalConsumers.Add(...); // List is not thread-safe } foreach (IHostedAmqpConsumer consumer in this.internalConsumers) { _ = Task.Factory.StartNew(...); // Fire-and-forget, no tracking } } ``` -------------------------------- ### Pack a Specific Project Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Packs a specific project into a NuGet package with a specified version. ```bash dotnet pack ./src/Oragon.RabbitMQ/Oragon.RabbitMQ.csproj --configuration Release -p:PackageVersion=1.0.0 ``` -------------------------------- ### Run Build Environment Container Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Runs the Docker container for the build environment. Mounts the Docker socket and current directory for integration tests and development. ```bash docker run --privileged -it --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ -w /projeto \ -v ./:/projeto \ oragon-rabbitmq-builder ``` -------------------------------- ### Fluent API for Consumer Configuration Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md Demonstrates the fluent API for configuring a message queue consumer. It allows setting prefetch count, dispatch concurrency, and defining behavior for processing failures. ```csharp app.MapQueue("orders", (OrderService svc, Order order) => svc.Process(order)) .WithPrefetch(100) .WithDispatchConcurrency(4) .WhenProcessFail((ctx, ex) => AmqpResults.Nack(requeue: true)); ``` -------------------------------- ### Consumer Configuration Methods Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Configuration options for Oragon RabbitMQ consumers using fluent methods on the ConsumerDescriptor. ```APIDOC ## Consumer Configuration All configuration is done through fluent methods on the `ConsumerDescriptor` returned by `MapQueue()`: ### Methods - `.WithPrefetch(ushort)`: Number of messages prefetched from the broker. Default: `1`. - `.WithDispatchConcurrency(ushort)`: Concurrent message processing slots. Default: `1`. - `.WithConsumerTag(string)`: Custom consumer tag. Default: auto-generated. - `.WithExclusive(bool)`: Exclusive consumer on the queue. Default: `false`. - `.WithTopology(Func)`: Declare exchanges/queues/bindings on startup. Default: none. - `.WithConnection(Func>)`: Custom connection factory. Default: `IConnection` from DI. - `.WithSerializer(Func)`: Custom serializer factory. Default: `IAmqpSerializer` from DI. - `.WithChannel(Func>)`: Custom channel factory. Default: auto-created. - `.WhenSerializationFail(Func)`: Behavior on deserialization errors. Default: `Reject(requeue: false)`. - `.WhenProcessFail(Func)`: Behavior on handler exceptions. Default: `Nack(requeue: false)`. ### Example Usage ```csharp app.MapQueue("orders", ([FromServices] OrderService svc, OrderCreated msg) => svc.HandleAsync(msg)) .WithPrefetch(100) .WithDispatchConcurrency(8) .WhenProcessFail((ctx, ex) => AmqpResults.Nack(requeue: true)); ``` ``` -------------------------------- ### Build Builder Docker Image Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Builds the Docker image used for the local build pipeline. This image contains the .NET 10 SDK and other necessary tools. ```bash docker build -t oragon-rabbitmq-builder . ``` -------------------------------- ### Map Queue with Consumer Configuration Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Maps a queue to a handler and configures consumer options like prefetch count and dispatch concurrency. ```csharp // 3. Map queues after app.Build() app.MapQueue("queueName", (MyService svc, MyMessage msg) => svc.Handle(msg)) .WithPrefetch(100) .WithDispatchConcurrency(4); ``` -------------------------------- ### Register System.Text.Json Serializer Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Use AddAmqpSerializer to register System.Text.Json. Supports default and keyed registrations for different serialization needs. ```csharp using System.Text.Json; using Oragon.RabbitMQ; // Default (unnamed) serializer — used by all MapQueue calls unless overridden builder.Services.AddAmqpSerializer(options: JsonSerializerOptions.Web); // Named serializer — use with .WithSerializer(...) on a specific consumer builder.Services.AddAmqpSerializer(key: "compact-json", options: new JsonSerializerOptions { WriteIndented = false }); // Wire a specific queue to the named serializer app.MapQueue("compact-events", ([FromBody] MyEvent evt) => HandleAsync(evt)) .WithSerializer(sp => sp.GetRequiredKeyedService("compact-json")); ``` -------------------------------- ### Configure RabbitMQ Client with Connection String Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/src/Oragon.RabbitMQ.AspireClient/README.md Configures the RabbitMQ client to use a connection string defined in the application's configuration. The connection string name is passed to the AddRabbitMQClient method. ```csharp builder.AddRabbitMQClient("myConnection"); ``` -------------------------------- ### Run Integration Tests Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Executes integration tests. Requires Docker for Testcontainers.RabbitMq. ```bash dotnet test ./tests/Oragon.RabbitMQ.IntegratedTests/Oragon.RabbitMQ.IntegratedTests.csproj ``` -------------------------------- ### Restore Workloads in Container Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Restores .NET workloads within the running build environment container. ```bash # Restore workloads dotnet workload restore ./Oragon.RabbitMQ.slnx ``` -------------------------------- ### Register RabbitMQ Client with .NET Aspire Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Integrates RabbitMQ with .NET Aspire, providing retries, health checks, and tracing. Supports multi-broker configurations. ```csharp // Package: Oragon.RabbitMQ.AspireClient // AppHost: builder.AddRabbitMQ("rabbitmq"); // Worker / API project builder.AddRabbitMQConsumer(); builder.AddRabbitMQClient("rabbitmq"); // reads ConnectionStrings:rabbitmq from Aspire builder.Services.AddAmqpSerializer(options: JsonSerializerOptions.Web); // Multi-broker: register additional connections as keyed services builder.AddKeyedRabbitMQClient("orders-broker"); builder.AddKeyedRabbitMQClient("analytics-broker"); // Wire a queue to the keyed connection app.MapQueue("order-events", ([FromBody] OrderEvent evt) => HandleAsync(evt)) .WithConnection((sp, ct) => Task.FromResult(sp.GetRequiredKeyedService("orders-broker"))); // appsettings.json (or Aspire resource config) // { // "Aspire": { // "RabbitMQ": { // "Client": { // "MaxConnectRetryCount": 5, // "DisableHealthChecks": false, // "DisableTracing": false // } // } // } // } ``` -------------------------------- ### Run All Tests in Container Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Executes all tests (unit and integration) within the running build environment container. ```bash # Run all tests (unit + integration) dotnet test ./Oragon.RabbitMQ.slnx ``` -------------------------------- ### Add RabbitMQ Client to DI Container Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/src/Oragon.RabbitMQ.AspireClient/README.md Registers an IConnection for RabbitMQ in the DI container. Use this in your application's Program.cs file. ```csharp builder.AddRabbitMQClient("messaging"); ``` -------------------------------- ### Implement IAsyncDisposable on ConsumerServer Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md Recommends implementing IAsyncDisposable on ConsumerServer to properly manage IAsyncDisposable children, avoiding blocking async operations in Dispose(). ```csharp ConsumerServer ``` -------------------------------- ### Register Newtonsoft.Json Serializer Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Register NewtonsoftAmqpSerializer for legacy producers. Supports keyed registration for coexistence with other serializers. ```csharp using Newtonsoft.Json; using Oragon.RabbitMQ; // Package: Oragon.RabbitMQ.Serializer.NewtonsoftJson builder.Services.AddAmqpSerializer(options: new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None, NullValueHandling = NullValueHandling.Ignore, DateFormatHandling = DateFormatHandling.IsoDateFormat }); // Keyed variant for side-by-side use with System.Text.Json builder.Services.AddNewtonsoftAmqpSerializer(key: "legacy", options: new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }); ``` -------------------------------- ### Run Tests with Coverage in Container Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Runs tests with code coverage collection enabled, mirroring the CI process. ```bash # Or run tests with coverage (as CI does) dotnet-coverage collect "dotnet test --framework net10.0 -p:TargetFrameworks=net10.0" -f xml -o /output-coverage/coverage.xml ``` -------------------------------- ### Map Queue to Handler (Simple) Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Maps a RabbitMQ queue named 'orders' to a handler function that processes 'OrderCreated' messages using an injected 'OrderService'. ```csharp // 5. Map queue to handler app.MapQueue("orders", ([FromServices] OrderService svc, OrderCreated msg) => svc.HandleAsync(msg)); app.Run(); ``` -------------------------------- ### Optimize Reflection Performance with Compiled Delegates Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md Replaces slow DynamicInvoke with compiled delegates using Expression.Lambda for improved performance in high-throughput scenarios. Compile delegates at startup. ```csharp return this.handler.DynamicInvoke(arguments); ``` -------------------------------- ### AddRabbitMQClient (.NET Aspire) Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Registers a RabbitMQ connection using .NET Aspire, including built-in retries, health checks, and OpenTelemetry tracing. ```APIDOC ## AddRabbitMQClient (.NET Aspire) ### Description Registers RabbitMQ connection via Aspire, including built-in retries, health checks, and OpenTelemetry tracing. Compatible with .NET Aspire's service discovery and connection string conventions. ### Usage ```csharp // Package: Oragon.RabbitMQ.AspireClient // AppHost: builder.AddRabbitMQ("rabbitmq"); // Worker / API project builder.AddRabbitMQConsumer(); builder.AddRabbitMQClient("rabbitmq"); // reads ConnectionStrings:rabbitmq from Aspire builder.Services.AddAmqpSerializer(options: JsonSerializerOptions.Web); // Multi-broker: register additional connections as keyed services builder.AddKeyedRabbitMQClient("orders-broker"); builder.AddKeyedRabbitMQClient("analytics-broker"); // Wire a queue to the keyed connection app.MapQueue("order-events", ([FromBody] OrderEvent evt) => HandleAsync(evt)) .WithConnection((sp, ct) => Task.FromResult(sp.GetRequiredKeyedService("orders-broker"))); // appsettings.json (or Aspire resource config) // { // "Aspire": { // "RabbitMQ": { // "Client": { // "MaxConnectRetryCount": 5, // "DisableHealthChecks": false, // "DisableTracing": false // } // } // } // } ``` ``` -------------------------------- ### Default Configuration Object Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/docs/src/app/docs/installation/page.md This JavaScript object defines the default configuration for the lorem ipsum generator. It includes basic settings for text generation and formatting. ```javascript /** @type {import('@tailwindlabs/lorem').ipsum} */ export default { lorem: 'ipsum', dolor: ['sit', 'amet', 'consectetur'], adipiscing: { elit: true, }, } ``` -------------------------------- ### Register RabbitMQ Consumer Infrastructure Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Registers the ConsumerServer hosted service and related infrastructure. Ensure this is called before MapQueue. Includes options for serializer and connection factory registration. ```csharp using Oragon.RabbitMQ; using Oragon.RabbitMQ.Serializer; using RabbitMQ.Client; var builder = Host.CreateApplicationBuilder(args); // Register the consumer hosted service builder.AddRabbitMQConsumer(); // Register System.Text.Json serializer builder.Services.AddAmqpSerializer(options: JsonSerializerOptions.Default); // Register RabbitMQ connection (used by all consumers by default) builder.Services.AddSingleton(_ => new ConnectionFactory { Uri = new Uri("amqp://guest:guest@localhost:5672") }); builder.Services.AddSingleton(sp => sp.GetRequiredService().CreateConnectionAsync().GetAwaiter().GetResult()); // Register application services builder.Services.AddScoped(); var app = builder.Build(); // ... MapQueue calls ... app.Run(); ``` -------------------------------- ### Configure RabbitMQ ConnectionFactory with Inline Delegate Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/src/Oragon.RabbitMQ.AspireClient/README.md Sets the client provided name for RabbitMQ connections using an inline delegate for the ConnectionFactory. ```csharp builder.AddRabbitMQClient("messaging", configureConnectionFactory: factory => factory.ClientProvidedName = "MyApp"); ``` -------------------------------- ### Register RabbitMQ Consumer Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Registers the ConsumerServer as an IHostedService. Ensure serializers are registered separately. ```csharp // 1. Register consumer server builder.AddRabbitMQConsumer(); // Adds ConsumerServer as IHostedService ``` -------------------------------- ### Configure RabbitMQ Client with Inline Delegate Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/src/Oragon.RabbitMQ.AspireClient/README.md Disables health checks for the RabbitMQ client by passing a configuration delegate to AddRabbitMQClient. ```csharp builder.AddRabbitMQClient("messaging", settings => settings.DisableHealthChecks = true); ``` -------------------------------- ### Add RabbitMQ Resource to AppHost Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/src/Oragon.RabbitMQ.AspireClient/README.md Registers a RabbitMQ resource in the AppHost project. This makes the RabbitMQ service available to other services in the distributed application. ```csharp var messaging = builder.AddRabbitMQ("messaging"); var myService = builder.AddProject() .WithReference(messaging); ``` -------------------------------- ### Manage AMQP Acknowledgments with AmqpResults Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Use the AmqpResults static factory to return IAmpqResult implementations for explicit control over the AMQP acknowledgment lifecycle. Results like Ack, Nack, and Reject are cached singletons. ```csharp app.MapQueue("orders", async ([FromServices] OrderService svc, OrderCreated msg) => { // --- Basic acknowledgments --- // BasicAck — message processed successfully return AmqpResults.Ack(); // BasicNack(requeue: true) — transient failure, put back in queue // BasicNack(requeue: false) — permanent failure, route to DLQ return AmqpResults.Nack(requeue: false); // BasicReject(requeue: false) — semantically "bad message", route to DLQ return AmqpResults.Reject(requeue: false); }); ``` ```csharp // --- RPC: reply to the caller's ReplyTo queue --- app.MapQueue("rpc-add", ([FromBody] AddRequest req) => AmqpResults.ReplyAndAck(new AddResponse { Result = req.A + req.B })); ``` ```csharp // Reply without Ack (caller must ack separately via Compose) app.MapQueue("rpc-query", async ([FromServices] CatalogService svc, [FromBody] CatalogQuery q) => AmqpResults.Reply(await svc.QueryAsync(q))); ``` ```csharp // --- Routing: forward to another exchange --- app.MapQueue("ingest", ([FromBody] RawEvent raw) => AmqpResults.ForwardAndAck( exchange: "processed-events", routingKey: raw.EventType, mandatory: true, new ProcessedEvent { Id = raw.Id, Payload = raw.Payload })); ``` ```csharp // Forward without Ack (ack explicitly via Compose) app.MapQueue("fanout-source", ([FromBody] DomainEvent evt) => AmqpResults.Forward("notifications", "", mandatory: false, evt)); ``` ```csharp // --- Compose: chain multiple results sequentially --- app.MapQueue("saga-step", async ([FromServices] SagaService svc, [FromBody] SagaCommand cmd) => { var next = await svc.ExecuteStepAsync(cmd); return AmqpResults.Compose( AmqpResults.Forward("saga-exchange", next.NextStep, true, next.NextCommand), AmqpResults.Ack() ); }); ``` -------------------------------- ### Map Queue with Exception Handling and Manual Ack/Nack Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Maps a queue to a handler that includes a try-catch block to handle exceptions during message processing. It returns an Ack on success and a Nack with requeue on failure. ```csharp app.MapQueue("orders", async ([FromServices] OrderService svc, OrderCreated msg) => { try { await svc.HandleAsync(msg); return AmqpResults.Ack(); } catch (Exception ex) { // Log the exception return AmqpResults.Nack(requeue: true); } }); ``` -------------------------------- ### MapQueue Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Binds a RabbitMQ queue name to a delegate handler, enabling fluent configuration through a ConsumerDescriptor. Parameter binding follows ASP.NET Core Minimal APIs conventions. ```APIDOC ## MapQueue ### Description Maps a RabbitMQ queue name to a delegate handler, returning a `ConsumerDescriptor` for further fluent configuration. The handler parameters are resolved via the same model-binding rules as ASP.NET Core Minimal APIs. Available on both `IHost` and `IServiceProvider`. ### Usage #### Simple fire-and-forget handler Implicit Ack on success, Nack on exception. ```csharp app.MapQueue("orders", ([FromServices] OrderService svc, OrderCreated msg) => svc.HandleAsync(msg)); ``` #### Explicit flow control Return `IAmqpResult` for full AMQP control. ```csharp app.MapQueue("payments", async ([FromServices] PaymentService svc, PaymentRequested msg) => { if (!svc.CanProcess(msg.PaymentMethod)) return AmqpResults.Nack(requeue: true); await svc.ProcessAsync(msg); return AmqpResults.Ack(); }); ``` #### Access raw AMQP metadata Via auto-bound parameters (no attribute needed). ```csharp app.MapQueue("audit", (IAmqpContext ctx, IReadOnlyBasicProperties props, [FromBody] AuditEvent evt, CancellationToken ct) => { Console.WriteLine($"MsgId={props.MessageId} CorrelationId={props.CorrelationId}"); return AmqpResults.Ack(); }); ``` ``` -------------------------------- ### ConsumerServer Dispose Blocking Async Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md This code snippet illustrates a critical issue in the Dispose method of ConsumerServer where asynchronous disposal is blocked using GetAwaiter().GetResult(). This can lead to deadlocks in synchronization contexts and lacks proper exception handling, potentially halting cleanup on the first failure. Implementing IAsyncDisposable is recommended. ```csharp protected virtual void Dispose(bool disposing) { foreach (IHostedAmqpConsumer consumer in this.internalConsumers.NewReverseList()) { consumer.DisposeAsync().AsTask().GetAwaiter().GetResult(); // BLOCKS! } } ``` -------------------------------- ### AddRabbitMQClient Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/src/Oragon.RabbitMQ.AspireClient/PublicAPI.Unshipped.txt Adds a RabbitMQ client to the host application builder. This is a convenient method for registering a single RabbitMQ client. ```APIDOC ## AddRabbitMQClient ### Description Adds a RabbitMQ client to the host application builder, providing a straightforward way to register a single RabbitMQ client instance. ### Method `static void AddRabbitMQClient(this IHostApplicationBuilder builder, string connectionName, Action? configureSettings = null, Action? configureConnectionFactory = null)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```csharp builder.Services.AddRabbitMQClient("defaultRabbitMQ", settings => { settings.ConnectionString = "amqp://guest:guest@localhost:5672"; }); ``` ### Response #### Success Response (void) This method does not return a value. ``` -------------------------------- ### Ensure ConfigureAwait(false) Consistency Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md Standardizes ConfigureAwait usage to 'false' across the library, except for this specific location which incorrectly uses 'true'. ```csharp await this.ForwardMessage(context, objectToForward).ConfigureAwait(true); // Inconsistent ``` -------------------------------- ### AddRabbitMQConsumer Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Registers the necessary consumer infrastructure, including the ConsumerServer hosted service, into the application's DI container. This method must be called before MapQueue. ```APIDOC ## AddRabbitMQConsumer ### Description Registers the `ConsumerServer` hosted service and all related infrastructure into the application's DI container. Must be called before `MapQueue`. Accepts either `IHostApplicationBuilder` or `IServiceCollection`. ### Usage ```csharp using Oragon.RabbitMQ; using Oragon.RabbitMQ.Serializer; using RabbitMQ.Client; var builder = Host.CreateApplicationBuilder(args); // Register the consumer hosted service builder.AddRabbitMQConsumer(); // Register System.Text.Json serializer builder.Services.AddAmqpSerializer(options: JsonSerializerOptions.Default); // Register RabbitMQ connection (used by all consumers by default) builder.Services.AddSingleton(_ => new ConnectionFactory { Uri = new Uri("amqp://guest:guest@localhost:5672") }); builder.Services.AddSingleton(sp => sp.GetRequiredService().CreateConnectionAsync().GetAwaiter().GetResult()); // Register application services builder.Services.AddScoped(); var app = builder.Build(); // ... MapQueue calls ... app.Run(); ``` ``` -------------------------------- ### Bind RabbitMQ Queue to Handler Delegate Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Maps a RabbitMQ queue name to a delegate handler, enabling fluent configuration via ConsumerDescriptor. Supports implicit Ack/Nack, explicit flow control with IAmqpResult, and access to raw AMQP metadata. ```csharp // Simple fire-and-forget handler — implicit Ack on success, Nack on exception app.MapQueue("orders", ([FromServices] OrderService svc, OrderCreated msg) => svc.HandleAsync(msg)); ``` ```csharp // Explicit flow control — return IAmqpResult for full AMQP control app.MapQueue("payments", async ([FromServices] PaymentService svc, PaymentRequested msg) => { if (!svc.CanProcess(msg.PaymentMethod)) return AmqpResults.Nack(requeue: true); await svc.ProcessAsync(msg); return AmqpResults.Ack(); }); ``` ```csharp // Access raw AMQP metadata via auto-bound parameters (no attribute needed) app.MapQueue("audit", (IAmqpContext ctx, IReadOnlyBasicProperties props, [FromBody] AuditEvent evt, CancellationToken ct) => { Console.WriteLine($"MsgId={props.MessageId} CorrelationId={props.CorrelationId}"); return AmqpResults.Ack(); }); ``` -------------------------------- ### Register AMQP Serializer Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Registers a serializer for AMQP messages. Uses System.Text.Json by default. ```csharp // 2. Register serializer builder.Services.AddAmqpSerializer(options: JsonSerializerOptions.Default); ``` -------------------------------- ### AddKeyedRabbitMQClient Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/src/Oragon.RabbitMQ.AspireClient/PublicAPI.Unshipped.txt Adds a keyed RabbitMQ client to the host application builder. This allows for multiple RabbitMQ clients to be registered with distinct names. ```APIDOC ## AddKeyedRabbitMQClient ### Description Adds a keyed RabbitMQ client to the host application builder, enabling the registration of multiple RabbitMQ clients with unique names. ### Method `static void AddKeyedRabbitMQClient(this IHostApplicationBuilder builder, string name, Action? configureSettings = null, Action? configureConnectionFactory = null)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```csharp builder.Services.AddKeyedRabbitMQClient("myRabbitMQ", settings => { settings.ConnectionString = "amqp://localhost:5672"; }); ``` ### Response #### Success Response (void) This method does not return a value. ``` -------------------------------- ### AddAmqpSerializer (Newtonsoft.Json) Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Registers the Newtonsoft.Json serializer. Useful for consuming messages from legacy producers that use Newtonsoft.Json-specific settings. ```APIDOC ## AddAmqpSerializer (Newtonsoft.Json) ### Description Registers the `NewtonsoftAmqpSerializer`. Useful when consuming messages from legacy producers that serialize with Newtonsoft.Json-specific settings (e.g., `TypeNameHandling`). ### Usage ```csharp // Package: Oragon.RabbitMQ.Serializer.NewtonsoftJson builder.Services.AddAmqpSerializer(options: new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None, NullValueHandling = NullValueHandling.Ignore, DateFormatHandling = DateFormatHandling.IsoDateFormat }); // Keyed variant for side-by-side use with System.Text.Json builder.Services.AddNewtonsoftAmqpSerializer(key: "legacy", options: new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }); ``` ``` -------------------------------- ### Bind Handler Parameters from AMQP Message Headers Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Use the [FromAmqpHeader] attribute to resolve string handler parameters from AMQP message headers by key. Only string parameter types are supported; other types will cause an InvalidOperationException at startup. ```csharp // Producer sets headers when publishing: // basicProperties.Headers["x-tenant-id"] = "acme"; // basicProperties.Headers["x-trace-id"] = "abc-123"; app.MapQueue("tenant-events", ([FromAmqpHeader("x-tenant-id")] string tenantId, [FromAmqpHeader("x-trace-id")] string traceId, [FromServices] TenantEventService svc, [FromBody] TenantEvent evt) => { Console.WriteLine($"Tenant={tenantId} Trace={traceId}"); return svc.HandleAsync(tenantId, evt); }); ``` -------------------------------- ### Run a Single Test Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/CLAUDE.md Executes a specific test using a filter based on its fully qualified name. ```bash dotnet test --filter "FullyQualifiedName~TestMethodName" ``` -------------------------------- ### Map Queue with Conditional Processing and Manual Ack/Nack Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Maps a queue to a handler that conditionally processes messages based on 'OrderService.CanProcess'. It returns an Ack for processed messages and a Nack with requeue for others. ```csharp app.MapQueue("orders", async ([FromServices] OrderService svc, OrderCreated msg) => { if (svc.CanProcess(msg)) { await svc.HandleAsync(msg); return AmqpResults.Ack(); } return AmqpResults.Nack(requeue: true); }); ``` -------------------------------- ### Replace Console.WriteLine with ILogger in Library Code Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md Replaces Console.WriteLine with ILogger for logging retry attempts in library code. Ensures consistent logging practices. ```csharp OnRetry = static args => { Console.WriteLine("OnRetry, Attempt: {0}", args.AttemptNumber); // Not ILogger return default; } ``` -------------------------------- ### Forward and Acknowledge RabbitMQ Message Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Use AmqpResults.ForwardAndAck(exchange, routingKey, mandatory, params T[]) to forward a message and acknowledge it simultaneously. ```csharp AmqpResults.ForwardAndAck(exchange, routingKey, mandatory, params T[]) ``` -------------------------------- ### Fix Silent Failures in WaitRabbitMQAsync Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md Corrects logic in WaitRabbitMQAsync to ensure connection closing and exception throwing occurs when expected. Addresses dead code related to checking connection status. ```csharp if (connection.IsOpen) { return; // Exits successfully } await channel.CloseAsync(...); // Dead code - connection was just created open await connection.CloseAsync(...); throw new InvalidOperationException("Connection is not open"); ``` -------------------------------- ### Compose Multiple RabbitMQ Results Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Use AmqpResults.Compose(params IAmqpResult[]) to combine multiple acknowledgment or action results into a single result. ```csharp AmqpResults.Compose(params IAmqpResult[]) ``` -------------------------------- ### Fix Exception Shadowing Bug in QueueConsumer Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md Corrects a bug where the wrong exception variable was used in the failure handler. Ensure 'ex' is used instead of 'exception'. ```csharp catch (Exception ex) { s_logQueueUnhandledException(this.logger, this.consumerDescriptor.QueueName, ex); result = this.consumerDescriptor.ResultForProcessFailure(context, exception); // BUG: uses 'exception' not 'ex' } ``` -------------------------------- ### Reply to RPC Caller in RabbitMQ Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Use AmqpResults.Reply(T) to send a reply to the caller in an RPC-style communication pattern. ```csharp AmqpResults.Reply(T) ``` -------------------------------- ### Dispatcher Error Handling Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md This snippet shows direct dependency on ConsumerDescriptor for error handling, leading to tight coupling. Consider using an interface for broader compatibility. ```csharp return this.consumerDescriptor.ResultForProcessFailure(context, exception); ``` -------------------------------- ### Access Full Message Context with IAmqpContext in C# Source: https://context7.com/luizcarlosfaria/oragon.rabbitmq/llms.txt Use IAmqpContext to access the complete delivery context within a handler for advanced scenarios like logging, manual republishing, correlation tracing, or custom result execution. This includes raw delivery arguments and AMQP properties. ```csharp app.MapQueue("audit-log", async (IAmqpContext ctx, [FromBody] AuditEntry entry) => { // Access raw delivery args ulong deliveryTag = ctx.Request.DeliveryTag; string exchange = ctx.Request.Exchange; string routingKey = ctx.Request.RoutingKey; // Access AMQP properties string msgId = ctx.Request.BasicProperties.MessageId; string corrId = ctx.Request.BasicProperties.CorrelationId; // Log a handler exception with full body content try { await ctx.ServiceProvider .GetRequiredService() .RecordAsync(entry, ctx.CancellationToken); return AmqpResults.Ack(); } catch (Exception ex) { ctx.LogException(ex); // logs type, queue name, raw body, exception return AmqpResults.Nack(requeue: false); } }); ``` -------------------------------- ### Flow Control and Acknowledgments Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Mechanisms for controlling message flow and acknowledgments within Oragon RabbitMQ handlers. ```APIDOC ## Flow Control By default, Oragon handles acknowledgments automatically: - **Success** → `BasicAck` - **Serialization failure** → `BasicReject` (no requeue) — use dead-lettering - **Processing failure** → `BasicNack` (no requeue) — use dead-lettering For explicit control, return an `IAmqpResult` from your handler: ### Result Types - **Basic**: - `AmqpResults.Ack()`: Acknowledge the message. - `AmqpResults.Nack(requeue)`: Negative acknowledge. - `AmqpResults.Reject(requeue)`: Reject the message. - **RPC**: - `AmqpResults.Reply(T)`: Reply to the caller. - `AmqpResults.ReplyAndAck(T)`: Reply and acknowledge. - **Routing**: - `AmqpResults.Forward(exchange, routingKey, mandatory, params T[])`: Forward to another exchange. - `AmqpResults.ForwardAndAck(exchange, routingKey, mandatory, params T[])`: Forward and acknowledge. - **Composition**: - `AmqpResults.Compose(params IAmqpResult[])`: Combine multiple results. ``` -------------------------------- ### Model Binding Attributes Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Attributes used for model binding in Oragon RabbitMQ handlers. ```APIDOC ## Model Binding ### Attributes - `[FromServices]`: Resolves from the DI container (supports keyed services). - `[FromBody]`: Resolves from the deserialized message body. - `[FromAmqpHeader("key")]`: Resolves from the AMQP message header by key. ``` -------------------------------- ### Update IAmqpArgumentBinder for Async Support Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md Recommends modifying IAmqpArgumentBinder to support asynchronous operations, enabling async argument resolution like database lookups or HTTP calls. ```csharp public interface IAmqpArgumentBinder { object GetValue(IAmqpContext context); // Synchronous } ``` -------------------------------- ### Introduce Generic Type Parameter for IResultHandler Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md Suggests replacing the 'object' parameter in IResultHandler.Handle with a generic type parameter for compile-time type safety and to avoid runtime type checking. ```csharp Task Handle(IAmqpContext context, object dispatchResult); ``` -------------------------------- ### Reply and Acknowledge RPC Caller in RabbitMQ Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Use AmqpResults.ReplyAndAck(T) to send a reply and acknowledge the message in an RPC-style communication. ```csharp AmqpResults.ReplyAndAck(T) ``` -------------------------------- ### UTF-8 Encoding Assumption Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md This code assumes UTF-8 encoding for message deserialization. It should respect the 'content_encoding' from BasicProperties for proper handling of different encodings. ```csharp var message = Encoding.UTF8.GetString(bytes); ``` -------------------------------- ### Forward RabbitMQ Message to Another Exchange Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/README.md Use AmqpResults.Forward(exchange, routingKey, mandatory, params T[]) to forward a message to a different exchange with specified routing. ```csharp AmqpResults.Forward(exchange, routingKey, mandatory, params T[]) ``` -------------------------------- ### Add Validation for Reflection Property Access Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md Ensures the 'Result' property exists before attempting to access it via reflection. Prevents runtime NullReferenceException if the property is missing. ```csharp this.resultProperty = type.GetProperty("Result"); ``` -------------------------------- ### Remove Redundant Null Checks Source: https://github.com/luizcarlosfaria/oragon.rabbitmq/blob/master/ARCHITECTURE_ANALYSIS.md Removes unnecessary null checks for 'objectsToForward' as the constructor already validates this parameter. Avoids redundant runtime checks. ```csharp ArgumentNullException.ThrowIfNull(objectsToForward, nameof(objectsToForward)); ``` ```csharp if (this.objectsToForward != null && this.objectsToForward.Length != 0) ```