👌

Azure Extensions Librariesを使ってAzure接続クライアントを管理する

2021/08/16に公開

はじめに

ここ最近(最近。。?)、 .NET から StorageAccount などの Azure リソースへ接続するための NuGet ライブラリ群が Azure.* に変更されています。ライブラリの使い方自体は Microsoft のドキュメントを眺めれば良いのですが、各接続クライアントの管理に利用できるライブラリ Microsoft.Extensions.Azure に関するドキュメントが全然見当たりません。 Microsoft.Extensions.Azure は Azure 接続クライアントと Microsoft Extensions の DI 、 Configuration 機能を統合して利用するためのライブラリです。この記事では Microsoft.Extensions.Azure の機能を利用して BlobStorage に接続します。

https://www.nuget.org/packages/Microsoft.Extensions.Azure/

https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.Extensions.Azure_1.1.0/sdk/extensions/Microsoft.Extensions.Azure

ライブラリのバージョンなど

  • .NET 5.0 (SDK 5.0.400)
  • Azure.Storage.Blobs@12.9.1
  • Microsoft.Extensions.Azure@1.1.0
  • Azurite@3.14.0

Extensions を使用しないで接続

まずはシンプルに、 Extensions ライブラリを使用せず、接続文字列と BlobServiceClient を直接使ってみます。

