Azure Functions (C#) で ASP.NET Core っぽく DI を利用する


概要

Azure Functions にて .NET Core で開発する際において、DIを用いた実装方法について纏めます。
ASP.NET Core にて利用したように、Serviceインスタンスの依存関係注入がやり易い形になっています。

公式のドキュメントは、下記になります。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-dotnet-dependency-injection

以下、記事を参考にさせていただきました。
https://blog.shibayan.jp/entry/20190520/1558340113
https://qiita.com/HiroyukiSakoh/items/e9e472a92a53da4d7568

環境

  • Azure Functions v3
  • Windows10
  • Visual Studio 2019
  • .NET Core 3.1

コードサンプル

サンプルコードを Github にアップしています。
https://github.com/tYoshiyuki/azure-functions-di-sample
(※) コードサンプルを Azure Functions v3 に更新しました。

解説

公式の記事の通り、Microsoft.Azure.Functions.Extensions を Nuget より取得し、プロジェクトに加えます。
https://www.nuget.org/packages/Microsoft.Azure.Functions.Extensions/

Startup.cs にて DI の設定を行います。
実装例として、設定ファイルの読み込みとサービスのDI設定を行っています。

Startup.cs
[assembly: FunctionsStartup(typeof(Startup))]
namespace AzureFunctionsDiSample
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var services = builder.Services;
            var provider = services.BuildServiceProvider();
            var configuration = provider.GetRequiredService<IConfiguration>();

            // 設定ファイルの内容をバインドする
            services.Configure<AppSettings>(configuration.GetSection("AppSettings"));

            // サービスのDI設定を行う
            services.AddTransient<IHelloService, HelloService>();
            services.AddTransient<IApplication, Application>();
        }
    }
}

コードサンプルは、以下のようにクラスのレイヤー分けを行っています。

クラス名 説明
EntryPoint Azure Functions のエントリーポイント
Application メインとなるアプリケーション
HelloService ビジネスロジックサービス

Azure Functions のエントリーポイントとなるクラス (EntryPoint.cs) から、
実際のアプリケーション処理を行うクラス、サービス処理を行うクラスを別レイヤーに切り出しています。
これにより、エントリーポイントとアプリケーションの依存関係を分離し、ユニットテストを実施し易い形にしています。

DIで設定したインスタンスは、コンストラクタインジェクションで受け取ります。
ASP.NET Core で利用する形と同じようなイメージになります。

EntryPoint.cs

public EntryPoint(IApplication application)
{
    _application = application;
}

[FunctionName("FunctionsDiSample")]
public IActionResult Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    ILogger log)
{
    log.LogInformation("Function Run");

    _application.Run();

    return new OkObjectResult("Hello Functions DI Sample");
}

EntryPointでは Application を受け取り、また同様に Application では HelloService を受け取ります。
設定ファイルについては、IOptionsで受け取れます。

Application.cs

private readonly AppSettings _appSettings;
private readonly IHelloService _service;

public Application(ILogger<IApplication> logger, IOptions<AppSettings> optionsAccessor, IHelloService service) : base(logger)
{
    _appSettings = optionsAccessor.Value;
    _service = service;
}

ASP.NET Core で開発する際に意識するような クラスのレイヤー分け のノウハウが、
Azure Functionsでも生かせるようになり、今後の開発の幅が広がると思います。