ROMANCE DAWN for the new world

Microsoft Azure を中心とした技術情報を書いています。

ローカルでの ASP.NET Core アプリケーション開発で Azurite V3 を試してみた

Azure がリリースされて以来、かれこれ10年以上もお世話になってきた Azure Storage Emulator が非推奨となり、今後は Azurite を使うことが推奨されています。
最新の Azure Storage API がサポートされるのは Azurite になるので、ローカルでの ASP.NET Core アプリケーション開発を題材に試してみました。
docs.microsoft.com

Azurite をインストールする

Azurite V3 になって、Blob・Queue・Table の基本的な機能がサポートされるようになりました。接続文字列も UseDevelopmentStorage=true が使えます。
github.com
Azurite V3 のインストールには、いくつかの方法があります。

  • npm でインストールする
  • Docker を使う
  • VS Code 拡張機能を使う

今回は、ローカル開発端末の影響を受けにくく、CI/CD パイプラインの自動テストにも導入しやすい Docker を選択しました。

$ docker run --rm -it -p 10000:10000 -p 10001:10001 -p 10002:10002 -v c:/azurite:/data mcr.microsoft.com/azure-storage/azurite:3.12.0

この docker run コマンドを実行するだけで、Azurite V3 を実行できます。

f:id:TonyTonyKun:20211101175835p:plain

Storage のデータは永続化したいので、c:/azurite:/data ディレクトリをマウントしています。Docker Image のバージョンは、latest でローカルに意図せずキャッシュされるのが好きではないので、バージョンを指定しています。--rm -it のオプションはお好みで。

Blob

Azure Blob Storage SDK v12 を使って、Azurite の Blob Storage を操作してみました。
gooner.hateblo.jp

Azurite の Blob Storage に接続する際に、「The value for one of the HTTP headers is not in the correct format」という例外が発生してしまいます。
こちらの Issues に Bug として報告されており、workaround が提示されています。
github.com
使用する API バージョンをデフォルトの V2020_10_02 ではなく、V2020_06_12 に下げることで例外を回避できます。

public void ConfigureServices(IServiceCollection services)
{
    var options = new BlobClientOptions
    (
        BlobClientOptions.ServiceVersion.V2020_06_12
    );
    services.AddSingleton(new BlobServiceClient(Configuration["WebApi:StorageConnection"], options));
}

Table

Azure Data Tables SDK を使って、Azurite の Table Storage を操作してみました。
gooner.hateblo.jp

Queue

Azure Queue Storage SDK v12 を使って、Azurite の Table Storage を操作してみました。
gooner.hateblo.jp

まとめ

ローカルでの ASP.NET Core アプリケーション開発で、Azurite V3 を試してみました。
現時点では Blob だけ少し注意が必要ですが、いずれ Bug が修正されるはずです。
Azure Storage Emulator から Azurite への移行を検討してみてください。

ASP.NET Core Web API で multipart / form-data を使ってファイルをアップロードする

Azure Storage Blobs client library を使って Blob Storage にアクセスできますが、

  • クライアントに Azure Storage の情報を公開したくない
  • アプリケーション側でアクセスログを取りたい

といった要件があった場合、ASP.NET Core Web API を経由して Blob にアクセスさせることがあります。ファイルをアップロードする際には、Content-Type に multipart / form-data を使うことが多いです。
過去に ASP.NET の記事を書きましたが、その内容を ASP.NET Core にアップデートした内容となります。
gooner.hateblo.jp
gooner.hateblo.jp

Blob Storage へのアクセス

Azure Blob Storage SDK v12 を使用する方法をまとめたので、こちらの記事を参照してください。
gooner.hateblo.jp

Controller クラス

Controller クラスのコンストラクタでは、上記の記事で作成した Blob Storage にアクセスするクラスの FileRepository のインスタンスを DI で受け取ります。

[Route("api/[controller]")]
[Produces("application/json")]
[ApiController]
public class FilesController : ControllerBase
{
    private readonly IFileRepository _repository;

    public FilesController(IFileRepository repository)
    {
        _repository = repository;
    }
}

Blob にファイルをアップロードする