Program.cs
using System;
using System.Threading;
using System.Threading.Tasks;
using Azure;
using Azure.Storage.Blobs;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace ClientLibSample
{
    class Program : IHostedService
    {
        static async Task Main(string[] args)
        {
            await Host.CreateDefaultBuilder(args)
                .ConfigureServices((context, services) =>
                {
                    services.AddHostedService<Program>();
                })
                .RunConsoleAsync();
        }

        // Azuriteへの接続文字列
        const string ConnectionString = @"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;";


        private readonly ILogger<Program> _logger;

        public Program(ILogger<Program> logger)
        {
            _logger = logger;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {

            try
            {
                // Blobクライアントの初期化からBlobのアップロードまで
                BlobServiceClient client = new BlobServiceClient(ConnectionString);
                BlobContainerClient container = client.GetBlobContainerClient($"container-{Guid.NewGuid():N}");
                await container.CreateIfNotExistsAsync(cancellationToken: cancellationToken);

                BlobClient blob = container.GetBlobClient($"blob-{Guid.NewGuid():N}");
                await blob.UploadAsync(new BinaryData("Blob Data"), cancellationToken: cancellationToken);

                Response<bool> uploaded = await blob.ExistsAsync(cancellationToken: cancellationToken);
                _logger.LogInformation("アップロードできてる?: {result}", uploaded.Value);
            }
            catch (RequestFailedException e)
            {
                // 何かの接続エラー
                _logger.LogError(e, "なんか接続おかしい");
                throw;
            }
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            return Task.CompletedTask;
        }
    }
}

全体を載せているのでちょっと長くなっていますが、シンプルに接続するならこんな形になるでしょう。実装コード中で BlobStorageAccount を初期化して利用しています。このコードを少しずつ Extensions を利用する方法へと書き換えます。

Extensions を使用して接続

Extensions を利用して BlobStorage に接続します。 Extensions の機能は大きく3つあり、「クライアント生成」「Configurationの利用」「クライアントの名前管理」です。いずれの機能を使用するにせよ、 IServiceCollection の拡張メソッド AddAzureClients を使用します。

Program.cs
static async Task Main(string[] args)
{
    await Host.CreateDefaultBuilder(args)
        .ConfigureServices((context, services) =>
        {
            services.AddHostedService<Program>();

            // Azureクライアント管理
            services.AddAzureClients(builder =>
            {
                // クライアントの登録
            });

        })
        .RunConsoleAsync();
}

ちなみに AddAzureClient を呼び出すと EventSource へ出力されていた接続ログが Logger へも出力される様になるため、以下のような詳細なログが出力されます。ログが不要な場合はフィルターしてしまいましょう。

info: Azure.Core[1]
      Request [baa4633f-f25e-4911-ae40-382cde9beecc] HEAD http://127.0.0.1:10000/devstoreaccount1/container-26ab3481cf02481ea412c1a74bec70fc/blob-2d8b191add784dce94487dda9b933922
      x-ms-version:2020-08-04
      Accept:application/xml
      x-ms-client-request-id:baa4633f-f25e-4911-ae40-382cde9beecc
      x-ms-return-client-request-id:true
      User-Agent:azsdk-net-Storage.Blobs/12.9.1,(.NET 5.0.9; Microsoft Windows 10.0.19043)
      x-ms-date:Sun, 15 Aug 2021 14:28:07 GMT
      Authorization:REDACTED
      client assembly: Azure.Storage.Blobs
info: Azure.Core[5]
      Response [baa4633f-f25e-4911-ae40-382cde9beecc] 200 OK (00.0s)
      Server:Azurite-Blob/3.14.0
      x-ms-creation-time:Sun, 15 Aug 2021 14:28:07 GMT
      x-ms-blob-type:BlockBlob
      x-ms-lease-state:available
      x-ms-lease-status:unlocked
      ETag:"0x1A7A5342F76A910"
      x-ms-client-request-id:baa4633f-f25e-4911-ae40-382cde9beecc
      x-ms-request-id:907fe1d4-06a5-4388-a94d-a24081f643c7
      x-ms-version:2020-10-02
      Date:Sun, 15 Aug 2021 14:28:07 GMT
      Accept-Ranges:bytes
      x-ms-server-encrypted:true
      x-ms-access-tier:Hot
      x-ms-access-tier-inferred:true
      x-ms-access-tier-change-time:Sun, 15 Aug 2021 14:28:07 GMT
      Connection:keep-alive
      Keep-Alive:REDACTED
      Last-Modified:Sun, 15 Aug 2021 14:28:07 GMT
      Content-Length:9
      Content-Type:application/octet-stream
      Content-MD5:VDksDolm8gTbqpBPYkw5OA==

クライアント生成

まず Extensions の最初の機能であるクライアント生成を利用してみます。 BlobServiceClient を DI へ登録するには AddBlobServiceClient を呼び出します。

Program.cs
// Azureクライアント管理
services.AddAzureClients(builder =>
{
    // クライアントの登録
    // 1.接続文字列で登録
    builder.AddBlobServiceClient(ConnectionString);

    // 2.URIとCredentialをそれぞれ登録
    Uri serviceUri = new Uri("http://127.0.0.1:10000/devstoreaccount1");
    StorageSharedKeyCredential credential = new StorageSharedKeyCredential(
        accountName: "devstoreaccount1",
        accountKey: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==");
    builder.AddBlobServiceClient(serviceUri, credential);

    // 3.AccountKey以外のCredentialで登録
    builder.AddBlobServiceClient(serviceUri).WithCredential(new DefaultAzureCredential());
    builder.AddBlobServiceClient(serviceUri).WithCredential(new ClientSecretCredential("TenantID", "ClientID", "ClientSecret"));

    // ※Azure接続で利用する共通のCredentialを指定しておけばWithCredentialは不要
    builder.UseCredential(new DefaultAzureCredential());
    builder.AddBlobServiceClient(serviceUri);
});

AddBlobServiceClient を利用して BlobServiceClient を DI へ登録する方法は上の接続先を手動で設定する3通りと次の項で説明する1通り、合わせて4通りあります。
そしてこれらの方法で DI へ登録したクライアントを利用するには、利用する先のクラスのコンストラクターで BlobServiceClient を引数に指定します。

Program.cs
private readonly ILogger<Program> _logger;
private readonly BlobServiceClient _client;

public Program(ILogger<Program> logger, BlobServiceClient client)
{
    _logger = logger;
    _client = client;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
    try
    {
        // Blobクライアントの初期化からBlobのアップロードまで
        BlobContainerClient container = _client.GetBlobContainerClient($"container-{Guid.NewGuid():N}");
        await container.CreateIfNotExistsAsync(cancellationToken: cancellationToken);

        ...
}

Configurationの利用

続いて Configuration に設定した接続情報から BlobServiceClient を DI へ登録します。 BlobStorage への接続だと以下の2通りがサポートされています。

appsettings.json
{
  "Azure": {
    "Storage": {
      
      // 1.接続文字列
      "Blob_ConnectionString": {
        "ConnectionString": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
      },

      // 2.URI
      "Blob_AccountKey": {
        "ServiceUri": "http://127.0.0.1:10000/devstoreaccount1",
        "Credential": {
          "AccountName": "devstoreaccount1",
          "AccountKey": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
        }
      }

    }
  }
}

Configuration を作成したら、 AddBlobServiceClient の引数に Configuration Section を渡します。

Program.cs
// Azureクライアント管理
services.AddAzureClients(builder =>
{
    // クライアントの登録
    // 1.接続文字列をConfigurationから設定
    builder.AddBlobServiceClient(context.Configuration.GetSection("Azure:Storage:Blob_ConnectionString"));

    // 2.URIとCredentialをConfigurationから設定
    builder.AddBlobServiceClient(context.Configuration.GetSection("Azure:Storage:Blob_AccountKey"));
});

Configuration や Option の取り回しを全く考えること無くクライアントを利用することができるため、スッキリ書けてとても良いですね。

クライアントの名前管理

最後に、複数環境への接続が必要な場合などに使用するクライアントの名前管理を利用してみます。
まずは名前の登録について、これは AddBlobServiceClient の戻りに対して WithCredential と同じように WithName というメソッドを呼び出すことで実装できます。

Program.cs
// Azureクライアント管理
services.AddAzureClients(builder =>
{
    // クライアントの登録
    // 1.接続文字列をConfigurationから "ConnectionString" という名前で設定
    builder.AddBlobServiceClient(context.Configuration.GetSection("Azure:Storage:Blob_ConnectionString"))
        .WithName("ConnectionString");

    // 2.URIとCredentialをConfigurationから "AccountKey" という名前で設定
    builder.AddBlobServiceClient(context.Configuration.GetSection("Azure:Storage:Blob_AccountKey"))
        .WithName("AccountKey");

名前付きで登録したクライアントを利用するには、利用するクラスのコンストラクターにクラスを直接指定せず IAzureClientFactory<TClient> を指定します。 IAzureClientFactory には CreateClient メソッドが生えているので、そこから欲しいクライアントの名前を指定してクライアントを取り出しましょう。

Program.cs
private readonly ILogger<Program> _logger;
private readonly IAzureClientFactory<BlobServiceClient> _clientFactory;

public Program(ILogger<Program> logger, IAzureClientFactory<BlobServiceClient> clientFactory)
{
    _logger = logger;
    _clientFactory = clientFactory;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
    try
    {
        // Blobクライアントの初期化からBlobのアップロードまで
        BlobServiceClient client = _clientFactory.CreateClient("ConnectionString");
        BlobContainerClient container = client.GetBlobContainerClient($"container-{Guid.NewGuid():N}");
        await container.CreateIfNotExistsAsync(cancellationToken: cancellationToken);

        ...
}

Discussion