用于 .NET 的 Azure SDK 的依赖关系注入

本文演示了如何从.NET 应用中的依赖项注入最新适用于 .NET 的 Azure 客户端库注册 Azure 服务客户端。 每个现代 .NET 应用都使用Program.cs文件中提供的指令来启动。

安装包

要从Azure.前缀包注册和配置服务客户端:

  1. 在项目中安装Microsoft.Extensions.Azure包:

    dotnet add package Microsoft.Extensions.Azure
    
  2. 安装Azure.Identity包以配置TokenCredential类型,用于对接受此类类型的所有已注册客户端进行身份验证:

    dotnet add package Azure.Identity
    

出于演示目的,本文中的示例代码使用密钥库机密、Blob 存储、服务总线和 Azure OpenAI 库。 安装以下包以遵循:

dotnet add package Azure.Security.KeyVault.Secrets
dotnet add package Azure.Storage.Blobs
dotnet add package Azure.Messaging.ServiceBus
dotnet add package Azure.AI.OpenAI

注册客户端和子客户端

服务客户端是 Azure 服务的 API 入口点 - 从该入口点,库用户可以调用服务提供的所有操作,并且可以轻松实现最常见的方案。 在可简化 API 设计的位置,可以将服务调用按更小的子类型来组织。 例如,ServiceBusClient 可以注册用于发布消息的其他 ServiceBusSender 子对象,或使用消息的 ServiceBusReceiver 子客户端。

Program.cs文件中,调用AddAzureClients扩展方法以为每个服务注册客户端。 以下代码示例提供有关Microsoft.AspNetCore.BuilderMicrosoft.Extensions.Hosting命名空间的应用程序生成器的指导。

using Azure.Identity;
using Azure.Messaging.ServiceBus;
using Azure.Messaging.ServiceBus.Administration;
using Microsoft.Extensions.Azure;
using Azure.AI.OpenAI;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddAzureClients(async 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");

    // Set a credential for all clients to use by default
    DefaultAzureCredential credential = new();
    clientBuilder.UseCredential(credential);

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

    // Register a custom client factory
    clientBuilder.AddClient<AzureOpenAIClient, AzureOpenAIClientOptions>(
        (options, _, _) => new AzureOpenAIClient(
            new Uri("<url_here>"), credential, options)); 
});

WebApplication app = builder.Build();

async Task<List<string>> GetQueueNames(DefaultAzureCredential credential)
{
    // Query the available queues for the Service Bus namespace.
    var adminClient = new ServiceBusAdministrationClient
        ("<your_namespace>.servicebus.windows.net", credential);
    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;
}

在上述代码中:

  • 分别使用 AddSecretClientAddBlobServiceClientAddServiceBusClientWithNamespace 注册 Key Vault 机密、Blob 存储和服务总线客户端。 将传递 Uri-类型和 string-类型参数。 要避免显式指定这些 URL,请参阅将配置与代码分开存储部分。
  • DefaultAzureCredential用于满足每个已注册客户端的TokenCredential参数要求。 创建其中一个客户端时,DefaultAzureCredential用于进行身份验证。
  • 使用子客户端和相应的选项类型为服务上的每个队列注册服务总线子客户端。 子客户端的队列名称通过使用服务注册外部的一个单独方法检索,因为 GetQueuesAsync 方法必须异步运行。
  • Azure OpenAI 客户端通过 AddClient 该方法使用自定义客户端工厂进行注册,该方法提供对客户端实例创建方式的控制。 自定义客户端工厂在以下情况下非常有用:
    • 需要在客户端构造过程中使用其他依赖项。
    • 要注册的服务客户端不存在注册扩展方法。

使用已注册的客户端

已按注册客户端和子客户端部分中所述注册了客户端,现在可以使用它们了。 在以下示例中,构造函数注入用于在 ASP.NET Core API 控制器中获取 Blob 存储客户端和服务总线发送方子客户端的工厂:

[ApiController]
[Route("[controller]")]
public class MyApiController : ControllerBase
{
    private readonly BlobServiceClient _blobServiceClient;
    private readonly ServiceBusSender _serviceBusSender;
  
    public MyApiController(
        BlobServiceClient blobServiceClient,
        IAzureClientFactory<ServiceBusSender> senderFactory)
    {
        _blobServiceClient = blobServiceClient;
        _serviceBusSender = senderFactory.CreateClient("myQueueName");
    }
  
    [HttpGet]
    public async Task<IEnumerable<string>> Get()
    {
        BlobContainerClient containerClient = 
            _blobServiceClient.GetBlobContainerClient("demo");
        var results = new List<string>();

        await foreach (BlobItem blob in containerClient.GetBlobsAsync())
        {
            results.Add(blob.Name);
        }

        return results.ToArray();
    }
}

将配置与代码分开存储

