Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 260 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,125 @@ services.AddAzureStorageQueueClient(x =>
});
```

### Add typed clients (IHttpClientFactory pattern)

The library supports strongly-typed clients similar to the IHttpClientFactory pattern in ASP.NET Core. There are two main approaches:

#### Option 1: Direct Configuration (Recommended)
Register a client type with configuration in one step, just like `AddHttpClient<T>()`:

```csharp
// Register MyOrderClient with queue configuration
services.AddQueueClient<MyOrderClient>(settings =>
{
settings.ConnectionString = "[your_connection_string]";
settings.QueueName = "orders";
settings.CreateIfNotExists = true;
});

// Inject the client directly
public class OrderService
{
private readonly MyOrderClient _orderClient;

public OrderService(MyOrderClient orderClient) // Direct injection, just like HttpClient pattern
{
_orderClient = orderClient;
}

public async Task ProcessOrderAsync(Order order)
{
await _orderClient.SendOrderAsync(order);
}
}

// Define the client class
public class MyOrderClient
{
private readonly AzureStorageQueueClient _queueClient;

public MyOrderClient(AzureStorageQueueClient queueClient) // Configured client injected automatically
{
_queueClient = queueClient;
}

public async Task SendOrderAsync(Order order)
{
await _queueClient.SendMessageAsync(order);
}

public async Task SendPriorityOrderAsync(Order order)
{
await _queueClient.SendMessageAsync(order, TimeSpan.Zero); // Immediate visibility
}
}
```

#### Option 2: Message-Type-Centric Clients
Register typed clients for specific message types:

```csharp
// Register a typed client for a specific message type using the default queue client
services.AddAzureStorageQueueClient(x =>
x.AddDefaultClient(y => Configuration.Bind(nameof(QueueClientSettings), y)));
services.AddTypedQueueClient<MyMessage>();

// Register a typed client for a specific message type using a named queue client
services.AddAzureStorageQueueClient(x =>
x.AddClient("orders", y => Configuration.Bind(nameof(OrderQueueSettings), y)));
services.AddTypedQueueClient<OrderMessage>("orders");
```

This allows you to inject `ITypedQueueClient<TMessage>` directly instead of using the factory pattern:

```csharp
public class OrderService
{
private readonly ITypedQueueClient<OrderMessage> _queueClient;

public OrderService(ITypedQueueClient<OrderMessage> queueClient)
{
_queueClient = queueClient;
}

public async Task ProcessOrderAsync(OrderMessage order)
{
await _queueClient.SendMessageAsync(order);
}
}
```

#### Multiple Clients
Register multiple client types with different configurations:

```csharp
// Register different client types
services.AddQueueClient<OrderClient>(settings =>
{
settings.ConnectionString = "[your_connection_string]";
settings.QueueName = "orders";
});

services.AddQueueClient<NotificationClient>(settings =>
{
settings.ConnectionString = "[your_connection_string]";
settings.QueueName = "notifications";
});

// Use in services
public class ECommerceService
{
private readonly OrderClient _orderClient;
private readonly NotificationClient _notificationClient;

public ECommerceService(OrderClient orderClient, NotificationClient notificationClient)
{
_orderClient = orderClient;
_notificationClient = notificationClient;
}
}
```

## Using the IQueueClientFactory

### Example 1: Get a default queue client
Expand Down Expand Up @@ -146,10 +265,96 @@ public class MyClass
}
```

## Pattern Comparison

The library supports both traditional factory pattern and modern IHttpClientFactory-style pattern:

### Modern Pattern (Recommended)
```csharp
// Registration
services.AddQueueClient<OrderClient>(settings =>
{
settings.ConnectionString = "[your_connection_string]";
settings.QueueName = "orders";
});

// Usage - direct injection like IHttpClientFactory
public class OrderService
{
public OrderService(OrderClient client) { } // Clean, typed injection
}
```

### Traditional Factory Pattern
```csharp
// Registration
services.AddAzureStorageQueueClient(x =>
x.AddClient("orders", y => { /* configure */ }));

// Usage - factory pattern
public class OrderService
{
public OrderService(IQueueClientFactory factory)
{
_client = factory.GetQueueClient("orders"); // String-based lookup
}
}
```

The modern pattern provides:
- **Type Safety**: Compile-time checking instead of runtime string lookups
- **Cleaner DI**: Direct injection like HttpClient pattern
- **Better Tooling**: IntelliSense and refactoring support
- **Familiar Pattern**: Consistent with IHttpClientFactory that developers already know

## Sending messages to an Azure storage account queue

The following example shows the .NET Worker Service template where the class uses the `IHostedService` interface to send a message every five seconds.

### Using Custom Client Types (Recommended)

```csharp
// Registration
services.AddQueueClient<MessageSender>(settings =>
{
settings.ConnectionString = "[your_connection_string]";
settings.QueueName = "messages";
});

// Client class
public class MessageSender
{
private readonly AzureStorageQueueClient _queueClient;

public MessageSender(AzureStorageQueueClient queueClient) => _queueClient = queueClient;

public async Task SendAsync<T>(T message, CancellationToken cancellationToken = default)
{
await _queueClient.SendMessageAsync(message, cancellationToken);
}
}

// Usage
public class Sender : IHostedService
{
private readonly MessageSender _messageSender;

public Sender(MessageSender messageSender) => _messageSender = messageSender;

public async Task StartAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var myMessage = new MyMessage("Test");
await _messageSender.SendAsync(myMessage, cancellationToken);
await Task.Delay(5000);
}
}
}
```

