Azure Functions は、v2 で DI を正式にサポート。
Startup
クラスを(自分で)定義し、 DI を設定することで Azure Functions にインジェクションすることができるようになる。
目次
検証環境
- Azure Functions v3
- Microsoft.Azure.Functions.Extensions v1.1.0
- Microsoft.Extensions.Http v3.1.11
- v5.0.0 だとMicrosoft .Extentions.DependencyInjection のライブラリがバージョン不一致を起こす
- Azure.Extensions.AspNetCore.Configuration.Secrets v1.0.2
- Microsoft.Extensions.Configuration.AzureKeyVault v3.1.11
- Microsoft.Azure.Services.AppAuthentication v1.6.0
- Microsoft.Azure.KeyVault v3.0.5
- Microsoft.Extensions.Azure v1.0.0
インストール
Nuget で Microsoft.Azure.Functions.Extensions をインストールする。
使い方
基本的な使い方
今回は DI するために適当なクラスを用意する。
public interface IMyService { }
public class MyServiceA : IMyService { }
public class MyServiceB : IMyService { }
public class MyServiceC : IMyService { }
まず FunctionsStartup
を継承した Statup
クラスを新規作成する。
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
[assembly: FunctionsStartup(typeof(FunctionApp1.Startup))]
namespace FunctionApp1
{
class Startup : FunctionsStartup
{
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
base.ConfigureAppConfiguration(builder);
}
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddTransient<MyServiceA>();
builder.Services.AddTransient<IMyService, MyServiceA>();
builder.Services.AddScoped<MyServiceB>();
builder.Services.AddScoped<IMyService, MyServiceB>();
builder.Services.AddSingleton<MyServiceC>();
builder.Services.AddSingleton<IMyService, MyServiceC>();
}
}
}
アセンブリ属性 で FunctionsStartupAttribute
に Startup
クラスを指定する。
IFunctionsHostBuilder
を使ってサービスを登録する。登録メソッドは登録するサービスの有効期間毎に別れており、各インスタンスの有効期間は以下の3通りになる。
- 一時的(Transient)
- DIでサービスを取得するたびに新しいインスタンスが生成される
- スコープ(Scoped)
- シングルトン(Singleton)
- ホストの有効期間と一致する
DocumentClient
や HttpClient
など接続やクライアントに推奨されている
なお IFunctionsConfigurationBuilder
で FunctionsHostBuilderContext
を取得すると以下の3つの値が取得できる。
var context = builder.GetContext();
var rootPath = context.ApplicationRootPath;
var configuration = context.Configuration;
var environment = context.EnvironmentName;
関数側は、 Azure Functions のクラスとメソッドの static
を外し、コンストラクタを実装してインジェクションするための引数を追加する。
public class Function1
{
private readonly MyServiceA _serviceA;
private readonly MyServiceB _serviceB;
private readonly MyServiceC _serviceC;
private readonly IMyService _service;
public Function1(
MyServiceA serviceA,
MyServiceB serviceB,
MyServiceC serviceC,
IMyService service)
{
_serviceA = serviceA;
_serviceB = serviceB;
_serviceC = serviceC;
_service = service;
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
return new OkObjectResult("This HTTP triggered function executed successfully.");
}
}
参考:
IConfiguration
は Startup
クラスの中で何もしなくてもインジェクションされる。ローカル実行時も ConfigurationBuilder
と違い明示的に local.settings.json
を追加しなくて良い。
public class Function1
{
private readonly IConfiguration _configuration;
public Function1(IConfiguration configuration)
{
_configuration = configuration;
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation(_configuration["AzureWebJobsStorage"]);
log.LogInformation(_configuration["APPLICTION_SETTINGS_VALUE"]);
log.LogInformation(_configuration.GetConnectionString("DB_CONNECTION_STRING"));
return new OkObjectResult("This HTTP triggered function executed successfully.");
}
}
HttpClient を利用する
Nuget で Microsoft.Extensions.Http をインストールする。
v5.0.0 だとMicrosoft .Extentions.DependencyInjection のライブラリがバージョン不一致を起こすので注意。
サービスに HttpClient
を登録する。(この1行なくてもインジェクションされるけどどうなんだろうか)
class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
}
}
Functions 側はコンストラクタで受け取る。
private readonly HttpClient _httpClient;
public Function1(HttpClient httpClient)
{
_httpClient = httpClient;
}
ロガーを利用する
host.json
に logLevel
を追加する。
FunctionApp1
の部分は出力対象とする名前空間やクラス名を指定する。
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingExcludedTypes": "Request",
"samplingSettings": {
"isEnabled": true
}
},
"logLevel": {
"FunctionApp1": "Information"
}
}
}
Functions で利用する場合は、コンストラクタで受け取るだけ。
デフォルトで付いてくるメソッド引数の log
は削除して問題ない。
private readonly ILogger<Function1> _logger;
public Function1(ILogger<Function1> logger)
{
_logger = logger;
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req)
{
_logger.LogInformation("ILogger<Functions1> _logger");
return new OkObjectResult("This HTTP triggered function executed successfully.");
}
Functions 以外のクラスで利用する場合は、対象のクラスに ILogger
をインジェクションしてもらうために、Startup
クラスの Configure
メソッドでサービスに登録する。
こうすることでロガーをクラス間でたらい回しにしなくて済む。
public class LogWriter
{
private readonly ILogger<LogWriter> _logger;
public LogWriter(ILogger<LogWriter> logger)
{
_logger = logger;
}
public void Write()
{
_logger.LogInformation("ILogger<LogWriter> _logger");
}
}
class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddTransient<LogWriter>();
}
}
Functions 側では目的のクラスをインジェクションしてもらいそれを利用する。
private readonly ILogger<Function2> _logger;
private readonly LogWriter _logWriter;
public Function2(
ILogger<Function2> logger,
LogWriter logWriter)
{
_logger = logger;
_logWriter = logWriter;
}
[FunctionName("Function2")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req)
{
_logger.LogInformation("ILogger<Functions2> _logger");
_logWriter.Write();
return new OkObjectResult("This HTTP triggered function executed successfully.");
}
参考:Azure Functions におけるロガーの扱いとフィルタの注意点 - しばやん雑記
appsettings.json を読み込む
ASP.NET Core でよく使われる appsettings.json
を読み込む。local.settings.json
では配列や特殊なオブジェクトなどを定義できない。
プロジェクトに appsettings.json
を追加して、出力ディレクトリに「常にコピーする」ように設定する。実行環境毎の設定は appsettings.Development.json
などを追加する。
{
"SampleAppSettings": {
"Name": "SampleAppSettings",
"Items": [
{
"Key": "Key1",
"Message": "Message1"
},
{
"Key": "Key2",
"Message": "Message2"
}
]
}
}
設定を受け取るクラスを定義する。
public class SampleAppSettings
{
public string Name { get; set; }
public Item[] Items { get; set; }
}
public class Item
{
public string Key { get; set; }
public string Message { get; set; }
}
Startup
クラスで appsettings.json
を読み込んで、設定を受け取るクラスを登録する。
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
base.ConfigureAppConfiguration(builder);
builder.ConfigurationBuilder
.AddJsonFile(Path.Combine(context.ApplicationRootPath, "appsettings.json"), optional: true, reloadOnChange: false)
.AddJsonFile(Path.Combine(context.ApplicationRootPath, $"appsettings.{context.EnvironmentName}.json"), optional: true, reloadOnChange: false)
.AddEnvironmentVariables();
}
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.Configure<SampleAppSettings>(context.Configuration.GetSection("SampleAppSettings"));
}
参考:構成ソースのカスタマイズ - .NET Azure Functions で依存関係の挿入を使用する
Functions 側は IOption<SampleAppSettings>
で受け取る。
public Function1(IOptions<SampleAppSettings> sampleAppSettings)
{
}
参考:Azure Functions V2 の Startup.cs で appsettings.json を読み込む(2019年5月バージョン) - BEACHSIDE BLOG
設定にオブジェクト単位ではなく、個別のパラメータを定義してバインドすることもできる。
local.settings.json
に MyOptions:Value1
と MyOptions:Value2
を追加する。
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"MyOptionsSettings:Value1": "local.settings.json-Value1",
"MyOptionsSettings:Value2": "local.settings.json-Value2"
}
}
設定を受け取るクラスを定義する。
public class MyOptions
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
Startup
クラスで設定を読み込んで、MyOptions
クラスを MyOptionSettings
セクションにバインドする。
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddOptions<MyOptions>()
.Configure<IConfiguration>((target, configuration) =>
{
configuration.GetSection("MyOptionsSettings").Bind(target);
});
}
Functions 側は IOptions<MyOptions>
で受け取る。
public Function1(IOptions<MyOptions> options)
{
}
参考:Azure Functions で設定情報を使いたい - Qiita
オプション
環境変数を含むアプリ設定に定義されている値は IConfiguration
で読み取ることできる。
さらに、ASP.NET Core ではオプションパターンを使って IConfiguration
から任意のクラスに値を抽出することができる。
このパターンで関連する設定をグループ化する。
docs.microsoft.com
参考:
Key Valut を読み込む
Azure Key Vault のアクセスポリシーは「取得」と「一覧」を設定する。
Key Vault のエンドポイントをアプリケーション設定などに保存する。local.settings.json
の例。
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"KeyVaultEndpoint": "https://<KeyVaultName>.vault.azure.net/"
}
}
Nuget で以下のパッケージをインストールする。
- Azure.Extensions.AspNetCore.Configuration.Secrets
Startup
で、Key Vault のエンドポイントを使って Configuration に追加する。
class Startup : FunctionsStartup
{
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
var builtConfig = builder.ConfigurationBuilder.Build();
builder.ConfigurationBuilder.AddAzureKeyVault(new Uri(builtConfig["KeyVaultEndpoint"]), new DefaultAzureCredential());
}
}
※Options を使う場合は、「appsettings.json を読み込む」の項目を参照。
Functions 側は インジェクションした IConfiguration
からシークレットの名前で取得できる。
public class Function1
{
private readonly IConfiguration _configuration;
public Function1(IConfiguration configuration)
{
_configuration = configuration;
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation(_configuration["keys-blobconnectionstring"]);
return new OkObjectResult("This HTTP triggered function executed successfully.");
}
}
以下は、既に非推奨となっているライブラリであるが、実装例として残しておく。
Nuget で以下のパッケージをインストールする。
Startup
で、Key Vault を参照して Configuration に追加する。
class Startup : FunctionsStartup
{
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
var builtConfig = builder.ConfigurationBuilder.Build();
var tokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback));
builder.ConfigurationBuilder.AddAzureKeyVault(builtConfig["KeyVaultEndpoint"], keyVaultClient, new DefaultKeyVaultSecretManager());
}
}
参考:
Blob Storage の Client を利用する
Nuget で以下のパッケージをインストールする。
- Microsoft.Extensions.Azure
- Azure.Storage.Blobs
Startup
で、Blob Service Client を追加する。接続先が複数ある場合は名前を付けておく。
class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var context = builder.GetContext();
builder.Services.AddAzureClients(x =>
{
var connectionString = context.Configuration["AzureWebJobsStorage"];
x.AddBlobServiceClient(connectionString);
});
}
}
Functions 側では、BlobServiceClient
を受け取る。
public Function2(BlobServiceClient blobClient)
{
}
名前付きのクライアントを取得するには、IAzureClientFactory<BlobServiceClient>
をインジェクションしてもらう。
public Function2(IAzureClientFactory<BlobServiceClient> clientFactory)
{
var namedClient = clientFactory.CreateClient("BlobClientA");
}
参考:【.NET】BlobServiceClientFactoryはあったんだ! – 10bace LOG
Table Storage の Client を利用する
Nuget で以下のパッケージをインストールする。
Startup
で、CloudTableClient
を追加する。
class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var context = builder.GetContext();
builder.Services.AddSingleton(_ =>
{
var connectionsString = context.Configuration["AzureWebJobsStorage"];
var storageAccount = CloudStorageAccount.Parse(connectionsString);
return storageAccount.CreateCloudTableClient();
});
}
}
Functions 側では、CloudTableClient
を受け取る。
public Function2(CloudTableClient tableClient)
{
}
昔の書き込みはスレッドセーフじゃないってあるが、最近はライフサイクルは Singleton で良さそう。
Queue Storage の Client を利用する
QueueClient
のインスタンスは queuName
に結びつくため、キュー毎にラップしたクラスなどを用意して別々に生成する必要がある。
yotiky.hatenablog.com
App Configuration を読み込む
App Configuration のエンドポイントをアプリケーション設定などに保存する。
Nuget で以下のパッケージをインストールする。
- Azure.Extensions.AspNetCore.Configuration.Secrets
Startup
で、App Configuration のエンドポイントを使って Configuration に追加する。
class Startup : FunctionsStartup
{
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
var builtConfig = builder.ConfigurationBuilder.Build();
builder.ConfigurationBuilder.AddAzureAppConfiguration(options =>
options.Connect(new Uri(builtConfig["AppConfigEndpoint"]), new ManagedIdentityCredential()));
}
}
設定や接続文字列を使うなど詳細は以下を参照。
yotiky.hatenablog.com
関連記事