注册客户端和子客户端部分中,将 Uri-类型变量显式传递给了客户端构造函数。 在开发和生产期间针对不同环境运行代码时,此方法可能会导致一些问题。 .NET 团队建议将此类配置存储在依赖于环境的 JSON 文件中。 例如,你可以有一个包含开发环境设置的 appsettings.Development.json 文件。 另一个 appsettings.Production.json 文件则包含生产环境设置,依此类推。 文件格式如下:

{
  "AzureDefaults": {
    "Diagnostics": {
      "IsTelemetryDisabled": false,
      "IsLoggingContentEnabled": true
    },
    "Retry": {
      "MaxRetries": 3,
      "Mode": "Exponential"
    }
  },
  "KeyVault": {
    "VaultUri": "https://mykeyvault.vault.azure.net"
  },
  "ServiceBus": {
    "Namespace": "<your_namespace>.servicebus.windows.net"
  },
  "Storage": {
    "ServiceUri": "https://mydemoaccount.storage.windows.net"
  }
}

可以将ClientOptions类中的任何属性添加到 JSON 文件中。 可以使用IConfiguration检索 JSON 配置文件中的设置。

builder.Services.AddAzureClients(clientBuilder =>
{
    clientBuilder.AddSecretClient(
        builder.Configuration.GetSection("KeyVault"));

    clientBuilder.AddBlobServiceClient(
        builder.Configuration.GetSection("Storage"));

    clientBuilder.AddServiceBusClientWithNamespace(
        builder.Configuration["ServiceBus:Namespace"]);

    clientBuilder.UseCredential(new DefaultAzureCredential());

    // Set up any default settings
    clientBuilder.ConfigureDefaults(
        builder.Configuration.GetSection("AzureDefaults"));
});

在前面的 JSON 示例中:

使用不同的名称配置多个服务客户端

假设你有两个存储帐户:一个用于私人信息,另一个用于公共信息。 完成某个操作后,你的应用将数据从公共存储帐户传输到私有存储帐户。 你需要有两个存储服务客户端。 要区分这两个客户端,请使用WithName扩展方法:

builder.Services.AddAzureClients(clientBuilder =>
{
    clientBuilder.AddBlobServiceClient(
        builder.Configuration.GetSection("PublicStorage"));

    clientBuilder.AddBlobServiceClient(
            builder.Configuration.GetSection("PrivateStorage"))
        .WithName("PrivateStorage");
});

以 ASP.NET Core 控制器为例,使用IAzureClientFactory<TClient>接口访问命名服务客户端:

public class HomeController : Controller
{
    private readonly BlobServiceClient _publicStorage;
    private readonly BlobServiceClient _privateStorage;

    public HomeController(
        BlobServiceClient defaultClient,
        IAzureClientFactory<BlobServiceClient> clientFactory)
    {
        _publicStorage = defaultClient;
        _privateStorage = clientFactory.CreateClient("PrivateStorage");
    }
}

未命名的服务客户端仍然可以像以前一样使用。 命名客户端是附加的。

配置新的重试策略

在某些时候,你可能需要更改服务客户端的默认设置。 例如,你可能需要不同的重试设置或使用不同的服务 API 版本。 你可以全局设置或按服务设置重试设置。 假设你在 ASP.NET Core 项目中具有以下appsettings.json文件:

{
  "AzureDefaults": {
    "Retry": {
      "maxRetries": 3
    }
  },
  "KeyVault": {
    "VaultUri": "https://mykeyvault.vault.azure.net"
  },
  "ServiceBus": {
    "Namespace": "<your_namespace>.servicebus.windows.net"
  },
  "Storage": {
    "ServiceUri": "https://store1.storage.windows.net"
  },
  "CustomStorage": {
    "ServiceUri": "https://store2.storage.windows.net"
  }
}

可以更改重试策略以满足需求,如下所示:

builder.Services.AddAzureClients(clientBuilder =>
{
    // Establish the global defaults
    clientBuilder.ConfigureDefaults(
        builder.Configuration.GetSection("AzureDefaults"));
    clientBuilder.UseCredential(new DefaultAzureCredential());

    // A Key Vault Secrets client using the global defaults
    clientBuilder.AddSecretClient(
        builder.Configuration.GetSection("KeyVault"));

    // A Blob Storage client with a custom retry policy
    clientBuilder.AddBlobServiceClient(
            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"))
        .WithName("CustomStorage")
        .ConfigureOptions(options =>
        {
            options.Retry.Mode = Azure.Core.RetryMode.Exponential;
            options.Retry.MaxRetries = 5;
            options.Retry.MaxDelay = TimeSpan.FromSeconds(120);
        });
});

还可以在appsettings.json文件中放置重试策略替代项:

{
  "KeyVault": {
    "VaultUri": "https://mykeyvault.vault.azure.net",
    "Retry": {
      "maxRetries": 10
    }
  }
}

另请参阅