From 4ba0b56f3af74e027bde29133bb561e0bd5e17f1 Mon Sep 17 00:00:00 2001 From: Jesse Squire Date: Wed, 26 Jul 2023 17:36:48 -0400 Subject: [PATCH] [Service Bus] ReadMe DI Snippets The focus of these changes is to convert the dependency injection examples in the README to be snippet-driven. In addition, an example for registering SubClients was also added, as this has been a frequent inquiry. --- eng/Packages.Data.props | 12 +- .../Azure.Messaging.ServiceBus/README.md | 100 +++++++++++----- .../Azure.Messaging.ServiceBus.Tests.csproj | 5 +- .../DependencyInjectionSnippets.cs | 112 ++++++++++++++++++ .../tests/Samples/Readme.cs | 5 - 5 files changed, 193 insertions(+), 41 deletions(-) create mode 100644 sdk/servicebus/Azure.Messaging.ServiceBus/tests/Compatibility/DependencyInjectionSnippets.cs diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index d3fa17f513e7..4e5160a9d8aa 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -198,16 +198,16 @@ - - + + - + - + @@ -249,13 +249,13 @@ - + - + diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/README.md b/sdk/servicebus/Azure.Messaging.ServiceBus/README.md index 08187fcf0d75..b7a21f99badb 100755 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/README.md +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/README.md @@ -401,6 +401,13 @@ string fullyQualifiedNamespace = "yournamespace.servicebus.windows.net"; await using var client = new ServiceBusClient(fullyQualifiedNamespace, new DefaultAzureCredential()); ``` +### Working with Sessions + +[Sessions](https://docs.microsoft.com/azure/service-bus-messaging/message-sessions) provide a mechanism for grouping related messages. In order to use sessions, you need to be working with a session-enabled entity. + +- [Sending and receiving session messages](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/servicebus/Azure.Messaging.ServiceBus/samples/Sample03_SendReceiveSessions.md) +- [Using the session processor](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/servicebus/Azure.Messaging.ServiceBus/samples/Sample05_SessionProcessor.md) + ### Registering with ASP.NET Core dependency injection To inject `ServiceBusClient` as a dependency in an ASP.NET Core app, install the Azure client library integration for ASP.NET Core package. @@ -409,60 +416,95 @@ To inject `ServiceBusClient` as a dependency in an ASP.NET Core app, install the dotnet add package Microsoft.Extensions.Azure ``` -Then register the client in the `Startup.ConfigureServices` method: +Then register the client where your services are configured. For ASP.NET Core applications, this is often directly in `Program.cs` or the `StartupConfigureServices` method: -```csharp +```C# Snippet:DependencyInjectionRegisterClient public void ConfigureServices(IServiceCollection services) { services.AddAzureClients(builder => { - builder.AddServiceBusClient(Configuration.GetConnectionString("ServiceBus")); + builder.AddServiceBusClient("<< SERVICE BUS CONNECTION STRING >>"); }); - services.AddControllers(); + // Register other services, controllers, and other infrastructure. } ``` -To use the preceding code, add this to the configuration for your application: +For applications that prefer using a shared `Azure.Identity` credential for their clients, registration looks slightly different: -```json -{ - "ConnectionStrings": { - "ServiceBus": "" - } -} +```C# Snippet:DependencyInjectionRegisterClientWithIdentity +public void ConfigureServices(IServiceCollection services) + { + services.AddAzureClients(builder => + { + // This will register the ServiceBusClient using an Azure Identity credential. + builder.AddServiceBusClientWithNamespace("<< YOUR NAMESPACE >>.servicebus.windows.net"); + + // By default, DefaultAzureCredential is used, which is likely desired for most + // scenarios. If you need to restrict to a specific credential instance, you could + // register that instance as the default credential instead. + builder.UseCredential(new ManagedIdentityCredential()); + }); + + // Register other services, controllers, and other infrastructure. + } ``` -For applications that prefer using a shared `Azure.Identity` credential for their clients, registration looks slightly different: +It is also possible to register sub-clients, such as `ServiceBusSender` and `ServiceBusReceiver` with DI using the registered `ServiceBusClient` instance. For example, to register a sender for each queue that belongs to the namespace: -```csharp -var fullyQualifiedNamespace = "yournamespace.servicebus.windows.net"; - -public void ConfigureServices(IServiceCollection services) +```C# Snippet:DependencyInjectionRegisterSubClients +public async Task ConfigureServicesAsync(IServiceCollection services) { - services.AddAzureClients(builder => + // Query the available queues for the Service Bus namespace. + var adminClient = new ServiceBusAdministrationClient("<< SERVICE BUS CONNECTION STRING >>"); + var queueNames = new List(); + + // Because the result is async, they need to be captured to a standard list to avoid async + // calls when registering. Failure to do so results in an error with the services collection. + await foreach (var queue in adminClient.GetQueuesAsync()) { - // This will register the ServiceBusClient using the default credential. - builder.AddServiceBusClientWithNamespace(fullyQualifiedNamespace); + queueNames.Add(queue.Name); + } - // By default, DefaultAzureCredential is used, which is likely desired for most - // scenarios. If you need to restrict to a specific credential instance, you could - // register that instance as the default credential instead. - builder.UseCredential(new ManagedIdentityCredential()); + // After registering the ServiceBusClient, register a named factory for each + // queue. This allows them to be lazily created and managed as singleton instances. + + services.AddAzureClients(builder => + { + builder.AddServiceBusClient("<< SERVICE BUS CONNECTION STRING >>"); + + foreach (var queueName in queueNames) + { + builder.AddClient((_, _, provider) => + provider + .GetService() + .CreateSender(queueName) + ) + .WithName(queueName); + } }); - services.AddControllers(); + // Register other services, controllers, and other infrastructure. } ``` -For more details, see [Dependency injection with the Azure SDK for .NET](https://docs.microsoft.com/dotnet/azure/sdk/dependency-injection). +Because the senders are named for their associated queue, when injecting, you don't bind to them directly. Instead, you'll bind to a factory that can be used to retrieve the named sender: -### Working with Sessions +```C# Snippet:DependencyInjectionBindToNamedSubClients +public class ServiceBusSendingController : ControllerBase +{ + private readonly ServiceBusSender _sender; -[Sessions](https://docs.microsoft.com/azure/service-bus-messaging/message-sessions) provide a mechanism for grouping related messages. In order to use sessions, you need to be working with a session-enabled entity. + public ServiceBusSendingController(IAzureClientFactory serviceBusSenderFactory) + { + // Though the method is called "CreateClient", the factory will manage the sender as a + // singleton, creating a new instance only on the first use. + _sender = serviceBusSenderFactory.CreateClient("<< QUEUE NAME >>"); + } +} +``` -- [Sending and receiving session messages](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/servicebus/Azure.Messaging.ServiceBus/samples/Sample03_SendReceiveSessions.md) -- [Using the session processor](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/servicebus/Azure.Messaging.ServiceBus/samples/Sample05_SessionProcessor.md) +For more details and examples, see [Dependency injection with the Azure SDK for .NET](https://learn.microsoft.com/dotnet/azure/sdk/dependency-injection). ## Troubleshooting diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Azure.Messaging.ServiceBus.Tests.csproj b/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Azure.Messaging.ServiceBus.Tests.csproj index a0022ebe0131..4be46a41c685 100755 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Azure.Messaging.ServiceBus.Tests.csproj +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Azure.Messaging.ServiceBus.Tests.csproj @@ -18,6 +18,9 @@ + + + @@ -28,7 +31,7 @@ - + diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Compatibility/DependencyInjectionSnippets.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Compatibility/DependencyInjectionSnippets.cs new file mode 100644 index 000000000000..2e9a88f1d2f7 --- /dev/null +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Compatibility/DependencyInjectionSnippets.cs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Azure.Identity; +using Azure.Messaging.ServiceBus.Administration; +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; + +namespace Azure.Messaging.ServiceBus.Tests +{ + [TestFixture] + public class DependencyInjectionSnippets + { + public class ClientRegistrationConnectionString + { + #region Snippet:DependencyInjectionRegisterClient + public void ConfigureServices(IServiceCollection services) + { + services.AddAzureClients(builder => + { + builder.AddServiceBusClient("<< SERVICE BUS CONNECTION STRING >>"); + }); + + // Register other services, controllers, and other infrastructure. + } + #endregion + } + + public class ClientRegistrationIdentity + { + #region Snippet:DependencyInjectionRegisterClientWithIdentity + public void ConfigureServices(IServiceCollection services) + { + services.AddAzureClients(builder => + { + // This will register the ServiceBusClient using an Azure Identity credential. + builder.AddServiceBusClientWithNamespace("<< YOUR NAMESPACE >>.servicebus.windows.net"); + + // By default, DefaultAzureCredential is used, which is likely desired for most + // scenarios. If you need to restrict to a specific credential instance, you could + // register that instance as the default credential instead. + builder.UseCredential(new ManagedIdentityCredential()); + }); + + // Register other services, controllers, and other infrastructure. + } + #endregion + } + + public class SubClientRegistration + { + #region Snippet:DependencyInjectionRegisterSubClients + public async Task ConfigureServicesAsync(IServiceCollection services) + { + // Query the available queues for the Service Bus namespace. + var adminClient = new ServiceBusAdministrationClient("<< SERVICE BUS CONNECTION STRING >>"); + var queueNames = new List(); + + // Because the result is async, they need to be captured to a standard list to avoid async + // calls when registering. Failure to do so results in an error with the services collection. + await foreach (var queue in adminClient.GetQueuesAsync()) + { + queueNames.Add(queue.Name); + } + + // After registering the ServiceBusClient, register a named factory for each + // queue. This allows them to be lazily created and managed as singleton instances. + + services.AddAzureClients(builder => + { + builder.AddServiceBusClient("<< SERVICE BUS CONNECTION STRING >>"); + + foreach (var queueName in queueNames) + { + builder.AddClient((_, _, provider) => + provider + .GetService() + .CreateSender(queueName) + ) + .WithName(queueName); + } + }); + + // Register other services, controllers, and other infrastructure. + } + #endregion + + #region Snippet:DependencyInjectionBindToNamedSubClients + public class ServiceBusSendingController : ControllerBase + { + private readonly ServiceBusSender _sender; + + public ServiceBusSendingController(IAzureClientFactory serviceBusSenderFactory) + { + // Though the method is called "CreateClient", the factory will manage the sender as a + // singleton, creating a new instance only on the first use. + _sender = serviceBusSenderFactory.CreateClient("<< QUEUE NAME >>"); + } + } + #endregion + } + + // This is a dummy class pretending to be an ASP.NET controller. It is intended + // to allow the snippet to compile. + public class ControllerBase + { + } + } +} diff --git a/sdk/servicebus/Azure.ResourceManager.ServiceBus/tests/Samples/Readme.cs b/sdk/servicebus/Azure.ResourceManager.ServiceBus/tests/Samples/Readme.cs index 867ad7e96a1f..6d5b0fcaaed0 100644 --- a/sdk/servicebus/Azure.ResourceManager.ServiceBus/tests/Samples/Readme.cs +++ b/sdk/servicebus/Azure.ResourceManager.ServiceBus/tests/Samples/Readme.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -#region Snippet:Managing_ServiceBus_AuthClient_Usings using Azure.Identity; -#endregion - using NUnit.Framework; namespace Azure.ResourceManager.ServiceBus.Tests.Samples @@ -15,9 +12,7 @@ public class Readme [Ignore("Only verifying that the sample builds")] public void ClientAuth() { - #region Snippet:Managing_ServiceBus_AuthClient ArmClient armClient = new ArmClient(new DefaultAzureCredential()); - #endregion } } }