ASP.NET のときは、マルチパートのコンテンツを読み取るプロバイダークラスを使うなど面倒な実装が必要でした。
ASP.NET Core では、アップロードされたファイルが IFormFile に読み込まれるのでシンプルに実装できるようになりました。

public class UploadRequest
{
    public string FileName { get; set; }
    public IFormFile File { get; set; }
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> Upload([FromForm] UploadRequest upload)
{
    var fileName = upload.FileName;
    var file = upload.File;

    if (file.Length > 0)
    {
        using (var fileStream = file.OpenReadStream())
        {
            await _repository.UploadAsync(fileStream, fileName, file.ContentType);
        }
        return Ok();
    }
    return BadRequest();
}

Blob からファイルをダウンロードする

ダウンロードの API の場合は、FileContentResult の ActionResult を返します。

[HttpGet("{fileName}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> DownloadAsync(string fileName)
{
    var fileContents = await _repository.DownloadAsync(fileName);
    return File(fileContents, "application/octet-stream", fileName);
}

Swagger ドキュメント

ASP.NET のときは、Swashbuckle の IOperationFilter インターフェイスを使うなど面倒な実装が必要でした。
gooner.hateblo.jp
ASP.NET Core では、IFormFile が引数に使われたアクションメソッドを判定して Swashbuckle 側で対応してくれるので、特別な実装は必要ありません。
Visual Studio でプロジェクトを作成する際に、Open API サポートを有効にする設定を行うだけで OK です。

f:id:TonyTonyKun:20211030170204p:plain

ASP.NET Core アプリケーションで Azure Blob Storage SDK v12 を使用する

ASP.NET Core アプリケーションにおいて、Azure Blob Storage SDK v12 を使用する方法をまとめておきます。
公式ドキュメントに記載のある通りなので、個人的な備忘録です。
github.com

Azure Storage の接続情報管理

Azure Storage の接続情報は、Azure Key Vault および Secret Manager で管理することを前提とします。詳細は、こちらの記事を参照してください。
gooner.hateblo.jp

NuGet パッケージのインストール

現時点で最新の Azure Storage Blobs client library である v12 を使います。

Install-Package Azure.Storage.Blobs -Version 12.10.0

www.nuget.org

ASP.NET Core アプリケーションの初期化処理

BlobServiceClient クラスのインスタンスを DI コンテナーに登録します。

public void ConfigureServices(IServiceCollection services)
{
    // Blob Storage
    services.AddSingleton(new BlobServiceClient(Configuration["WebApi:StorageConnection"]));

    // Repository
    services.AddTransient<Infrastructure.IFileRepository, Infrastructure.FileRepository>();
}

FileRepository クラスは、Blob Storage にアクセスするためのクラスとして作ることにしました。コンテナーの名前は、sample としました。

public interface IFileRepository
{
    Task UploadAsync(Stream fileStream, string fileName, string contentType);
    Task<byte[]> DownloadAsync(string fileName);
    Task<IList<string>> GetAsync();
}

public class FileRepository : IFileRepository
{
    private readonly BlobContainerClient _container;
    private const string ContainerName = "sample";

    public FileRepository(BlobServiceClient blobServiceClient)
    {
        _container = blobServiceClient.GetBlobContainerClient(ContainerName);
    }
}

Blob にファイルをアップロードする

public async Task UploadAsync(Stream fileStream, string fileName, string contentType)
{
    var blockBlob = _container.GetBlobClient(fileName);
    await blockBlob.UploadAsync(fileStream, new BlobHttpHeaders { ContentType = contentType });
}

Blob からファイルをダウンロードする

public async Task<byte[]> DownloadAsync(string fileName)
{
    var blockBlob = _container.GetBlobClient(fileName);
    var response = await blockBlob.DownloadContentAsync();
    return response.Value.Content.ToArray();
}

Blob のリストを取得する

public async Task<IList<string>> GetAsync()
{
    var result = new List<string>();
    await foreach (var blobItem in _container.GetBlobsAsync())
    {
        result.Add(blobItem.Name);
    }
    return result;
}

ASP.NET Core Web API を経由して Blob にアクセスする

こちらの記事を参照してください。
gooner.hateblo.jp