
I love how the Azure SDKs have evolved over the years. In the past, there was no consistency between the various Azure SDKs. However, that's not longer the case (at least for most Azure libraries), as they now adhere to the same principles and follow a set of well-defined guidelines.
Having consistency between libraries, it's easier to handle things like authentication and dependency injection consistently when you are using multiple Azure SDKs in your project.
One aspect often overlooked by people using Azure SDKs is the use of Microsoft.Extensions.Azure. This package facilitates registering and configuring the service clients for interacting with Azure APIs.
Let's see why using this package could be beneficial for your project.
It's mentioned in the documentation to use this package for dependency injection with the Azure SDK for .NET. Still, many people don't read the documentation and manually register the Azure service clients.
It's not a problem in itself if you know what you are doing. Otherwise,
using Azure.Identity;
using DIWithAzureSDK;
using Microsoft.Extensions.Azure;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
builder.Services.AddAzureClients(clientBuilder =>
{
clientBuilder.AddBlobServiceClient(new Uri("https://stdiwithazuresdk.blob.core.windows.net/"));
clientBuilder.UseCredential(new DefaultAzureCredential());
});
var host = builder.Build();
host.Run();
In this sample, the AddBlobServiceClient handles the registration of all dependencies for us so that the BlobServiceClient can then be injected directly where needed.
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly BlobServiceClient _blobServiceClient;
public Worker(ILogger<Worker> logger, BlobServiceClient blobServiceClient)
{
_logger = logger;
_blobServiceClient = blobServiceClient;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await foreach (var blobContainer in _blobServiceClient.GetBlobContainersAsync(cancellationToken: stoppingToken))
{
_logger.LogInformation(blobContainer.Name);
}
}
}
AddAzureClients method. When applications become larger with different csproj, I often prefer to separate service registration by business domain/module so having everything in a central place does not always suit my needs. That's not a problem, as the internal methods of the library make use of the TryAddd methods for registering services, I can call AddAzureClients in multiple places with only the services I want to register.All the SDKs use the Azure.Identity package to authenticate to Azure. There are different authentication methods available and you can easily specify which one to use with each client. Additionally, you can define a default authentication method for all clients, as demonstrated in the previous example.
builder.Services.AddAzureClients(clientBuilder =>
{
clientBuilder.AddServiceBusClient("https://sb-diwithazuresdk.servicebus.windows.net/")
.WithCredential(new ManagedIdentityCredential());
clientBuilder.AddTableServiceClient(new Uri("https://stdiwithazuresdk.table.core.windows.net"))
.WithCredential(new EnvironmentCredential());
clientBuilder.AddBlobServiceClient(new Uri("https://stdiwithazuresdk.blob.core.windows.net/"));
clientBuilder.UseCredential(new DefaultAzureCredential());
});
In the example above, we configured:
UseCredential method)All Azure clients have options that can be effortlessly configured when registering them in the AddAzureClients method.
builder.Services.AddAzureClients(clientBuilder =>
{
clientBuilder.AddBlobServiceClient(new Uri("https://stdiwithazuresdk.blob.core.windows.net/"))
.WithCredential(new DefaultAzureCredential())
.ConfigureOptions(options =>
{
options.TrimBlobNameSlashes = true;
options.Retry.MaxRetries = 10;
options.Diagnostics.IsLoggingEnabled = false;
});
});
Some options are specific to the client (like the TrimBlobNameSlashes here for Blob client). Others can be configured globally and overridden on a client if necessary.
builder.Services.AddAzureClients(clientBuilder =>
{
clientBuilder.AddBlobServiceClient(new Uri("https://stdiwithazuresdk.blob.core.windows.net/"))
.WithCredential(new DefaultAzureCredential())
.ConfigureOptions(options =>
{
options.TrimBlobNameSlashes = true;
options.Retry.MaxRetries = 10;
options.Diagnostics.IsLoggingEnabled = false;
});
clientBuilder.ConfigureDefaults(options =>
{
options.Retry.MaxRetries = 5;
options.Retry.Mode = RetryMode.Exponential;
options.Diagnostics.IsDistributedTracingEnabled = true;
});
});
That's the purpose of the ConfigureDefaults method.
clientBuilder.AddTableServiceClient(builder.Configuration.GetSection("Inventory:Tables"));Usually, you only need one client of each SDK in your application. Let's say you have multiple Azure Storage tables that are used in your application, you will only need to have one TableServiceClient. However, if you are interacting with tables in two different storage accounts, you will need multiple table clients.
To do that you can register your clients with a specific name:
builder.Services.AddAzureClients(clientBuilder =>
{
clientBuilder.AddTableServiceClient(builder.Configuration.GetSection("Shop:Inventory"))
.WithName("Shop");
clientBuilder.AddTableServiceClient(builder.Configuration.GetSection("Warehouse:Inventory"))
.WithName("Warehouse");
}
This way, you will be able to retrieve the specific client you need in your code:
public class WarehouseDeliveryService
{
private readonly TableServiceClient _tableServiceClient;
public WarehouseDeliveryService(IAzureClientFactory<TableServiceClient> azureClientFactory)
{
_tableServiceClient = azureClientFactory.CreateClient("Warehouse");
}
}
If you have specific needs, the AddClient method can help you register your Azure client while letting you control how you instantiate the client.
For instance, the Azure Cosmos Db .NET SDK is not built on the same foundation (Azure.Core) as the other SDKs. So at the time of writing, there is no AddCosmosServiceClient you can use in the AddAzureClients (there is an issue about that though). However, you can use the AddClient I've just mentioned.
builder.Services.AddOptions<CosmosDbConfiguration>().BindConfiguration("Warehouse:CosmosDb");
builder.Services.AddAzureClients(clientBuilder =>
{
clientBuilder.AddClient<CosmosClient, CosmosClientOptions>((_, _, serviceProvider) =>
{
var cosmosConfiguration = serviceProvider.GetRequiredService<IOptions<CosmosDbConfiguration>>().Value;
return new CosmosClientBuilder(cosmosConfiguration.Endpoint, cosmosConfiguration.AuthKey)
.WithSerializerOptions(new () { PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase })
.Build();
}).WithName("Warehouse");
}
You can note that using the AddClient method allows us to take profit from the named clients' feature.
As you have seen, the use of the Microsoft.Extensions.Azure package simplifies the registration and configuration of Azure clients. While providing you with a consistent way of handling the dependency injection for Azure SDKs, it also allows you to easily customize the authentication and other options available.
I hope you learned something. Don't hesitate to share your tips or what you like about the Azure SDKs in the comments.
Week 4, 2024 - Tips I learned this week
Some tips about Azure and Azure DevOps.
Clean up your local git branches.
Playing with Nushell to create a useful git alias to delete unused local git branches.