Skip to content

Commit 71634d4

Browse files
committed
Add Dependency Injection documentation and examples for ArtemisNetClient
Co-authored-by: Copilot <copilot@github.com> # Conflicts: # src/ArtemisNetClient.Extensions.DependencyInjection/ArtemisNetClient.Extensions.DependencyInjection.csproj
1 parent 8c6a0cc commit 71634d4

13 files changed

Lines changed: 428 additions & 29 deletions

File tree

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,24 @@ var consumer = await connection.CreateConsumerAsync("a1", RoutingType.Anycast);
6363
var message = await consumer.ReceiveAsync();
6464
```
6565

66+
If your application already uses `Microsoft.Extensions.DependencyInjection` or ASP.NET Core, start with the dependency injection package instead of wiring the core client manually:
67+
68+
```
69+
dotnet add package ArtemisNetClient.Extensions.DependencyInjection
70+
dotnet add package ArtemisNetClient.Extensions.Hosting
71+
```
72+
73+
See the [Dependency Injection guide](docs/dependency-injection.md) for the hosted setup, typed producers, consumers, and request-reply registration.
74+
6675
## Documentation
6776

6877
Detailed documentation is available on [the project website](https://havret.github.io/dotnet-activemq-artemis-client/).
6978

79+
- [Getting Started](docs/getting-started.md)
80+
- [Dependency Injection](docs/dependency-injection.md)
81+
- [Request Reply](docs/request-reply.md)
82+
- [Testing](docs/testing.md)
83+
7084
## Online resources
7185

7286
- [Messaging with ActiveMQ Artemis and ASP.NET Core](https://havret.io/activemq-artemis-net-core) (January 31, 2021)
@@ -133,6 +147,17 @@ The following table shows what features are currently supported.
133147
## Extensions
134148
There are several extensions available that make integration of .NET Client for ActiveMQ Artemis with ASP.NET Core based projects seamless.
135149

150+
- `ArtemisNetClient.Extensions.DependencyInjection`: registers connections, consumers, producers, request-reply clients, and topology configuration in `IServiceCollection`. Start here for ASP.NET Core. See [Dependency Injection](docs/dependency-injection.md).
151+
- `ArtemisNetClient.Extensions.Hosting`: starts Artemis registrations through `IHostedService`. Used together with the DI package in hosted apps.
152+
- `ArtemisNetClient.Extensions.HealthChecks`: exposes Artemis connectivity through ASP.NET Core health checks.
153+
- `ArtemisNetClient.Extensions.LeaderElection`: adds distributed leader election on top of the DI and hosting integration.
154+
155+
Examples:
156+
157+
- [Minimal DI sample](samples/Testing/Application/Program.cs)
158+
- [ASP.NET Core sample](samples/ArtemisNetClient.Examples.AspNetCore/Startup.cs)
159+
- [Leader election sample](samples/ArtemisNetClient.Examples.LeaderElection/Startup.cs)
160+
136161
## ⚠️ Breaking Change in 3.0.0
137162

138163
Version **3.0.0** introduces a **breaking change** to the `IConsumer` API.

docs/dependency-injection.md

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
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)

docs/getting-started.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ sidebar_label: Getting Started
1414
dotnet add package ArtemisNetClient
1515
```
1616

17+
If you are building an ASP.NET Core application or otherwise using `Microsoft.Extensions.DependencyInjection`, start with the [Dependency Injection](dependency-injection.md) guide instead. It covers `AddActiveMq`, hosted startup, typed producers, and consumer registration.
18+
1719
## API overview
1820

1921
The API interfaces and classes are defined in the `ActiveMQ.Artemis.Client` namespace:

docs/messaging-model.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,7 @@ When the address was created with *Anycast* routing type all messages send to th
1717
## Further Reading
1818
The Apache ActiveMQ Artemis address model is described in detail (with examples using ArtemisNetClient) in [this](https://havret.io/activemq-artemis-address-model) article.
1919

20+
If you use the dependency injection package, the same address, queue, and routing concepts show up in `AddConsumer`, `AddProducer`, and topology declaration APIs. See [Dependency Injection](dependency-injection.md) for the ASP.NET Core registration flow.
21+
2022
### Footnotes:
2123
[^anycast-message-distribution]: Messages will be distributed using Round-robin distribution algorithm.

0 commit comments

Comments
 (0)