Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
26f27b9
initial commit
alexwolfmsft Aug 8, 2023
1b917a9
service bus integration
alexwolfmsft Aug 9, 2023
b806cad
removed extra file
alexwolfmsft Aug 9, 2023
4e79de1
merge
alexwolfmsft Aug 9, 2023
a1da61a
fixed conflicts
alexwolfmsft Aug 9, 2023
f51aeda
fixed csproj
alexwolfmsft Aug 9, 2023
e819db1
Fixed link
alexwolfmsft Aug 9, 2023
c13afef
fix version
alexwolfmsft Aug 9, 2023
59a3c08
updates
alexwolfmsft Aug 9, 2023
19966c6
updates
alexwolfmsft Aug 9, 2023
7562851
updates
alexwolfmsft Aug 9, 2023
1aac173
fix highlights
alexwolfmsft Aug 9, 2023
fe15f92
refactor
alexwolfmsft Aug 9, 2023
7252794
fix package
alexwolfmsft Aug 9, 2023
a1512a9
fix line numbers
alexwolfmsft Aug 9, 2023
74180da
fix
alexwolfmsft Aug 9, 2023
51a5d9e
fix
alexwolfmsft Aug 9, 2023
c273496
Apply suggestions from code review
alexwolfmsft Aug 9, 2023
7c4ec5d
merge
alexwolfmsft Aug 10, 2023
7ec5795
Merge branch 'dependency-injection-subclients' of https://github.com/…
alexwolfmsft Aug 10, 2023
bf5935c
tweaks
alexwolfmsft Aug 10, 2023
50d2905
Apply suggestions from code review
alexwolfmsft Aug 10, 2023
d228890
pr feedback
alexwolfmsft Aug 10, 2023
043df3f
Merge branch 'dependency-injection-subclients' of https://github.com/…
alexwolfmsft Aug 10, 2023
95a396f
Apply suggestions from code review
alexwolfmsft Aug 10, 2023
9e2f93b
removed namespaceuri
alexwolfmsft Aug 10, 2023
11c330c
Apply suggestions from code review
alexwolfmsft Aug 10, 2023
8e9b8a6
Apply suggestions from code review
alexwolfmsft Aug 11, 2023
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
43 changes: 33 additions & 10 deletions docs/azure/sdk/dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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.

### [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 <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.
* 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.
* <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.
* 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]
Expand Down Expand Up @@ -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
{
Expand All @@ -108,6 +112,9 @@ In the [Register clients](#register-clients) section, you explicitly passed the
"KeyVault": {
"VaultUri": "https://mykeyvault.vault.azure.net"
},
"ServiceBus": {
"Namespace": "<your_namespace>.servicebus.windows.net"
},
"Storage": {
"ServiceUri": "https://mydemoaccount.storage.windows.net"
}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 <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.
* 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.
* 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.

## Configure multiple service clients with different names

Expand Down Expand Up @@ -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": "<your_namespace>.servicebus.windows.net"
},
"Storage": {
"ServiceUri": "https://store1.storage.windows.net"
},
Expand Down Expand Up @@ -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"))
Expand Down
2 changes: 1 addition & 1 deletion docs/azure/sdk/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<ItemGroup>
<PackageVersion Include="Azure.Identity" Version="1.9.0" />
<PackageVersion Include="Azure.Security.KeyVault.Secrets" Version="4.5.0" />
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.16.0" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.17.0" />
<PackageVersion Include="Microsoft.Extensions.Azure" Version="1.6.3" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="7.0.9" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.Messaging.ServiceBus" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
<PackageReference Include="Azure.Storage.Blobs" />
<PackageReference Include="Microsoft.Extensions.Azure" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string> queueNames = await GetQueueNames();

builder.Services.AddAzureClients(clientBuilder =>
{
clientBuilder.AddSecretClient(new Uri("<key_vault_url>"));
clientBuilder.AddBlobServiceClient(new Uri("<storage_url>"));
clientBuilder.UseCredential(new DefaultAzureCredential());
});
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddAzureClients(clientBuilder =>
{
// Register clients for each service
clientBuilder.AddSecretClient(new Uri("<key_vault_url>"));
clientBuilder.AddBlobServiceClient(new Uri("<storage_url>"));
clientBuilder.AddServiceBusClientWithNamespace("<your_namespace>.servicebus.windows.net");
clientBuilder.UseCredential(new DefaultAzureCredential());

// Register subclients for Service Bus
foreach (string queueName in queueNames)
{
clientBuilder.AddClient<ServiceBusSender, ServiceBusClientOptions>((_, _, 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<List<string>> GetQueueNames()
{
// Query the available queues for the Service Bus namespace.
var adminClient = new ServiceBusAdministrationClient
("<your_namespace>.servicebus.windows.net", new DefaultAzureCredential());
var queueNames = new List<string>();

// 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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.Messaging.ServiceBus" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
<PackageReference Include="Azure.Storage.Blobs" />
<PackageReference Include="Microsoft.Extensions.Azure" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string> queueNames = await GetQueueNames();

IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<Worker>();
services.AddAzureClients(clientBuilder =>
{
// Register clients for each service
clientBuilder.AddSecretClient(new Uri("<key_vault_url>"));
clientBuilder.AddBlobServiceClient(new Uri("<storage_url>"));
clientBuilder.AddServiceBusClientWithNamespace("<your_namespace>.servicebus.windows.net");
clientBuilder.UseCredential(new DefaultAzureCredential());

// Register a subclient for each Service Bus Queue
foreach (string queue in queueNames)
{
clientBuilder.AddClient<ServiceBusSender, ServiceBusClientOptions>((_, _, provider) =>
provider.GetService<ServiceBusClient>().CreateSender(queue)
).WithName(queue);
}
});
})
.Build();
}).Build();

await host.RunAsync();

async Task<List<string>> GetQueueNames()
{
// Query the available queues for the Service Bus namespace.
var adminClient = new ServiceBusAdministrationClient
("<your_namespace>.servicebus.windows.net", new DefaultAzureCredential());
var queueNames = new List<string>();

// 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
Original file line number Diff line number Diff line change
@@ -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<string> queueNames = await GetQueueNames();

builder.Services.AddAzureClients(clientBuilder =>
{
// Register clients for each service
clientBuilder.AddSecretClient(new Uri("<key_vault_url>"));
clientBuilder.AddBlobServiceClient(new Uri("<storage_url>"));
clientBuilder.AddServiceBusClientWithNamespace(
"<your_namespace>.servicebus.windows.net");
clientBuilder.UseCredential(new DefaultAzureCredential());

// Register a subclient for each Service Bus Queue
foreach (string queue in queueNames)
{
clientBuilder.AddClient<ServiceBusSender, ServiceBusClientOptions>(
(_, _, provider) => provider.GetService<ServiceBusClient>()
.CreateSender(queue)).WithName(queue);
}
});

WebApplication app = builder.Build();
#endregion snippet_WebApplicationBuilder

async Task<List<string>> GetQueueNames()
{
// Query the available queues for the Service Bus namespace.
var adminClient = new ServiceBusAdministrationClient
("<your_namespace>.servicebus.windows.net", new DefaultAzureCredential());
var queueNames = new List<string>();

// 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())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
<PackageReference Include="Azure.Storage.Blobs" />
<PackageReference Include="Azure.Messaging.ServiceBus" />
<PackageReference Include="Microsoft.Extensions.Azure" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Swashbuckle.AspNetCore" />
Expand Down