### Using the IQueueClientFactory

1. Inject the `IQueueClientFactory` interface and use as follows:

```csharp
Expand All @@ -171,10 +376,35 @@ The following example shows the .NET Worker Service template where the class use
}
```

### Using Typed Clients

Alternatively, you can use typed clients for a cleaner dependency injection experience:

```csharp
public class Sender : IHostedService
{
private readonly ITypedQueueClient<MyMessage> _queueClient;

public Sender(ITypedQueueClient<MyMessage> queueClient) => _queueClient = queueClient;

public async Task StartAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var myMessage = new MyMessage("Test");
await _queueClient.SendMessageAsync(myMessage, cancellationToken);
await Task.Delay(5000);
}
}
}
```

## Receiving and handling messages from an Azure storage account queue

The following example shows the .NET Worker Service template where the class uses the `IHostedService` interface to run a particular code block repeatedly. The application will receive the payload from the queue repeatedly.

### Using the IQueueClientFactory

1. Inject the `IQueueClientFactory` interface and use as follows:

```csharp
Expand Down Expand Up @@ -204,6 +434,36 @@ The following example shows the .NET Worker Service template where the class use
}
```

### Using Typed Clients

Alternatively, you can use typed clients for a cleaner dependency injection experience:

```csharp
public class MySubscriber : IHostedService
{
private readonly ITypedQueueClient<MyMessage> _queueClient;
private readonly IMyMessageHandler _myMessageHandler;

public MySubscriber(ITypedQueueClient<MyMessage> queueClient, IMyMessageHandler myMessageHandler)
{
_queueClient = queueClient;
_myMessageHandler = myMessageHandler;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await _queueClient.ReceiveMessagesAsync(
message => _myMessageHandler.HandleAsync(message),
exception => _myMessageHandler.HandleExceptionAsync(exception),
cancellationToken);
await Task.Delay(1000);
}
}
}
```

### Handling multiple messages

The library allows you to pull multiple messages by specifying the `maxMessage` count as an integer in the `ReceiveMessagesAsync<T>()` method. These are sent to the handler as individual messages but pulled from the queue as a batch the consuming application would hold a lock on for the default duration used in the Azure Storage Queue library.
Expand Down
10 changes: 0 additions & 10 deletions src/AzureStorage.QueueService/IQueueClientBuilder.cs

This file was deleted.

64 changes: 64 additions & 0 deletions src/AzureStorage.QueueService/ITypedQueueClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Azure.Storage.Queues.Models;

namespace AzureStorage.QueueService;

/// <summary>
/// Represents a typed queue client for a specific message type.
/// This interface provides strongly-typed operations for queue interactions.
/// </summary>
/// <typeparam name="TMessage">The type of message this client handles</typeparam>
public interface ITypedQueueClient<TMessage> where TMessage : class
{
/// <summary>
/// Creates the queue if it doesn't exist.
/// </summary>
/// <param name="metadata">Optional metadata for the queue</param>
/// <param name="cancellationToken">Cancellation token</param>
ValueTask CreateQueueIfNotExistsAsync(IDictionary<string, string>? metadata = null, CancellationToken cancellationToken = default);

/// <summary>
/// Clears all messages from the queue.
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
ValueTask ClearMessagesAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Peeks at messages without removing them from the queue.
/// </summary>
/// <param name="numMessages">Number of messages to peek at</param>
/// <param name="cancellationToken">Cancellation token</param>
ValueTask<IEnumerable<TMessage>> PeekMessagesAsync(int numMessages, CancellationToken cancellationToken = default);

/// <summary>
/// Peeks at messages without removing them from the queue (synchronous).
/// </summary>
/// <param name="numMessages">Number of messages to peek at</param>
/// <param name="cancellationToken">Cancellation token</param>
IEnumerable<TMessage> PeekMessages(int numMessages, CancellationToken cancellationToken = default);

/// <summary>
/// Receives and processes messages from the queue.
/// </summary>
/// <param name="handleMessage">Delegate to handle each received message</param>
/// <param name="handleException">Delegate to handle exceptions during processing</param>
/// <param name="numMessages">Number of messages to receive</param>
/// <param name="cancellationToken">Cancellation token</param>
ValueTask ReceiveMessagesAsync(
Func<TMessage?, IDictionary<string, string>?, ValueTask> handleMessage,
Func<Exception, IDictionary<string, string>?, ValueTask> handleException,
int numMessages = 1,
CancellationToken cancellationToken = default);

/// <summary>
/// Sends a message to the queue.
/// </summary>
/// <param name="message">The message to send</param>
/// <param name="visibilityTimeout">Optional visibility timeout</param>
/// <param name="timeToLive">Optional time to live</param>
/// <param name="cancellationToken">Cancellation token</param>
ValueTask<SendReceipt> SendMessageAsync(
TMessage message,
TimeSpan? visibilityTimeout = null,
TimeSpan? timeToLive = null,
CancellationToken cancellationToken = default);
}
Loading
Loading