diff --git a/docs/azure/sdk/dependency-injection.md b/docs/azure/sdk/dependency-injection.md index 844741e355c3b..344728d6b5bbd 100644 --- a/docs/azure/sdk/dependency-injection.md +++ b/docs/azure/sdk/dependency-injection.md @@ -26,39 +26,43 @@ To register and configure service clients from an [`Azure.`-prefixed package](pa dotnet add package Azure.Identity ``` -For demonstration purposes, the sample code in this article uses the Key Vault Secrets and Blob Storage libraries. Install the following packages to follow along: +For demonstration purposes, the sample code in this article uses the Key Vault Secrets, Blob Storage, and Service Bus libraries. Install the following packages to follow along: ```dotnetcli dotnet add package Azure.Security.KeyVault.Secrets dotnet add package Azure.Storage.Blobs +dotnet add package Azure.Messaging.ServiceBus ``` -## Register clients +## Register clients and subclients + +A service client is the entry point to the API for an Azure service – from it, library users can invoke all operations the service provides and can easily implement the most common scenarios. Where it will simplify an API's design, groups of service calls can be organized around smaller subclient types. For example, `ServiceBusClient` can register additional `ServiceBusSender` subclients for publishing messages or `ServiceBusReceiver` subclients for consuming messages. In the *Program.cs* file, invoke the extension method to register a client for each service. The following code samples provide guidance on application builders from the `Microsoft.AspNetCore.Builder` and `Microsoft.Extensions.Hosting` namespaces. ### [WebApplicationBuilder](#tab/web-app-builder) -:::code language="csharp" source="snippets/dependency-injection/WebApplicationBuilder/Program.cs" id="snippet_WebApplicationBuilder" highlight="6-11"::: +:::code language="csharp" source="snippets/dependency-injection/WebApplicationBuilder/Program.cs" id="snippet_WebApplicationBuilder" highlight="10-26"::: ### [HostApplicationBuilder](#tab/host-app-builder) -:::code language="csharp" source="snippets/dependency-injection/HostApplicationBuilder/Program.cs" highlight="7-12"::: +:::code language="csharp" source="snippets/dependency-injection/HostApplicationBuilder/Program.cs" highlight="12-30"::: ### [HostBuilder](#tab/host-builder) -:::code language="csharp" source="snippets/dependency-injection/HostBuilder/Program.cs" id="snippet_HostBuilder" highlight="8-13"::: +:::code language="csharp" source="snippets/dependency-injection/HostBuilder/Program.cs" id="snippet_HostBuilder" highlight="11-27"::: --- In the preceding code: -* Key Vault Secrets and Blob Storage clients are registered using and , respectively. The `Uri`-typed arguments are passed. To avoid specifying these URLs explicitly, see the [Store configuration separately from code](#store-configuration-separately-from-code) section. +* Key Vault Secrets, Blob Storage, and Service Bus clients are registered using the , and , respectively. The `Uri`- and `string`-typed arguments are passed. To avoid specifying these URLs explicitly, see the [Store configuration separately from code](#store-configuration-separately-from-code) section. * is used to satisfy the `TokenCredential` argument requirement for each registered client. When one of the clients is created, `DefaultAzureCredential` is used to authenticate. +* Service Bus subclients are registered for each queue on the service using the subclient and corresponding options types. The queue names for the subclients are retrieved using a separate method outside of the service registration because the `GetQueuesAsync` method must be run asynchronously. ## Use the registered clients -With the clients registered, as described in the [Register clients](#register-clients) section, you can now use them. In the following example, [constructor injection](../../core/extensions/dependency-injection.md#constructor-injection-behavior) is used to obtain the Blob Storage client in an ASP.NET Core API controller: +With the clients registered, as described in the [Register clients and subclients](#register-clients-and-subclients) section, you can now use them. In the following example, [constructor injection](/dotnet/core/extensions/dependency-injection#constructor-injection-behavior) is used to obtain the Blob Storage client in an ASP.NET Core API controller: ```csharp [ApiController] @@ -91,7 +95,7 @@ public class MyApiController : ControllerBase ## Store configuration separately from code -In the [Register clients](#register-clients) section, you explicitly passed the `Uri`-typed variables to the client constructors. This approach could cause problems when you run code against different environments during development and production. The .NET team suggests [storing such configurations in environment-dependent JSON files](../../core/extensions/configuration-providers.md#json-configuration-provider). For example, you can have an *appsettings.Development.json* file containing development environment settings. Another *appsettings.Production.json* file would contain production environment settings, and so on. The file format is: +In the [Register clients and subclients](#register-clients-and-subclients) section, you explicitly passed the `Uri`-typed variables to the client constructors. This approach could cause problems when you run code against different environments during development and production. The .NET team suggests [storing such configurations in environment-dependent JSON files](../../core/extensions/configuration-providers.md#json-configuration-provider). For example, you can have an *appsettings.Development.json* file containing development environment settings. Another *appsettings.Production.json* file would contain production environment settings, and so on. The file format is: ```json { @@ -108,6 +112,9 @@ In the [Register clients](#register-clients) section, you explicitly passed the "KeyVault": { "VaultUri": "https://mykeyvault.vault.azure.net" }, + "ServiceBus": { + "Namespace": ".servicebus.windows.net" + }, "Storage": { "ServiceUri": "https://mydemoaccount.storage.windows.net" } @@ -127,6 +134,9 @@ builder.Services.AddAzureClients(clientBuilder => clientBuilder.AddBlobServiceClient( builder.Configuration.GetSection("Storage")); + clientBuilder.AddServiceBusClientWithNamespace( + builder.Configuration["ServiceBus:Namespace"]); + clientBuilder.UseCredential(new DefaultAzureCredential()); // Set up any default settings @@ -146,6 +156,9 @@ builder.Services.AddAzureClients(clientBuilder => clientBuilder.AddBlobServiceClient( builder.Configuration.GetSection("Storage")); + clientBuilder.AddServiceBusClientWithNamespace( + builder.Configuration["ServiceBus:Namespace"]); + clientBuilder.UseCredential(new DefaultAzureCredential()); // Set up any default settings @@ -169,6 +182,9 @@ IHost host = Host.CreateDefaultBuilder(args) clientBuilder.AddBlobServiceClient( hostContext.Configuration.GetSection("Storage")); + clientBuilder.AddServiceBusClientWithNamespace( + hostContext.Configuration["ServiceBus:Namespace"]); + clientBuilder.UseCredential(new DefaultAzureCredential()); // Set up any default settings @@ -183,11 +199,11 @@ IHost host = Host.CreateDefaultBuilder(args) In the preceding JSON sample: -* The top-level key names, `AzureDefaults`, `KeyVault`, and `Storage`, are arbitrary. All other key names hold significance, and JSON serialization is performed in a case-insensitive manner. +* The top-level key names, `AzureDefaults`, `KeyVault`, `ServiceBus`, and `Storage`, are arbitrary. All other key names hold significance, and JSON serialization is performed in a case-insensitive manner. * The `AzureDefaults.Retry` object literal: * Represents the [retry policy configuration settings](#configure-a-new-retry-policy). * Corresponds to the property. Within that object literal, you find the `MaxRetries` key, which corresponds to the property. -* The `KeyVault:VaultUri` and `Storage:ServiceUri` key values map to the `Uri`-typed arguments of the and constructor overloads, respectively. The `TokenCredential` variants of the constructors are used because a default `TokenCredential` is set via the method call. +* The `KeyVault:VaultUri`, `ServiceBus:Namespace`, and `Storage:ServiceUri` key values map to the `Uri`- and `string`-typed arguments of the , , and constructor overloads, respectively. The `TokenCredential` variants of the constructors are used because a default `TokenCredential` is set via the method call. ## Configure multiple service clients with different names @@ -239,6 +255,9 @@ At some point, you may want to change the default settings for a service client. "KeyVault": { "VaultUri": "https://mykeyvault.vault.azure.net" }, + "ServiceBus": { + "Namespace": ".servicebus.windows.net" + }, "Storage": { "ServiceUri": "https://store1.storage.windows.net" }, @@ -267,6 +286,10 @@ builder.Services.AddAzureClients(clientBuilder => builder.Configuration.GetSection("Storage")) .ConfigureOptions(options => options.Retry.MaxRetries = 10); + clientBuilder.AddServiceBusClientWithNamespace( + builder.Configuration["ServiceBus:Namespace"]) + .ConfigureOptions(options => options.RetryOptions.MaxRetries = 10); + // A named storage client with a different custom retry policy clientBuilder.AddBlobServiceClient( builder.Configuration.GetSection("CustomStorage")) diff --git a/docs/azure/sdk/logging.md b/docs/azure/sdk/logging.md index 839863a5f3e25..6a7b9d8553c99 100644 --- a/docs/azure/sdk/logging.md +++ b/docs/azure/sdk/logging.md @@ -154,7 +154,7 @@ Using the Azure Service Bus library as an example, complete the following steps: ### Logging without client registration -There are scenarios in which [registering an Azure SDK library's client with the DI container](dependency-injection.md#register-clients) is either impossible or unnecessary: +There are scenarios in which [registering an Azure SDK library's client with the DI container](dependency-injection.md#register-clients-and-subclients) is either impossible or unnecessary: - The Azure SDK library doesn't include an `IServiceCollection` extension method to register a client in the DI container. - Your app uses Azure extension libraries that depend on other Azure SDK libraries. Examples of such Azure extension libraries include: diff --git a/docs/azure/sdk/snippets/dependency-injection/Directory.Packages.props b/docs/azure/sdk/snippets/dependency-injection/Directory.Packages.props index e0495eadc37f5..b60632a2f7ae9 100644 --- a/docs/azure/sdk/snippets/dependency-injection/Directory.Packages.props +++ b/docs/azure/sdk/snippets/dependency-injection/Directory.Packages.props @@ -5,6 +5,7 @@ + diff --git a/docs/azure/sdk/snippets/dependency-injection/HostApplicationBuilder/HostApplicationBuilder.csproj b/docs/azure/sdk/snippets/dependency-injection/HostApplicationBuilder/HostApplicationBuilder.csproj index 219deff185e56..54b28c586b873 100644 --- a/docs/azure/sdk/snippets/dependency-injection/HostApplicationBuilder/HostApplicationBuilder.csproj +++ b/docs/azure/sdk/snippets/dependency-injection/HostApplicationBuilder/HostApplicationBuilder.csproj @@ -9,6 +9,7 @@ + diff --git a/docs/azure/sdk/snippets/dependency-injection/HostApplicationBuilder/Program.cs b/docs/azure/sdk/snippets/dependency-injection/HostApplicationBuilder/Program.cs index f5c8976d41b16..c4a061428e197 100644 --- a/docs/azure/sdk/snippets/dependency-injection/HostApplicationBuilder/Program.cs +++ b/docs/azure/sdk/snippets/dependency-injection/HostApplicationBuilder/Program.cs @@ -1,15 +1,51 @@ using Azure.Identity; +using Azure.Messaging.ServiceBus; +using Azure.Messaging.ServiceBus.Administration; using Microsoft.Extensions.Azure; using Microsoft.Extensions.Hosting; -HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); +List queueNames = await GetQueueNames(); -builder.Services.AddAzureClients(clientBuilder => -{ - clientBuilder.AddSecretClient(new Uri("")); - clientBuilder.AddBlobServiceClient(new Uri("")); - clientBuilder.UseCredential(new DefaultAzureCredential()); -}); +IHost host = Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + services.AddAzureClients(clientBuilder => + { + // Register clients for each service + clientBuilder.AddSecretClient(new Uri("")); + clientBuilder.AddBlobServiceClient(new Uri("")); + clientBuilder.AddServiceBusClientWithNamespace(".servicebus.windows.net"); + clientBuilder.UseCredential(new DefaultAzureCredential()); + + // Register subclients for Service Bus + foreach (string queueName in queueNames) + { + clientBuilder.AddClient((_, _, provider) => + provider.GetService(typeof(ServiceBusClient)) switch + { + ServiceBusClient client => client.CreateSender(queueName), + _ => throw new InvalidOperationException("Unable to create ServiceBusClient") + }).WithName(queueName); + } + }); + }).Build(); -using IHost host = builder.Build(); await host.RunAsync(); + +async Task> GetQueueNames() +{ + // Query the available queues for the Service Bus namespace. + var adminClient = new ServiceBusAdministrationClient + (".servicebus.windows.net", new DefaultAzureCredential()); + var queueNames = new List(); + + // Because the result is async, the queue names 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 (QueueProperties queue in adminClient.GetQueuesAsync()) + { + queueNames.Add(queue.Name); + } + + return queueNames; +} diff --git a/docs/azure/sdk/snippets/dependency-injection/HostBuilder/HostBuilder.csproj b/docs/azure/sdk/snippets/dependency-injection/HostBuilder/HostBuilder.csproj index aac5b5819bb1f..393da41132e87 100644 --- a/docs/azure/sdk/snippets/dependency-injection/HostBuilder/HostBuilder.csproj +++ b/docs/azure/sdk/snippets/dependency-injection/HostBuilder/HostBuilder.csproj @@ -9,6 +9,7 @@ + diff --git a/docs/azure/sdk/snippets/dependency-injection/HostBuilder/Program.cs b/docs/azure/sdk/snippets/dependency-injection/HostBuilder/Program.cs index 5d1946a8b267c..6fa1e415cce9f 100644 --- a/docs/azure/sdk/snippets/dependency-injection/HostBuilder/Program.cs +++ b/docs/azure/sdk/snippets/dependency-injection/HostBuilder/Program.cs @@ -1,20 +1,49 @@ -using HostBuilder; -#region snippet_HostBuilder +#region snippet_HostBuilder using Azure.Identity; +using Azure.Messaging.ServiceBus; +using Azure.Messaging.ServiceBus.Administration; using Microsoft.Extensions.Azure; +List queueNames = await GetQueueNames(); + IHost host = Host.CreateDefaultBuilder(args) .ConfigureServices(services => { - services.AddHostedService(); services.AddAzureClients(clientBuilder => { + // Register clients for each service clientBuilder.AddSecretClient(new Uri("")); clientBuilder.AddBlobServiceClient(new Uri("")); + clientBuilder.AddServiceBusClientWithNamespace(".servicebus.windows.net"); clientBuilder.UseCredential(new DefaultAzureCredential()); + + // Register a subclient for each Service Bus Queue + foreach (string queue in queueNames) + { + clientBuilder.AddClient((_, _, provider) => + provider.GetService().CreateSender(queue) + ).WithName(queue); + } }); - }) - .Build(); + }).Build(); await host.RunAsync(); + +async Task> GetQueueNames() +{ + // Query the available queues for the Service Bus namespace. + var adminClient = new ServiceBusAdministrationClient + (".servicebus.windows.net", new DefaultAzureCredential()); + var queueNames = new List(); + + // Because the result is async, the queue names 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 (QueueProperties queue in adminClient.GetQueuesAsync()) + { + queueNames.Add(queue.Name); + } + + return queueNames; +} #endregion snippet_HostBuilder diff --git a/docs/azure/sdk/snippets/dependency-injection/WebApplicationBuilder/Program.cs b/docs/azure/sdk/snippets/dependency-injection/WebApplicationBuilder/Program.cs index e659fb734a3b3..3dd7f253c4ba2 100644 --- a/docs/azure/sdk/snippets/dependency-injection/WebApplicationBuilder/Program.cs +++ b/docs/azure/sdk/snippets/dependency-injection/WebApplicationBuilder/Program.cs @@ -1,18 +1,51 @@ #region snippet_WebApplicationBuilder using Azure.Identity; +using Azure.Messaging.ServiceBus; +using Azure.Messaging.ServiceBus.Administration; using Microsoft.Extensions.Azure; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); +List queueNames = await GetQueueNames(); + builder.Services.AddAzureClients(clientBuilder => { + // Register clients for each service clientBuilder.AddSecretClient(new Uri("")); clientBuilder.AddBlobServiceClient(new Uri("")); + clientBuilder.AddServiceBusClientWithNamespace( + ".servicebus.windows.net"); clientBuilder.UseCredential(new DefaultAzureCredential()); + + // Register a subclient for each Service Bus Queue + foreach (string queue in queueNames) + { + clientBuilder.AddClient( + (_, _, provider) => provider.GetService() + .CreateSender(queue)).WithName(queue); + } }); WebApplication app = builder.Build(); -#endregion snippet_WebApplicationBuilder + +async Task> GetQueueNames() +{ + // Query the available queues for the Service Bus namespace. + var adminClient = new ServiceBusAdministrationClient + (".servicebus.windows.net", new DefaultAzureCredential()); + var queueNames = new List(); + + // Because the result is async, the queue names 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 (QueueProperties queue in adminClient.GetQueuesAsync()) + { + queueNames.Add(queue.Name); + } + + return queueNames; +} +#endregion if (app.Environment.IsDevelopment()) { diff --git a/docs/azure/sdk/snippets/dependency-injection/WebApplicationBuilder/WebApplicationBuilder.csproj b/docs/azure/sdk/snippets/dependency-injection/WebApplicationBuilder/WebApplicationBuilder.csproj index 8c8344ce817d1..87ea3e207d5ae 100644 --- a/docs/azure/sdk/snippets/dependency-injection/WebApplicationBuilder/WebApplicationBuilder.csproj +++ b/docs/azure/sdk/snippets/dependency-injection/WebApplicationBuilder/WebApplicationBuilder.csproj @@ -10,6 +10,7 @@ +