I have often had to integrate the Azure Storage SDK into various applications, and each time, I've done it differently. Here's an overview of the questions I've asked myself over time:
Should I register a BlobServiceClient
instance in the dependency injection service? What if I need to use multiple storage accounts? How do I manage multiple clients for different resources, such as containers, tables, or individual queues? Should I create my own abstraction or factory on top of the SDK?
How do I configure the clients? Should I create my own option classes? How can I easily override the SDK options? What if I want to use the Azurite emulator locally, and a managed identity in the Azure cloud environment?
I realized that I often created instances of DefaultAzureCredential
throughout my code, for different services such as Azure Storage, Event Grid, Key Vault, etc. DefaultAzureCredential
can slow down application startup in a local environment. How can I avoid the multiplication of these TokenCredential
derived instances?
If you've ever asked yourself these questions, then this article is for you. Just like for dependency injection, configuration, logging, and HTTP request resilience, Microsoft provides a library of extensions to uniformly integrate the Azure SDK into your applications.
Unsurprisingly, the name of this library is Microsoft.Extensions.Azure. In the remainder of this article, we will use it to integrate the Azure Blob Storage SDK, but similar code applies to other Azure services.
Prerequisites for using Microsoft.Extensions.Azure
Microsoft.Extensions.Azure is built on top of the Microsoft extension libraries ecosystem. Therefore, you need to install the following libraries:
- Microsoft.Extensions.Configuration.Abstractions and Microsoft.Extensions.Options for configuration,
- Microsoft.Extensions.DependencyInjection.Abstractions for dependency injection,
- Microsoft.Extensions.Logging.Abstractions for logging.
If you are using ASP.NET Core, or if your application uses the .NET generic host, then you already have these dependencies in your project.
Install Microsoft.Extensions.Azure in your project:
dotnet add package Microsoft.Extensions.Azure
Various ways to configure the Azure Blob Storage SDK with Microsoft.Extensions.Azure
Microsoft.Extensions.Azure provides several ways to configure the Azure Storage SDK. We will explore them from the simplest to the most flexible.
Single client registration
The simplest way to configure the Azure Blob Storage SDK is as follows. Here, we assume that we want to create only one BlobServiceClient
in an ASP.NET Core application.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAzureClients(azure =>
{
azure.AddBlobServiceClient(builder.Configuration.GetRequiredSection("Azure:Storage:Blob"));
});
Next, in your configuration, you just need to create the corresponding section:
{
"Azure": {
"Storage": {
"Blob": {
// Either this way (ideally the value would be provided from a secret vault)
"ConnectionString": "<your-connection-string>",
// Or this way (if you want to use a managed identity or SAS token)
"ServiceUri": "<your-service-uri>"
}
}
}
}
Subsequently, you can retrieve the instance of BlobServiceClient
via dependency injection:
public class MyController : ControllerBase
{
public MyController(BlobServiceClient blobServiceClient)
{
// ...
}
}
Microsoft.Extensions.Azure uses reflection to find the appropriate constructor to use to create the BlobServiceClient
instance based on the provided configuration section.
Multiple client registration
Similarly to named options, it is possible to register multiple Azure Blob Storage clients in the dependency injection service. These are named clients:
services.AddAzureClients(azure =>
{
azure.AddBlobServiceClient(configuration.GetRequiredSection("Azure:Storage:Blob:Invoices"))
.WithName("Invoices");
azure.AddBlobServiceClient(configuration.GetRequiredSection("Azure:Storage:Blob:Backups"))
.WithName("Backups");
});
Then, you use the IAzureClientFactory<BlobServiceClient>
interface to retrieve the client you want:
var clientFactory = serviceProvider.GetRequiredService<IAzureClientFactory<BlobServiceClient>>();
var invoicesClient = clientFactory.CreateClient("Invoices");
var backupsClient = clientFactory.CreateClient("Backups");
This approach allows for greater flexibility and control, enabling you to manage multiple Azure Blob Storage clients with different configurations within the same application.
It's worth mentioning that clients registered without specifying a name are automatically named Default
as we can see in the extensions source code.
Customizing the client factory with your own custom options
Instead of using the AddBlobServiceClient
extension method, you can use the more granular AddClient<TClient, TOptions>
. Consider the following custom options for demonstration purposes:
public class InvoiceClientOptions
{
public string AzureStorageUri { get; set; } = string.Empty;
}
We can then write a custom BlobServiceClient
factory compatible with both Azurite and a real Azure Storage account protected by managed identity:
services.AddAzureClients(azure =>
{
azure.AddClient<BlobServiceClient, BlobClientOptions>((clientOptions, tokenCredential, serviceProvider) =>
{
var invoiceOptions = serviceProvider.GetRequiredService<IOptions<InvoiceClientOptions>>();
var serviceUri = new Uri(invoiceOptions.Value.AzureStorageUri, UriKind.Absolute);
return serviceUri.Scheme == "https"
? new BlobServiceClient(serviceUri, tokenCredential, clientOptions) // Using TokenCredential
: new BlobServiceClient(serviceUri, clientOptions); // Using a shared access signature (SAS) for Azurite
})
.WithName("Invoices");
});
Customizing the Azure credential used by all Azure SDK clients created using Microsoft.Extensions.Azure
By default, Microsoft.Extensions.Azure uses DefaultAzureCredential
to create the TokenCredential
used by all Azure SDK clients. This can be customized by providing your own TokenCredential
:
services.AddAzureClients(azure =>
{
// Speed up the startup time of your application in local development by using Azure CLI in priority,
// and falling back to DefaultAzureCredential if Azure CLI is not available.
// See benchmarks at https://anthonysimmon.com/defaultazurecredential-local-development-optimization/
azure.UseCredential(new ChainedTokenCredential(new AzureCliCredential(), new DefaultAzureCredential()));
// ... client registrations
});
Customizing the options for a given client
You can customize the BlobClientOptions
that will be used by the BlobServiceClient
:
services.AddAzureClients(azure =>
{
// Method 1: default client
azure.AddBlobServiceClient(configuration.GetRequiredSection("Azure:Storage:Blob"))
.ConfigureOptions(options => options.Retry.MaxRetries = 10);
// Method 2: named client
azure.AddBlobServiceClient(configuration.GetRequiredSection("Azure:Storage:Blob:Backups"))
.ConfigureOptions(options => options.Retry.MaxRetries = 10)
.WithName("Backups");
});
// Method 3: still configuring a named client, but directly using the IConfigureOptions<TOptions> interface,
// which can be done anywhere in your startup code
services.Configure<BlobClientOptions>("Backups", options =>
{
options.Retry.Mode = RetryMode.Exponential;
});
Conclusion
Microsoft.Extensions.Azure is an extension library that allows for uniform integration of the Azure SDK into your applications, while giving you the necessary flexibility to customize the behavior of the created Azure SDK clients. The use of named clients is particularly convenient for supporting multiple instances of the same Azure resource type. You also get free logging as the Azure SDK events are automatically forwarded to an ILogger
instance.
Top comments (0)