しばやん雑記

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

Azure Functions v2 でアプリケーション設定と接続文字列を読み込む

.NET Framework 版では ConfigurationManager を使っておけば、アプリケーション設定と接続文字列を App Service がいい感じに環境変数からオーバーライドしてくれましたが、.NET Core 版の Azure Functions を使っている場合はクラスが無いので調べました。

GitHub の Issue が引っ掛かったので読むと、ASP.NET Core の仕組みを使ってねとありました。

Azure Functions v2 は ASP.NET Core ベースなので納得いく回答ですが、ConfigurationManager を使う場合と比べて準備が必要になるので少し手間です。

ここ数日の間にコメントで動くコードが紹介されていたので、ありがたく使わせてもらいました。

ConfigurationBuilder を使って local.settings.json をオプションとして読みつつ、環境変数からも読み込むように設定を行って IConfigurationRoot を作成すれば読めるようになります。

上の記事のサンプルでは SetBasePath を使っていましたが、カレントディレクトリがちゃんと設定されているのでファイル名だけにしました。ExecutionContext を取るのが嫌だったんです。

public static class Function1
{
    static Function1()
    {
        var builder = new ConfigurationBuilder()
                        .AddJsonFile("local.settings.json", true)
                        .AddEnvironmentVariables();

        Configuration = builder.Build();
    }

    private static IConfigurationRoot Configuration { get; }

    [FunctionName("Function1")]
    public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, TraceWriter log)
    {
        log.Info("C# HTTP trigger function processed a request.");

        log.Info($"AppSettings:SlotName {Configuration["SlotName"]}");
        log.Info($"ConnectionString:DefaultSqlConnection = {Configuration.GetConnectionString("DefaultSqlConnection")}");

        return new OkObjectResult($"Hello, world");
    }
}

起動時に 1 回だけ ConfigurationBuilder が走るようにしてますが、設定が変更されると再起動されるはずなので問題ありません。非常に単純なコードですが、ConfigurationManager よりは手間です。

ちなみに local.settings.json は以下のように適当にキーを追加しておきました。

f:id:shiba-yan:20180203012828p:plain

これで Function をローカルで実行してみると、ちゃんと local.settings.json に書いた内容が読み込まれます。

f:id:shiba-yan:20180203010632p:plain

ローカルでは local.settings.json に書かれた値が読み込まれますが、Azure 上では例によって Azure Portal から設定して、特殊な環境変数経由で読み込む必要があります。

これまで特に違和感なく使っていたのですが、ASP.NET Core 側で App Service 向けのコードが追加されていたので、気にせずとも値を読み込めるようになってました。

Azure .NET Core Application Settings – Http Client Protocol Issues (and other fun stuff I support)

なので安心して Azure Functions の設定からアプリケーション設定と接続文字列を追加します。

f:id:shiba-yan:20180203011322p:plain

設定後に Function を実行すると、ログに設定した値がちゃんと出力されているので、環境変数経由で正しく読み込まれたことが確認出来ました。この簡単さは相変わらず良いです。

f:id:shiba-yan:20180203011517p:plain

Function が 1 つの場合ならこれでもよいかもしれないですが、大体は複数の Function から設定を参照したいことばかりだと思うので、専用のクラスを作って対応しておきます。

文字列でキー名を指定するのは嫌いなので、大体プロパティにしてしまうタイプです。

public class Settings
{
    public Settings()
    {
        var builder = new ConfigurationBuilder()
                        .AddJsonFile("local.settings.json", true)
                        .AddEnvironmentVariables();

        _configuration = builder.Build();
    }

    private readonly IConfigurationRoot _configuration;

    public string SlotName => _configuration[nameof(SlotName)];

    public string DefaultSqlConnection => _configuration.GetConnectionString(nameof(DefaultSqlConnection));

    public static Settings Instance { get; } = new Settings();
}

Azure Functions は ASP.NET Core で言うところの Startup クラスが無いので、全体で 1 回だけ行いたい場合の処理が地味に書きにくいと思いました。CodePageEncodingProvider の追加とかも書きにくい。

このクラスを使って Function を微修正すれば、アプリケーション全体で参照できるようになってます。

public static class Function1
{
    [FunctionName("Function1")]
    public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, TraceWriter log)
    {
        log.Info("C# HTTP trigger function processed a request.");

        log.Info($"AppSettings:SlotName {Settings.Instance.SlotName}");
        log.Info($"ConnectionString:DefaultSqlConnection = {Settings.Instance.DefaultSqlConnection}");

        return new OkObjectResult($"Hello, world");
    }
}

みんな違和感なく ConfigurationBuilder を使って書いてるとも思いにくいし、ドキュメントやテンプレートが無いのは地味に困りましたね。

実際に悩んだ部分かつ、検索しても見つからなかったのでメモ代わりに残します。