最近は Azure Functions でサクッと HttpTrigger を使って REST API を書くことが多いですが、ASP.NET Core のように API 実装に必要な機能が揃っていないので、毎回同じようなコードを書いて対応してました。
具体的にはリクエストをモデルにバインドする部分や、そのバインドとされたモデルに対するバリデーションといった機能です。ASP.NET Core なら何も考えなくても良い部分ですが、Azure Functions では対応していないので再利用可能なサンプルコードを用意しました。
使い方は簡単で、これまで HttpTrigger の実装を行っていた Function のクラスを HttpFunctionBase
を継承するようにします。DI を使っているので、static を削除する必要があるはずです。
コンストラクタも追加が必要ですが、Visual Studio や ReSharper を使っていれば自動実装されるはずです。
public class Function1 : HttpFunctionBase { public Function1(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor) { } [FunctionName("Function1")] public IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req, ILogger log) { return Ok("Hello, world"); } }
この HttpFunctionBase
クラスは以下のような機能を実装しています。大体は ASP.NET Core の ControllerBase
に近くなるように実装していますが、メソッド数は全然少ないです。
IActionResult
を作成するためのヘルパーメソッドRequest
/Response
/User
へ簡単にアクセスするためのプロパティ- モデルのバリデーションを実行し
ModelState
に結果を格納するメソッド
利用可能なメソッド一覧は実装を見てもらえれば良いです。REST API の実装に便利そうなメソッドのみをピックアップして実装しています。
サンプルコードに用意している例を挙げると、POST で送信されたデータを C# のクラスにバインドし、検証属性を使ってバリデーションを行うという非常に一般的な処理が以下のようにシンプルに書けています。
あまり知られていない気がしますが HttpTrigger
を独自のクラスに付けると、いい感じにリクエストから値をバインドしてくれます。なので ASP.NET Core っぽく書けています。
public class Function1 : HttpFunctionBase { public Function1(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor) { } [FunctionName("Function1")] public IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "post")] SampleModel model, ILogger log) { if (!TryValidateModel(model)) { return BadRequest(ModelState); } return Ok(model); } } public class SampleModel { [Required] public string Name { get; set; } [Range(100, 10000)] public int Price { get; set; } }
ただしバリデーションは自動で行うことはできないため、明示的に TryValidateModel
メソッドを呼び出しています。バリデーション結果は ASP.NET Core と同様に ModelState
へ格納されるので、ヘルパーメソッドを使えばそのまま検証結果を返せます。
実際に空っぽのリクエストを投げてみると、以下のように 400 エラーと検証結果が返ってきます。
もちろん正しい形式のリクエストを投げると 200 とコンテンツが返ってくるようになります。
Azure Functions v3 から配列や enum のバインディングが正しく行えない問題が修正されたため、今回のような実装が利用できるようになりました。
地味に使い勝手が悪かったのが生の Request
や Response
を触る場合でしたが、ASP.NET Core と同様のプロパティを用意して扱いやすくしました。ログイン中のユーザー情報も User
でサクッと扱えます。
以下はレスポンスにヘッダーを付ける例ですが、特に違和感ないコードで完結しています。
public class Function2 : HttpFunctionBase { public Function2(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor) { } [FunctionName("Function2")] public IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req, ILogger log) { Response.Headers.Add("Cache-Control", "no-cache"); return Ok($"Now: {DateTime.Now}"); } }
実行してみると、ちゃんとヘッダーが付いていることが確認できます。単純なコードでした。
しっかり試したわけではないですが PipeWriter
を使って応答に直接コンテンツを書き込むことも可能そうでした。その場合は void
か Task
を返す Function を定義すればよい感じでした。
余力があれば Azure Functions の DI / Configuration 周りを含めた NuGet パッケージとして公開します。