.NET で利用する Azure Functions には In-Process Model と Isolated Model のふたつがあります。Isolated Model は .NET 6 から利用できるようになった新しいタイプです。それぞれの違いについては公式ドキュメントをご覧ください。
.NET 7 以降では Isolated Model を利用することが強制されるので、.NET 6 以前から Azure Functions を利用している方は移行作業が必要になります。そのときに必要となる作業のひとつとして「QueueTrigger 開発時に QueueMessage
型に直接マップできない」というものがあります。どういうことか、実装を見てみましょう。
QueueMessage
へのマップができない件の差分
In-Process Model のとき
関数のエントリーポイントの引数で直接 Azure.Storage.Queues.Models.QueueMessage
型にマップできます。QueueMessage
型には DequeueCount
や ExpiresOn
など、たまに使いたくなるようなプロパティが生えていて有用です。
[FunctionName(nameof(QueueTriggerFunction))] public void EntryPoint( [QueueTrigger("sample-queue")] QueueMessage queueMessage) { }
Isolated Model のとき
一方で Isolated Model になると byte[]
などの比較的プリミティブな型が Binding 可能な型としてサポートされているだけで、QueueMessage
型などの SDK 固有の型へはダイレクトにマップできません。では DequeueCount
などへはどうやってアクセスすれば良いのかということになりますが、代替手法として FunctionContext.BindingContext.BindingData
が汎用データストアとして用意されており、それを利用することになります。
[FunctionName(nameof(QueueTriggerFunction))] public void EntryPoint( [QueueTrigger("sample-queue")] byte[] payload, FunctionContext context) { // BindingData に IReadOnlyDictionary<string, object?> として入っている var kvs = context.BindingContext.BindingData; var value = (string)kvs["DequeueCount"]!; var dequeueCount = long.Parse(value); }
上のサンプルにコメントとして記載しましたが、IReadOnlyDictionary<string, object?>
として提供されるのでだいぶ扱いづらいんですよね。
QueueMessage
に変換する
ということで扱いやすい形にするために QueueMessage
型に変換してみましょう。ざっくり以下のような拡張メソッドを作ってみました。
using System; using System.Collections.Generic; using Azure.Storage.Queues.Models; using Microsoft.Azure.Functions.Worker; public static class BindingContextExtensions { public static QueueMessage ToQueueMessage(this BindingContext context) { // QueueTrigger かどうかを確認しておく var kvs = context.BindingData; if (!kvs.TryGetValue("QueueTrigger", out var payload)) throw new InvalidOperationException("Function is not triggered by QueueTrigger."); // 値を取得 var messageId = getString(kvs, "Id"); var popReceipt = getString(kvs, "PopReceipt"); var body = BinaryData.FromString((string)payload!); var dequeueCount = getInt64(kvs, "DequeueCount"); var nextVisibleOn = getNullableDateTimeOffset(kvs, "NextVisibleTime"); var insertedOn = getNullableDateTimeOffset(kvs, "InsertionTime"); var expiresOn = getNullableDateTimeOffset(kvs, "ExpirationTime"); return QueuesModelFactory.QueueMessage(messageId, popReceipt, body, dequeueCount, nextVisibleOn, insertedOn, expiresOn); #region ローカル関数 static string getString(IReadOnlyDictionary<string, object?> kvs, string key) { var value = kvs[key]; return value is null ? throw new NotSupportedException($"Null value is not supported. | Key : {key}") : (string)value; } static long getInt64(IReadOnlyDictionary<string, object?> kvs, string key) { var value = getString(kvs, key); return long.Parse(value); } static DateTimeOffset? getNullableDateTimeOffset(IReadOnlyDictionary<string, object?> kvs, string key) { var value = kvs[key]; if (value is null) return null; var span = (value as string).AsSpan(); span = span.Trim('"'); // 前後に謎の " が含まれるので削除 return DateTimeOffset.Parse(span); } #endregion } }
これで以下のように利用できるようになります。QueueMessage
のプロパティにアクセスするのがだいぶ楽になるのではないでしょうか。
[FunctionName(nameof(QueueTriggerFunction))] public void EntryPoint( [QueueTrigger("sample-queue")] byte[] payload, FunctionContext context) { var queueMessage = context.BindingContext.ToQueueMessage(); var dequeueCount = queueMessage.DequeueCount; }