Skip to content

Commit e8f49cd

Browse files
alexwolfmsftscottaddiejsquire
authored
Document Azure SDK subclient registration (#36555)
* initial commit * service bus integration * removed extra file * fixed conflicts * fixed csproj * Fixed link * fix version * updates * updates * updates * fix highlights * refactor * fix package * fix line numbers * fix * fix * Apply suggestions from code review Co-authored-by: Scott Addie <[email protected]> * tweaks * Apply suggestions from code review Co-authored-by: Scott Addie <[email protected]> * pr feedback * Apply suggestions from code review Co-authored-by: Jesse Squire <[email protected]> * removed namespaceuri * Apply suggestions from code review Co-authored-by: Jesse Squire <[email protected]> * Apply suggestions from code review Co-authored-by: Scott Addie <[email protected]> --------- Co-authored-by: Scott Addie <[email protected]> Co-authored-by: Jesse Squire <[email protected]>
1 parent 654469e commit e8f49cd

File tree

9 files changed

+150
-25
lines changed

9 files changed

+150
-25
lines changed

docs/azure/sdk/dependency-injection.md

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,39 +26,43 @@ To register and configure service clients from an [`Azure.`-prefixed package](pa
2626
dotnet add package Azure.Identity
2727
```
2828
29-
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:
29+
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:
3030
3131
```dotnetcli
3232
dotnet add package Azure.Security.KeyVault.Secrets
3333
dotnet add package Azure.Storage.Blobs
34+
dotnet add package Azure.Messaging.ServiceBus
3435
```
3536

36-
## Register clients
37+
## Register clients and subclients
38+
39+
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.
3740

3841
In the *Program.cs* file, invoke the <xref:Microsoft.Extensions.Azure.AzureClientServiceCollectionExtensions.AddAzureClients%2A> 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.
3942

4043
### [WebApplicationBuilder](#tab/web-app-builder)
4144

42-
:::code language="csharp" source="snippets/dependency-injection/WebApplicationBuilder/Program.cs" id="snippet_WebApplicationBuilder" highlight="6-11":::
45+
:::code language="csharp" source="snippets/dependency-injection/WebApplicationBuilder/Program.cs" id="snippet_WebApplicationBuilder" highlight="10-26":::
4346

4447
### [HostApplicationBuilder](#tab/host-app-builder)
4548

46-
:::code language="csharp" source="snippets/dependency-injection/HostApplicationBuilder/Program.cs" highlight="7-12":::
49+
:::code language="csharp" source="snippets/dependency-injection/HostApplicationBuilder/Program.cs" highlight="12-30":::
4750

4851
### [HostBuilder](#tab/host-builder)
4952

50-
:::code language="csharp" source="snippets/dependency-injection/HostBuilder/Program.cs" id="snippet_HostBuilder" highlight="8-13":::
53+
:::code language="csharp" source="snippets/dependency-injection/HostBuilder/Program.cs" id="snippet_HostBuilder" highlight="11-27":::
5154

5255
---
5356

5457
In the preceding code:
5558

56-
* Key Vault Secrets and Blob Storage clients are registered using <xref:Microsoft.Extensions.Azure.SecretClientBuilderExtensions.AddSecretClient%2A> and <xref:Microsoft.Extensions.Azure.BlobClientBuilderExtensions.AddBlobServiceClient%2A>, 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.
59+
* Key Vault Secrets, Blob Storage, and Service Bus clients are registered using the <xref:Microsoft.Extensions.Azure.SecretClientBuilderExtensions.AddSecretClient%2A>, <xref:Microsoft.Extensions.Azure.BlobClientBuilderExtensions.AddBlobServiceClient%2A> and <xref:Microsoft.Extensions.Azure.ServiceBusClientBuilderExtensions.AddServiceBusClientWithNamespace%2A>, 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.
5760
* <xref:Azure.Identity.DefaultAzureCredential> is used to satisfy the `TokenCredential` argument requirement for each registered client. When one of the clients is created, `DefaultAzureCredential` is used to authenticate.
61+
* 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.
5862

5963
## Use the registered clients
6064

61-
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:
65+
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:
6266

6367
```csharp
6468
[ApiController]
@@ -91,7 +95,7 @@ public class MyApiController : ControllerBase
9195

9296
## Store configuration separately from code
9397

94-
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:
98+
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:
9599

96100
```json
97101
{
@@ -108,6 +112,9 @@ In the [Register clients](#register-clients) section, you explicitly passed the
108112
"KeyVault": {
109113
"VaultUri": "https://mykeyvault.vault.azure.net"
110114
},
115+
"ServiceBus": {
116+
"Namespace": "<your_namespace>.servicebus.windows.net"
117+
},
111118
"Storage": {
112119
"ServiceUri": "https://mydemoaccount.storage.windows.net"
113120
}
@@ -127,6 +134,9 @@ builder.Services.AddAzureClients(clientBuilder =>
127134
clientBuilder.AddBlobServiceClient(
128135
builder.Configuration.GetSection("Storage"));
129136

137+
clientBuilder.AddServiceBusClientWithNamespace(
138+
builder.Configuration["ServiceBus:Namespace"]);
139+
130140
clientBuilder.UseCredential(new DefaultAzureCredential());
131141

132142
// Set up any default settings
@@ -146,6 +156,9 @@ builder.Services.AddAzureClients(clientBuilder =>
146156
clientBuilder.AddBlobServiceClient(
147157
builder.Configuration.GetSection("Storage"));
148158

159+
clientBuilder.AddServiceBusClientWithNamespace(
160+
builder.Configuration["ServiceBus:Namespace"]);
161+
149162
clientBuilder.UseCredential(new DefaultAzureCredential());
150163

151164
// Set up any default settings
@@ -169,6 +182,9 @@ IHost host = Host.CreateDefaultBuilder(args)
169182
clientBuilder.AddBlobServiceClient(
170183
hostContext.Configuration.GetSection("Storage"));
171184

185+
clientBuilder.AddServiceBusClientWithNamespace(
186+
hostContext.Configuration["ServiceBus:Namespace"]);
187+
172188
clientBuilder.UseCredential(new DefaultAzureCredential());
173189

174190
// Set up any default settings
@@ -183,11 +199,11 @@ IHost host = Host.CreateDefaultBuilder(args)
183199

184200
In the preceding JSON sample:
185201

186-
* 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.
202+
* 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.
187203
* The `AzureDefaults.Retry` object literal:
188204
* Represents the [retry policy configuration settings](#configure-a-new-retry-policy).
189205
* Corresponds to the <xref:Azure.Core.ClientOptions.Retry> property. Within that object literal, you find the `MaxRetries` key, which corresponds to the <xref:Azure.Core.RetryOptions.MaxRetries> property.
190-
* The `KeyVault:VaultUri` and `Storage:ServiceUri` key values map to the `Uri`-typed arguments of the <xref:Azure.Security.KeyVault.Secrets.SecretClient.%23ctor(System.Uri,Azure.Core.TokenCredential,Azure.Security.KeyVault.Secrets.SecretClientOptions)?displayProperty=fullName> and <xref:Azure.Storage.Blobs.BlobServiceClient.%23ctor(System.Uri,Azure.Core.TokenCredential,Azure.Storage.Blobs.BlobClientOptions)?displayProperty=fullName> constructor overloads, respectively. The `TokenCredential` variants of the constructors are used because a default `TokenCredential` is set via the <xref:Microsoft.Extensions.Azure.AzureClientFactoryBuilder.UseCredential(Azure.Core.TokenCredential)?displayProperty=fullName> method call.
206+
* The `KeyVault:VaultUri`, `ServiceBus:Namespace`, and `Storage:ServiceUri` key values map to the `Uri`- and `string`-typed arguments of the <xref:Azure.Security.KeyVault.Secrets.SecretClient.%23ctor(System.Uri,Azure.Core.TokenCredential,Azure.Security.KeyVault.Secrets.SecretClientOptions)?displayProperty=fullName>, <xref:Azure.Messaging.ServiceBus.ServiceBusClient.%23ctor(System.String)?displayProperty=fullName>, and <xref:Azure.Storage.Blobs.BlobServiceClient.%23ctor(System.Uri,Azure.Core.TokenCredential,Azure.Storage.Blobs.BlobClientOptions)?displayProperty=fullName> constructor overloads, respectively. The `TokenCredential` variants of the constructors are used because a default `TokenCredential` is set via the <xref:Microsoft.Extensions.Azure.AzureClientFactoryBuilder.UseCredential(Azure.Core.TokenCredential)?displayProperty=fullName> method call.
191207

192208
## Configure multiple service clients with different names
193209

@@ -239,6 +255,9 @@ At some point, you may want to change the default settings for a service client.
239255
"KeyVault": {
240256
"VaultUri": "https://mykeyvault.vault.azure.net"
241257
},
258+
"ServiceBus": {
259+
"Namespace": "<your_namespace>.servicebus.windows.net"
260+
},
242261
"Storage": {
243262
"ServiceUri": "https://store1.storage.windows.net"
244263
},
@@ -267,6 +286,10 @@ builder.Services.AddAzureClients(clientBuilder =>
267286
builder.Configuration.GetSection("Storage"))
268287
.ConfigureOptions(options => options.Retry.MaxRetries = 10);
269288

289+
clientBuilder.AddServiceBusClientWithNamespace(
290+
builder.Configuration["ServiceBus:Namespace"])
291+
.ConfigureOptions(options => options.RetryOptions.MaxRetries = 10);
292+
270293
// A named storage client with a different custom retry policy
271294
clientBuilder.AddBlobServiceClient(
272295
builder.Configuration.GetSection("CustomStorage"))

docs/azure/sdk/logging.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ Using the Azure Service Bus library as an example, complete the following steps:
154154
155155
### Logging without client registration
156156
157-
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:
157+
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:
158158
159159
- The Azure SDK library doesn't include an `IServiceCollection` extension method to register a client in the DI container.
160160
- Your app uses Azure extension libraries that depend on other Azure SDK libraries. Examples of such Azure extension libraries include:

docs/azure/sdk/snippets/dependency-injection/Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<ItemGroup>
66
<PackageVersion Include="Azure.Identity" Version="1.9.0" />
77
<PackageVersion Include="Azure.Security.KeyVault.Secrets" Version="4.5.0" />
8+
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.16.0" />
89
<PackageVersion Include="Azure.Storage.Blobs" Version="12.17.0" />
910
<PackageVersion Include="Microsoft.Extensions.Azure" Version="1.6.3" />
1011
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="7.0.9" />

docs/azure/sdk/snippets/dependency-injection/HostApplicationBuilder/HostApplicationBuilder.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="Azure.Identity" />
12+
<PackageReference Include="Azure.Messaging.ServiceBus" />
1213
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
1314
<PackageReference Include="Azure.Storage.Blobs" />
1415
<PackageReference Include="Microsoft.Extensions.Azure" />
Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,51 @@
11
using Azure.Identity;
2+
using Azure.Messaging.ServiceBus;
3+
using Azure.Messaging.ServiceBus.Administration;
24
using Microsoft.Extensions.Azure;
35
using Microsoft.Extensions.Hosting;
46

5-
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
7+
List<string> queueNames = await GetQueueNames();
68

7-
builder.Services.AddAzureClients(clientBuilder =>
8-
{
9-
clientBuilder.AddSecretClient(new Uri("<key_vault_url>"));
10-
clientBuilder.AddBlobServiceClient(new Uri("<storage_url>"));
11-
clientBuilder.UseCredential(new DefaultAzureCredential());
12-
});
9+
IHost host = Host.CreateDefaultBuilder(args)
10+
.ConfigureServices(services =>
11+
{
12+
services.AddAzureClients(clientBuilder =>
13+
{
14+
// Register clients for each service
15+
clientBuilder.AddSecretClient(new Uri("<key_vault_url>"));
16+
clientBuilder.AddBlobServiceClient(new Uri("<storage_url>"));
17+
clientBuilder.AddServiceBusClientWithNamespace("<your_namespace>.servicebus.windows.net");
18+
clientBuilder.UseCredential(new DefaultAzureCredential());
19+
20+
// Register subclients for Service Bus
21+
foreach (string queueName in queueNames)
22+
{
23+
clientBuilder.AddClient<ServiceBusSender, ServiceBusClientOptions>((_, _, provider) =>
24+
provider.GetService(typeof(ServiceBusClient)) switch
25+
{
26+
ServiceBusClient client => client.CreateSender(queueName),
27+
_ => throw new InvalidOperationException("Unable to create ServiceBusClient")
28+
}).WithName(queueName);
29+
}
30+
});
31+
}).Build();
1332

14-
using IHost host = builder.Build();
1533
await host.RunAsync();
34+
35+
async Task<List<string>> GetQueueNames()
36+
{
37+
// Query the available queues for the Service Bus namespace.
38+
var adminClient = new ServiceBusAdministrationClient
39+
("<your_namespace>.servicebus.windows.net", new DefaultAzureCredential());
40+
var queueNames = new List<string>();
41+
42+
// Because the result is async, the queue names need to be captured
43+
// to a standard list to avoid async calls when registering. Failure to
44+
// do so results in an error with the services collection.
45+
await foreach (QueueProperties queue in adminClient.GetQueuesAsync())
46+
{
47+
queueNames.Add(queue.Name);
48+
}
49+
50+
return queueNames;
51+
}

docs/azure/sdk/snippets/dependency-injection/HostBuilder/HostBuilder.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="Azure.Identity" />
12+
<PackageReference Include="Azure.Messaging.ServiceBus" />
1213
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
1314
<PackageReference Include="Azure.Storage.Blobs" />
1415
<PackageReference Include="Microsoft.Extensions.Azure" />
Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,49 @@
1-
using HostBuilder;
2-
#region snippet_HostBuilder
1+
#region snippet_HostBuilder
32
using Azure.Identity;
3+
using Azure.Messaging.ServiceBus;
4+
using Azure.Messaging.ServiceBus.Administration;
45
using Microsoft.Extensions.Azure;
56

7+
List<string> queueNames = await GetQueueNames();
8+
69
IHost host = Host.CreateDefaultBuilder(args)
710
.ConfigureServices(services =>
811
{
9-
services.AddHostedService<Worker>();
1012
services.AddAzureClients(clientBuilder =>
1113
{
14+
// Register clients for each service
1215
clientBuilder.AddSecretClient(new Uri("<key_vault_url>"));
1316
clientBuilder.AddBlobServiceClient(new Uri("<storage_url>"));
17+
clientBuilder.AddServiceBusClientWithNamespace("<your_namespace>.servicebus.windows.net");
1418
clientBuilder.UseCredential(new DefaultAzureCredential());
19+
20+
// Register a subclient for each Service Bus Queue
21+
foreach (string queue in queueNames)
22+
{
23+
clientBuilder.AddClient<ServiceBusSender, ServiceBusClientOptions>((_, _, provider) =>
24+
provider.GetService<ServiceBusClient>().CreateSender(queue)
25+
).WithName(queue);
26+
}
1527
});
16-
})
17-
.Build();
28+
}).Build();
1829

1930
await host.RunAsync();
31+
32+
async Task<List<string>> GetQueueNames()
33+
{
34+
// Query the available queues for the Service Bus namespace.
35+
var adminClient = new ServiceBusAdministrationClient
36+
("<your_namespace>.servicebus.windows.net", new DefaultAzureCredential());
37+
var queueNames = new List<string>();
38+
39+
// Because the result is async, the queue names need to be captured
40+
// to a standard list to avoid async calls when registering. Failure to
41+
// do so results in an error with the services collection.
42+
await foreach (QueueProperties queue in adminClient.GetQueuesAsync())
43+
{
44+
queueNames.Add(queue.Name);
45+
}
46+
47+
return queueNames;
48+
}
2049
#endregion snippet_HostBuilder

docs/azure/sdk/snippets/dependency-injection/WebApplicationBuilder/Program.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,51 @@
11
#region snippet_WebApplicationBuilder
22
using Azure.Identity;
3+
using Azure.Messaging.ServiceBus;
4+
using Azure.Messaging.ServiceBus.Administration;
35
using Microsoft.Extensions.Azure;
46

57
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
68

9+
List<string> queueNames = await GetQueueNames();
10+
711
builder.Services.AddAzureClients(clientBuilder =>
812
{
13+
// Register clients for each service
914
clientBuilder.AddSecretClient(new Uri("<key_vault_url>"));
1015
clientBuilder.AddBlobServiceClient(new Uri("<storage_url>"));
16+
clientBuilder.AddServiceBusClientWithNamespace(
17+
"<your_namespace>.servicebus.windows.net");
1118
clientBuilder.UseCredential(new DefaultAzureCredential());
19+
20+
// Register a subclient for each Service Bus Queue
21+
foreach (string queue in queueNames)
22+
{
23+
clientBuilder.AddClient<ServiceBusSender, ServiceBusClientOptions>(
24+
(_, _, provider) => provider.GetService<ServiceBusClient>()
25+
.CreateSender(queue)).WithName(queue);
26+
}
1227
});
1328

1429
WebApplication app = builder.Build();
15-
#endregion snippet_WebApplicationBuilder
30+
31+
async Task<List<string>> GetQueueNames()
32+
{
33+
// Query the available queues for the Service Bus namespace.
34+
var adminClient = new ServiceBusAdministrationClient
35+
("<your_namespace>.servicebus.windows.net", new DefaultAzureCredential());
36+
var queueNames = new List<string>();
37+
38+
// Because the result is async, the queue names need to be captured
39+
// to a standard list to avoid async calls when registering. Failure to
40+
// do so results in an error with the services collection.
41+
await foreach (QueueProperties queue in adminClient.GetQueuesAsync())
42+
{
43+
queueNames.Add(queue.Name);
44+
}
45+
46+
return queueNames;
47+
}
48+
#endregion
1649

1750
if (app.Environment.IsDevelopment())
1851
{

docs/azure/sdk/snippets/dependency-injection/WebApplicationBuilder/WebApplicationBuilder.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<PackageReference Include="Azure.Identity" />
1111
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
1212
<PackageReference Include="Azure.Storage.Blobs" />
13+
<PackageReference Include="Azure.Messaging.ServiceBus" />
1314
<PackageReference Include="Microsoft.Extensions.Azure" />
1415
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
1516
<PackageReference Include="Swashbuckle.AspNetCore" />

0 commit comments

Comments
 (0)