しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Core 1.0 の Dependency Injection を使って Azure Storage を使いやすくする

Azure を使った開発では欠かせないのが Storage ですが、最新の Azure Storage Client 7.0.1-preivew からは .NET Core に対応しているので、ASP.NET Core アプリに簡単に組み込めます。

これまでと変わらずに NuGet を使ってインストールするだけで準備は完了します。プレビューなので -Pre スイッチを付けないとインストール出来ないので、少しだけ注意が必要です。

Install-Package WindowsAzure.Storage -Pre

インストールしてしまえばこれまでと同じように使えますが、ASP.NET Core では設定の必要なサービスを ConfigureService 内で DI コンテナに登録するのが一般的になりそうなので、他のサービスの真似をしつつ Azute Storage を使えるようにします。

接続文字列は Configuration.GetConnectionString で取れるので、最低限のコードは以下のようになります。

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(_ =>
        CloudStorageAccount.Parse(Configuration.GetConnectionString("DefaultStorageConnection")));
}

これでコンストラクタに CloudStorageAccount を受け取るようにすると、設定に書いた接続文字列で初期化済みのストレージアカウントが自由に扱えるようになります。

public class StorageController
{
    public StorageController(CloudStorageAccount storageAccount)
    {
        // BlobClient を作成する
        var blobClient = storageAccount.CreateCloudBlobClient();
    }
}

Startup クラスで接続文字列を設定するので、使う側は気にせずに利用できるようになります。

ASP.NET Core の Dependency Injection は IServiceProvider を実装しているので、GetService メソッドで任意の型のインスタンスを取得出来ます。さらに CloudBlobClient を DI で取得できるようにしてみます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(_ =>
        CloudStorageAccount.Parse(Configuration.GetConnectionString("DefaultStorageConnection")));

    // DI で CloudStorageAccount を取得して CloudBlobClient を作成して返す
    services.AddScoped(provider => provider.GetService<CloudStorageAccount>().CreateCloudBlobClient());
}

AddScoped メソッドでは IServiceProvider が渡されるので、簡単に他のインスタンスが取得できます。実際に実行して試してみたところ、ちゃんと CloudBlobClient のインスタンスが取れていることが分かります。

これで使う分には問題なさそうですが、ASP.NET Core では IServiceCollection への拡張メソッドを作成して、DI コンテナへの追加から設定までを行うのが作法っぽいので、実際にコードを用意してみました。

オプションとして開発ストレージを使うのかどうかの項目を追加してみました。

public static class ServiceCollectionExtensions
{
    public static void AddAzureStorage(this IServiceCollection services, Action<AzureStorageOptions> setupAction)
    {
        services.AddSingleton(_ => AzureStorageOptionsFactory(setupAction));

        services.AddScoped(provider =>
        {
            var options = provider.GetRequiredService<AzureStorageOptions>();

            return options.UseDevelopmentStorage ? CloudStorageAccount.DevelopmentStorageAccount : CloudStorageAccount.Parse(options.ConnectionString);
        });

        services.AddScoped(provider => provider.GetRequiredService<CloudStorageAccount>().CreateCloudBlobClient());
    }

    private static AzureStorageOptions AzureStorageOptionsFactory(Action<AzureStorageOptions> setupAction)
    {
        var options = new AzureStorageOptions();

        setupAction(options);

        return options;
    }
}

public class AzureStorageOptions
{
    public bool UseDevelopmentStorage { get; set; }
    public string ConnectionString { get; set; }
}

public static class AzureStorageOptionsExtensions
{
    public static AzureStorageOptions UseDevelopmentStorage(this AzureStorageOptions options)
    {
        options.UseDevelopmentStorage = true;

        return options;
    }

    public static AzureStorageOptions UseStorageAccount(this AzureStorageOptions options, string connectionString)
    {
        options.ConnectionString = connectionString;

        return options;
    }
}

少し長めのコードですが、やっていることは大して変わっていません。オプションを保持するクラスはシングルトンにして、最初の 1 回だけ初期化するようにして後は使いまわすようにしています。

用意した拡張メソッドを使って ConfigureServices を修正すると以下のようになります。

public void ConfigureServices(IServiceCollection services)
{
    services.AddAzureStorage(options =>
        options.UseStorageAccount(Configuration.GetConnectionString("DefaultStorageConnection")));
}

最初に出したコードより分かりやすくなりました。本当はちゃんと Builder を用意して、最終的な成果物として Option を返すようにしないといけないのですが、今回はサンプルということで省きました。

ASP.NET Core では設定が必要な処理は Startup に集めておくと幸せになれると思います。