|
| 1 | +--- |
| 2 | +id: dependency-injection |
| 3 | +title: Dependency Injection |
| 4 | +sidebar_label: Dependency Injection |
| 5 | +--- |
| 6 | + |
| 7 | +`ArtemisNetClient.Extensions.DependencyInjection` integrates ArtemisNetClient with `Microsoft.Extensions.DependencyInjection`. For ASP.NET Core applications, it is the easiest way to register long-lived connections, consumers, producers, and request-reply clients. |
| 8 | + |
| 9 | +## Installation |
| 10 | + |
| 11 | +For DI-only registration: |
| 12 | + |
| 13 | +```bash |
| 14 | +dotnet add package ArtemisNetClient.Extensions.DependencyInjection |
| 15 | +``` |
| 16 | + |
| 17 | +For ASP.NET Core or generic hosted applications, add the hosting package as well: |
| 18 | + |
| 19 | +```bash |
| 20 | +dotnet add package ArtemisNetClient.Extensions.Hosting |
| 21 | +``` |
| 22 | + |
| 23 | +The extension methods live in these namespaces: |
| 24 | + |
| 25 | +```csharp |
| 26 | +using ActiveMQ.Artemis.Client; |
| 27 | +using ActiveMQ.Artemis.Client.Extensions.DependencyInjection; |
| 28 | +using ActiveMQ.Artemis.Client.Extensions.Hosting; |
| 29 | +``` |
| 30 | + |
| 31 | +## Minimal ASP.NET Core setup |
| 32 | + |
| 33 | +`AddActiveMq` registers the connection and the ArtemisNetClient services in the DI container. `AddActiveMqHostedService` starts those registrations when the host starts. |
| 34 | + |
| 35 | +```csharp |
| 36 | +var builder = WebApplication.CreateBuilder(args); |
| 37 | + |
| 38 | +builder.Services.AddActiveMqHostedService(); |
| 39 | + |
| 40 | +var endpoint = Endpoint.Create( |
| 41 | + host: "localhost", |
| 42 | + port: 5672, |
| 43 | + user: "artemis", |
| 44 | + password: "artemis"); |
| 45 | + |
| 46 | +var activeMq = builder.Services.AddActiveMq( |
| 47 | + name: "my-artemis", |
| 48 | + endpoints: new[] { endpoint }); |
| 49 | + |
| 50 | +activeMq.AddConsumer( |
| 51 | + address: "orders.incoming", |
| 52 | + routingType: RoutingType.Anycast, |
| 53 | + handler: async (message, consumer, serviceProvider, cancellationToken) => |
| 54 | + { |
| 55 | + var publisher = serviceProvider.GetRequiredService<OrderEventsProducer>(); |
| 56 | + var orderId = message.GetBody<string>(); |
| 57 | + |
| 58 | + await publisher.PublishAccepted(orderId, cancellationToken); |
| 59 | + await consumer.AcceptAsync(message); |
| 60 | + }); |
| 61 | + |
| 62 | +activeMq.AddProducer<OrderEventsProducer>("orders.events", RoutingType.Multicast); |
| 63 | + |
| 64 | +var app = builder.Build(); |
| 65 | +app.Run(); |
| 66 | + |
| 67 | +public sealed class OrderEventsProducer |
| 68 | +{ |
| 69 | + private readonly IProducer _producer; |
| 70 | + |
| 71 | + public OrderEventsProducer(IProducer producer) => _producer = producer; |
| 72 | + |
| 73 | + public Task PublishAccepted(string orderId, CancellationToken cancellationToken) |
| 74 | + { |
| 75 | + return _producer.SendAsync(new Message($"accepted:{orderId}"), cancellationToken); |
| 76 | + } |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +:::important |
| 81 | + |
| 82 | +If you register ArtemisNetClient services with `AddActiveMq` but do not start them with `AddActiveMqHostedService`, your consumers and typed producers will not be started automatically. |
| 83 | + |
| 84 | +This split is intentional. Hosted applications usually want `AddActiveMqHostedService`, while advanced scenarios can control startup manually through `IActiveMqClient`. |
| 85 | + |
| 86 | +::: |
| 87 | + |
| 88 | +## How the builder works |
| 89 | + |
| 90 | +`AddActiveMq` returns an `IActiveMqBuilder`. Use that builder to compose the messaging setup for one logical connection: |
| 91 | + |
| 92 | +- `ConfigureConnectionFactory` customizes `ConnectionFactory`. |
| 93 | +- `ConfigureConnection` attaches runtime hooks to the created `IConnection`. |
| 94 | +- `AddConsumer` registers message handlers. |
| 95 | +- `AddProducer` registers typed producers backed by `IProducer`. |
| 96 | +- `AddAnonymousProducer` registers typed producers backed by `IAnonymousProducer`. |
| 97 | +- `AddRequestReplyClient` registers typed request-reply clients. |
| 98 | +- `EnableQueueDeclaration` and `EnableAddressDeclaration` enable topology declaration on startup. |
| 99 | + |
| 100 | +The `name` parameter identifies the logical connection registration. Use different names when you need multiple Artemis connections in the same application. |
| 101 | + |
| 102 | +## Consumers |
| 103 | + |
| 104 | +The simplest consumer attaches to an address and routing type: |
| 105 | + |
| 106 | +```csharp |
| 107 | +activeMq.AddConsumer( |
| 108 | + address: "orders.incoming", |
| 109 | + routingType: RoutingType.Anycast, |
| 110 | + handler: async (message, consumer, serviceProvider, cancellationToken) => |
| 111 | + { |
| 112 | + var logger = serviceProvider.GetRequiredService<ILogger<Program>>(); |
| 113 | + logger.LogInformation("Received order {OrderId}", message.GetBody<string>()); |
| 114 | + |
| 115 | + await consumer.AcceptAsync(message); |
| 116 | + }); |
| 117 | +``` |
| 118 | + |
| 119 | +The handler signature is: |
| 120 | + |
| 121 | +```csharp |
| 122 | +Func<Message, IConsumer, IServiceProvider, CancellationToken, Task> |
| 123 | +``` |
| 124 | + |
| 125 | +That gives you direct access to the incoming `Message`, the active `IConsumer`, the current `IServiceProvider`, and the shutdown `CancellationToken`. |
| 126 | + |
| 127 | +Use the other overloads when you need more control: |
| 128 | + |
| 129 | +- Add `ConsumerOptions` to configure credit, concurrent consumers, filters, or `NoLocal` behavior. |
| 130 | +- Add a queue name when the consumer should bind to a specific queue. |
| 131 | +- Add `QueueOptions` when queue declaration is enabled and queue settings need to be declared on startup. |
| 132 | +- Use `AddSharedConsumer` or `AddSharedDurableConsumer` for shared subscription patterns. |
| 133 | + |
| 134 | +## Typed producers |
| 135 | + |
| 136 | +Typed producers keep your application code away from raw Artemis primitives while still reusing a long-lived underlying `IProducer`. |
| 137 | + |
| 138 | +```csharp |
| 139 | +activeMq.AddProducer<OrderEventsProducer>("orders.events", RoutingType.Multicast); |
| 140 | + |
| 141 | +public sealed class OrderEventsProducer |
| 142 | +{ |
| 143 | + private readonly IProducer _producer; |
| 144 | + |
| 145 | + public OrderEventsProducer(IProducer producer) => _producer = producer; |
| 146 | + |
| 147 | + public Task PublishAccepted(string orderId, CancellationToken cancellationToken) |
| 148 | + { |
| 149 | + return _producer.SendAsync(new Message($"accepted:{orderId}"), cancellationToken); |
| 150 | + } |
| 151 | +} |
| 152 | +``` |
| 153 | + |
| 154 | +`AddProducer<TProducer>` registers `TProducer` as a typed wrapper. By default the wrapper type is transient, but you can choose `Singleton` or `Scoped` when that better matches your application. |
| 155 | + |
| 156 | +Each typed wrapper type must be unique per registration. If you need two producers with the same public API, create two distinct wrapper classes. |
| 157 | + |
| 158 | +## Anonymous producers |
| 159 | + |
| 160 | +Use `AddAnonymousProducer<TProducer>` when the producer must publish to multiple addresses dynamically. |
| 161 | + |
| 162 | +```csharp |
| 163 | +activeMq.AddAnonymousProducer<ReplyProducer>(); |
| 164 | + |
| 165 | +public sealed class ReplyProducer |
| 166 | +{ |
| 167 | + private readonly IAnonymousProducer _producer; |
| 168 | + |
| 169 | + public ReplyProducer(IAnonymousProducer producer) => _producer = producer; |
| 170 | + |
| 171 | + public Task SendAsync(string address, Message message, CancellationToken cancellationToken) |
| 172 | + { |
| 173 | + return _producer.SendAsync(address, message, cancellationToken); |
| 174 | + } |
| 175 | +} |
| 176 | +``` |
| 177 | + |
| 178 | +## Request-reply |
| 179 | + |
| 180 | +`AddRequestReplyClient<TClient>` registers a typed wrapper over `IRequestReplyClient`. |
| 181 | + |
| 182 | +```csharp |
| 183 | +activeMq.AddRequestReplyClient<BookstoreRequestClient>(); |
| 184 | + |
| 185 | +public sealed class BookstoreRequestClient |
| 186 | +{ |
| 187 | + private readonly IRequestReplyClient _client; |
| 188 | + |
| 189 | + public BookstoreRequestClient(IRequestReplyClient client) => _client = client; |
| 190 | + |
| 191 | + public Task<Message> SendAsync(string address, string payload, CancellationToken cancellationToken) |
| 192 | + { |
| 193 | + return _client.SendAsync( |
| 194 | + address, |
| 195 | + RoutingType.Anycast, |
| 196 | + new Message(payload), |
| 197 | + cancellationToken); |
| 198 | + } |
| 199 | +} |
| 200 | +``` |
| 201 | + |
| 202 | +On the response side, handle the request with a consumer and reply to `message.ReplyTo`. The response must preserve `message.CorrelationId`. |
| 203 | + |
| 204 | +```csharp |
| 205 | +activeMq.AddAnonymousProducer<ReplyProducer>(); |
| 206 | + |
| 207 | +activeMq.AddConsumer( |
| 208 | + address: "bookstore.requests", |
| 209 | + routingType: RoutingType.Anycast, |
| 210 | + handler: async (message, consumer, serviceProvider, cancellationToken) => |
| 211 | + { |
| 212 | + var replyProducer = serviceProvider.GetRequiredService<ReplyProducer>(); |
| 213 | + |
| 214 | + await replyProducer.SendAsync( |
| 215 | + message.ReplyTo, |
| 216 | + new Message("ok") |
| 217 | + { |
| 218 | + CorrelationId = message.CorrelationId |
| 219 | + }, |
| 220 | + cancellationToken); |
| 221 | + |
| 222 | + await consumer.AcceptAsync(message); |
| 223 | + }); |
| 224 | +``` |
| 225 | + |
| 226 | +## Configuring the connection |
| 227 | + |
| 228 | +Use `ConfigureConnectionFactory` for transport, logging, recovery, or message ID settings. |
| 229 | + |
| 230 | +```csharp |
| 231 | +activeMq.ConfigureConnectionFactory((serviceProvider, factory) => |
| 232 | +{ |
| 233 | + factory.LoggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>(); |
| 234 | + factory.AutomaticRecoveryEnabled = true; |
| 235 | +}); |
| 236 | +``` |
| 237 | + |
| 238 | +Use `ConfigureConnection` for events on the created `IConnection`: |
| 239 | + |
| 240 | +```csharp |
| 241 | +activeMq.ConfigureConnection((_, connection) => |
| 242 | +{ |
| 243 | + connection.ConnectionClosed += (_, args) => |
| 244 | + { |
| 245 | + Console.WriteLine($"Connection closed. Error={args.Error}"); |
| 246 | + }; |
| 247 | +}); |
| 248 | +``` |
| 249 | + |
| 250 | +## Declaring topology on startup |
| 251 | + |
| 252 | +If the application should declare queues or addresses when it starts, enable topology declaration: |
| 253 | + |
| 254 | +```csharp |
| 255 | +activeMq |
| 256 | + .AddConsumer( |
| 257 | + address: "orders.incoming", |
| 258 | + routingType: RoutingType.Anycast, |
| 259 | + queue: "orders-service", |
| 260 | + handler: async (message, consumer, serviceProvider, cancellationToken) => |
| 261 | + { |
| 262 | + await consumer.AcceptAsync(message); |
| 263 | + }) |
| 264 | + .EnableQueueDeclaration() |
| 265 | + .EnableAddressDeclaration(); |
| 266 | +``` |
| 267 | + |
| 268 | +`QueueOptions` and `ConfigureTopology` let you refine the declared topology when startup needs to create or update broker objects. |
| 269 | + |
| 270 | +## Multiple Artemis connections |
| 271 | + |
| 272 | +Each `AddActiveMq(name, endpoints)` call registers one logical connection. Use different names when your application needs separate clusters or credentials. |
| 273 | + |
| 274 | +Keep the registrations for one connection on the returned builder so the consumers, producers, topology configuration, and observers all stay attached to the intended connection. |
| 275 | + |
| 276 | +## Manual startup and shutdown |
| 277 | + |
| 278 | +Hosted applications typically use `AddActiveMqHostedService`, but the DI package also registers `IActiveMqClient` for manual lifecycle control. |
| 279 | + |
| 280 | +That is useful when you need to start messaging later in the application lifecycle or coordinate startup with other infrastructure. |
| 281 | + |
| 282 | +## Related examples |
| 283 | + |
| 284 | +- [Minimal DI sample](https://github.com/Havret/dotnet-activemq-artemis-client/blob/master/samples/Testing/Application/Program.cs) |
| 285 | +- [ASP.NET Core sample](https://github.com/Havret/dotnet-activemq-artemis-client/blob/master/samples/ArtemisNetClient.Examples.AspNetCore/Startup.cs) |
| 286 | +- [Request-reply integration tests](https://github.com/Havret/dotnet-activemq-artemis-client/blob/master/test/ArtemisNetClient.Extensions.IntegrationTests/RequestReplyClientSpec.cs) |
0 commit comments