Azure FunctionsのDIでできること・できないこと

2月から新しいプロジェクトがスタートし、私は現在、新規Webアプリケーションの設計・開発を進めています。今回のプロジェクトでは、先日当サイトでも紹介された、Azure Static Web Appsを利用して開発しています。いざ使ってみると実感するのですが、GitHubとの連携による自動デプロイはなかなか便利です。開発が非常に捗りますね。

さてこのプロジェクト、私はバックエンドのWebAPIの実装を主に担当しているのですが、Azure Static Web Appsの構成にもあった通り、今回はAzure Functionsで開発しています。ここ最近携わってきたWebアプリ開発では、ASP.NET Core Webアプリケーションとしてバックエンドを開発することが多かったため、インタフェースや入出力の際など、細かな部分で作法が違い、戸惑うことも多いです。特に苦労しているのが、ASP.NET Coreで大いに活用していたDI (Dependency Injection) の設定。Azure Functionsではいろいろと機能が制限されている?ようで、痒いところに手が届かない、そんな感じで非常にもどかしいです。。。

Azure FunctionsでDIを利用する方法

Azure Functionsはv2からDIを正式にサポートしています。ただ、Azure Functionsプロジェクト作成直後はDIを利用する設定になっていません。利用するには自分でStartupクラスを定義する必要があります。

Startupクラスは、FunctionsStartupを継承して作成します。

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(MapQuest.TrafficSafety.API.Startup))]
namespace MapQuest.TrafficSafety.API
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            // ここでDIコンテナにサービス登録などを行う
            builder.Services.AddHttpClient();
        }
    }
}

DIコンテナに登録されたオブジェクトは、関数のコンストラクタでインジェクションして使うことができます。よくあるDIパターンですね。

namespace MapQuest.TrafficSafety.API
{
    public class SampleFunction
    {
        private readonly IHttpClientFactory _clientFactory;

        public SampleFunction(IHttpClientFactory clientFactory)
        {
            _clientFactory = clientFactory;
        }

        [FunctionName("Sample")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
 
            ILogger log)
        {
            var client = _clientFactory.CreateClient();
            // 省略
        }
    }
}

Azure FunctionsのDIでできないこと

実は、Azure FunctionsのDIに行き着いたのは、NTS(NetTopologySuite)のオブジェクトのシリアライズを実現するためでした。Azure FunctionsもASP.NET Core Webアプリケーションもそうなのですが、WebAPIのインタフェースであるメソッドの戻り値にオブジェクトを渡すと、シリアライズされてレスポンスを返してくれる便利機能があります。しかし、NTSのオブジェクトを渡してみたところ、シリアライズに失敗しているらしく、例外が発生して結果を返してくれません。

そこで思い出したのがDI。以前開発していたプロジェクトでは、確か以下の様にNTS用のJsonConverterを設定していたような…

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(
            new NetTopologySuite.IO.Converters.GeoJsonConverterFactory());
        options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
        options.JsonSerializerOptions.WriteIndented = true;
    });
    // 省略
}

これと同じことをAzure FunctionsのStartupに実装してやれば動くかな?と考え、以下の様に実装を試みたのですが。。。動かない!

public override void Configure(IFunctionsHostBuilder builder)
{
    builder.Services.AddMvcCore().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(
            new NetTopologySuite.IO.Converters.GeoJsonConverterFactory());
        options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
        options.JsonSerializerOptions.WriteIndented = true;
    });
}

GitHubのIssueに同じ問題に関する質問があったので確認しましたが、現行のAzure Functions v3でもJSONオプションを設定するインタフェースは存在しない、という見解の様子。

https://github.com/Azure/azure-functions-host/issues/5841

もう少し時間をかけて調べたい所ですが、調査時間も限られているので、DIによるJsonConverterの設定は諦め、別の方法で回避することにしました。ちょっと残念です。

所感

現在の開発がひと段落したら、Azure FunctionsのDI周りの仕様の詳細を確認し、改めてまとめておこうかと思います。

コメントを残す

メールアドレスが公開されることはありません。

CAPTCHA