分離されたプロセスにおける C# Azure Functions の実行のガイド

この記事では、C# を使用して、Azure Functions でアウトプロセスで実行する .NET 分離プロセス関数を開発する方法について説明します。 アウトプロセスで実行することで、関数コードを Azure Functions ランタイムから切り離すことができます。 Isolatedプロセスの C# 関数は、.NET 6.0、.NET 7.0、.NET Framework 4.8 (プレビュー サポート) で実行されます。 インプロセス C# クラス ライブラリ関数 は、.NET 7.0 ではサポートされていません。

作業の開始 概念 サンプル

なぜ .NET 分離プロセスなのか?

これまで Azure Functions で唯一サポートされていた .NET 関数の緊密統合モードは、ホストと同じプロセスでクラス ライブラリとして実行されました。 このモードにより、ホスト プロセスと関数の間の統合が深くなります。 たとえば、.NET クラス ライブラリ関数は、バインドの API と型を共有できます。 しかし、この統合では、ホスト プロセスと .NET 関数の間の結合がより緊密になっている必要もあります。 たとえば、インプロセスで実行中の .NET 関数が Functions ランタイムと同じ .NET のバージョンで実行される必要があります。 これらの制約がなく実行できるように、分離プロセスでの実行を選べるようになりました。 このプロセス分離を使用すれば、Functions ランタイムでネイティブにサポートされていない、現在の .NET リリース (.NET 7.0 など) を使用する関数を開発することもできます。 分離プロセスとインプロセス C# クラス ライブラリ関数は、どちらも .NET 6.0 で実行されます。 詳しくは、サポートされるバージョンをご覧ください。

これらの関数は別個のプロセスで実行されるため、.NET 分離関数アプリと .NET クラス ライブラリ関数アプリの間には特徴と機能の違いがいくつかあります。

アウトプロセスで実行する場合の利点

.NET 関数をアウトプロセスで実行すると、次のような利点を利用できます。

  • 競合が少ない: 関数は別個のプロセスで実行されるため、アプリで使用されるアセンブリは、ホスト プロセスにより使用される同じアセンブリの異なるバージョンと競合しません。
  • プロセスの完全制御: アプリの起動を制御でき、使用する構成や起動するミドルウェアを制御できます。
  • 依存関係の挿入: プロセスを完全制御できるため、現在の .NET 動作を使用して、依存関係の挿入や関数アプリへのミドルウェアの組み込みを行えます。

サポートされているバージョン

Functions ランタイムの各バージョンは、.NET の特定のバージョンと動作します。 Functions のバージョンの詳細については、「Azure Functions ランタイム バージョンの概要」を参照してください。 バージョンのサポートは、関数がイン プロセスで実行されるか、アウト プロセス (分離) で実行されるかによって異なります。

Note

関数アプリで使用される Functions ランタイム バージョンを変更する方法については、「現在のランタイム バージョンの表示と更新」を参照してください。

次の表は、特定のバージョンの Functions と共に使用できる最上位レベルの .NET Core と .NET Framework を示しています。

Functions ランタイムのバージョン インプロセス
(.NET クラス ライブラリ)
アウトプロセス
(.NET 分離)
Functions 4.x .NET 6.0 .NET 6.0
.NET 7.0 (プレビュー)
.NET Framework 4.8 (プレビュー)1
Functions 3.x .NET Core 3.1 .NET 5.02
Functions 2.x .NET Core 2.13 N/A
Functions 1.x .NET Framework 4.8 N/A

1 ビルド プロセスには .NET 6 SDK も必要です。 .NET Framework 4.8 のサポートはプレビュー段階です。

2 ビルドプロセスには .NET Core 3.1 SDK も必要です。

3 詳細については、「Functions v2.x に関する考慮事項」を参照してください。

特定の古いマイナー バージョンの削除など、Azure Functions リリースに関する最新のニュースについては、Azure App Service のお知らせを閲覧してください。

.NET 分離プロジェクト

.NET 分離関数プロジェクトは、基本的には、サポートされる .NET ランタイムをターゲットとする .NET コンソール アプリ プロジェクトです。 以下は、.NET 分離プロジェクトで必要となる基本的なファイルです。

  • host.json ファイル
  • local.settings.json ファイル
  • プロジェクトと依存関係を定義する C# プロジェクト ファイル (.csproj)
  • アプリのエントリ ポイントである Program.cs ファイル。
  • 関数を定義するすべてのコード ファイル。

詳細な例については、.NET 6 分離サンプル プロジェクト.NET Framework 4.8 分離サンプル プロジェクトを参照してください。

注意

分離された関数プロジェクトを Azure の Windows または Linux 関数アプリに発行できるようにするには、リモート dotnet-isolated アプリケーションの設定で dotnet-isolated の値を設定する必要があります。 zip デプロイと Linux 上でのデプロイ パッケージからの実行をサポートするには、 サイトの構成設定を に更新する必要もあります。 詳細については、「Linux 上でのバージョンの手動更新」を参照してください。

パッケージ参照

アウトプロセスで関数が実行されるとき、.NET プロジェクトは、コア機能とバインド拡張機能の両方を実装している、一意のパッケージ セットを使用します。

コア パッケージ

以下のパッケージは、.NET 関数を分離プロセスで実行するために必要です。

拡張機能パッケージ

.NET 分離プロセスで実行される関数はさまざまなバインドの種類を使用するため、バインド拡張機能パッケージの一意のセットが必要になります。

これらの拡張機能パッケージは、Microsoft.Azure.Functions.Worker.Extensions にあります。

スタートアップと構成

.NET 分離関数を使用する場合、通常は Program.cs にある関数アプリのスタートアップにアクセスできます。 各自のホスト インスタンスの作成と開始は、お客様が担当します。 そのため、自分のアプリの構成パイプラインに直接アクセスすることもできます。 アウトプロセスで関数を実行すると、構成の追加、依存関係の挿入、および独自のミドルウェアの実行がはるかに簡単になります。

次のコードは HostBuilder パイプラインの例を示します。

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(builder =>
    {
        builder
            .AddApplicationInsights()
            .AddApplicationInsightsLogger();
    })
    .ConfigureServices(s =>
    {
        s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
    })
    .Build();

このコードでは using Microsoft.Extensions.DependencyInjection; が必要です。

HostBuilder は、完全に初期化された IHost インスタンス (これを非同期で実行して関数アプリを起動します) をビルドして返すために使用します。

await host.RunAsync();

重要

プロジェクトのターゲットが .NET Framework 4.8 の場合は、HostBuilder を作成する前に FunctionsDebugger.Enable(); を追加する必要もあります。 これは、Main() メソッドの最初の行である必要があります。 詳細については、「.NET Framework をターゲットにするときのデバッグ」を参照してください。

構成

ConfigureFunctionsWorkerDefaults メソッドは、関数アプリがアウトプロセスの実行に必要な設定を追加するために使用されます。これには、次の機能が含まれます。

  • コンバーターの既定のセット。
  • プロパティ名の大文字と小文字の区別を無視するための既定の JsonSerializerOptions への設定。
  • Azure Functions ログとの統合。
  • 出力バインディング ミドルウェアと機能。
  • 関数の実行ミドルウェア。
  • 既定の gRPC サポート。
.ConfigureFunctionsWorkerDefaults(builder =>
{
    builder
        .AddApplicationInsights()
        .AddApplicationInsightsLogger();
})

ホスト ビルダー パイプラインにアクセスできるということは、初期化中にあらゆるアプリ固有の構成を設定できることも意味します。 ConfigureAppConfiguration メソッドを HostBuilder で 1 回以上呼び出して、関数アプリに必要な構成を追加できます。 アプリ構成について詳しくは、「ASP.NET Core の構成」を参照してください。

これらの構成は、別のプロセスで実行している関数アプリに適用されます。 Functions ホストまたはトリガー、およびバインド構成に変更を加えるには、この場合も host.json ファイルを使用する必要があります。

依存関係の挿入

依存関係の挿入は、.NET クラス ライブラリと比べると簡単です。 サービスを登録するためのスタートアップ クラスを作成するのではなく、ホスト ビルダーで ConfigureServices を呼び出して、IServiceCollection で拡張メソッドを使用して特定のサービスを挿入するだけで済みます。

次の例では、シングルトン サービスの依存関係を挿入します。

.ConfigureServices(s =>
{
    s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
})

このコードでは using Microsoft.Extensions.DependencyInjection; が必要です。 詳細については、「ASP.NET Core での依存関係の挿入」を参照してください。

ミドルウェア

.NET 分離では、ASP.NET にあるものと同様のモデルを使用して、ミドルウェア登録もサポートしています。 このモデルを使用すると、呼び出しパイプラインにロジックを挿入したり、関数の実行前と実行後にロジックを挿入したりすることができます。

ConfigureFunctionsWorkerDefaults 拡張メソッドには、次の例に示すように、独自のミドルウェアを登録できるオーバーロードがあります。

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(workerApplication =>
    {
        // Register our custom middlewares with the worker

        workerApplication.UseMiddleware<ExceptionHandlingMiddleware>();

        workerApplication.UseMiddleware<MyCustomMiddleware>();

        workerApplication.UseWhen<StampHttpHeaderMiddleware>((context) =>
        {
            // We want to use this middleware only for http trigger invocations.
            return context.FunctionDefinition.InputBindings.Values
                          .First(a => a.Type.EndsWith("Trigger")).Type == "httpTrigger";
        });
    })
    .Build();

UseWhen 拡張メソッドを使用して、条件付きで実行されるミドルウェアを登録できます。 ブール値を返す述語をこのメソッドに渡す必要があり、述語の戻り値が true の場合、ミドルウェアが呼び出し処理パイプラインに参加します。

FunctionContext に対する次の拡張メソッドにより、分離モデルでのミドルウェアの処理が簡単になります。

メソッド 説明
GetHttpRequestDataAsync HTTP トリガーによって呼び出されたときに HttpRequestData インスタンスを取得します。 このメソッドは、要求ヘッダーや Cookie などのメッセージ データを読み取るときに便利な ValueTask<HttpRequestData?> のインスタンスを返します。
GetHttpResponseData HTTP トリガーによって呼び出されたときに HttpResponseData インスタンスを取得します。
GetInvocationResult 現在の関数実行の結果を表す InvocationResult のインスタンスを取得します。 Value プロパティを使用して、必要に応じて値を取得または設定します。
GetOutputBindings 現在の関数実行の出力バインド エントリを取得します。 このメソッドの結果の各エントリの型は OutputBindingData です。 Value プロパティを使用して、必要に応じて値を取得または設定できます。
BindInputAsync 要求された BindingMetadata インスタンスの入力バインド項目をバインドします。 たとえば、ミドルウェアからアクセスまたは更新する必要がある BlobInput 入力バインドを持つ関数があるときに、このメソッドを使用できます。

関数の実行中に HttpRequestData インスタンスを読み取り、HttpResponseData インスタンスを更新するミドルウェア実装の例を次に示します。 このミドルウェアは、特定の要求ヘッダー (x-correlationId) の存在を確認し、存在するときはヘッダー値を使用して応答ヘッダーをスタンプします。 それ以外の場合は、新しい GUID 値を生成し、それを使用して応答ヘッダーをスタンプします。

internal sealed class StampHttpHeaderMiddleware : IFunctionsWorkerMiddleware
{
    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        var requestData = await context.GetHttpRequestDataAsync();

        string correlationId;
        if (requestData!.Headers.TryGetValues("x-correlationId", out var values))
        {
            correlationId = values.First();
        }
        else
        {
            correlationId = Guid.NewGuid().ToString();
        }

        await next(context);

        context.GetHttpResponseData()?.Headers.Add("x-correlationId", correlationId);
    }
}

関数アプリでカスタム ミドルウェアを使用する詳細な例については、カスタム ミドルウェアのリファレンス サンプルを参照してください。

実行コンテキスト

.NET 分離は FunctionContext オブジェクトを関数メソッドに渡します。 このオブジェクトを使用すれば、ログに書き込む ILogger インスタンスを取得できます。これは、GetLogger メソッドを呼び出して 文字列を指定することで実行します。 詳細については、「ログ」を参照してください。

バインド

バインドは、メソッド、パラメーター、および戻り値の型の属性を使用して定義します。 関数メソッドとは、次の例に示すように、Function 属性とトリガー属性が入力パラメーターに適用されたメソッドです。

[Function("QueueFunction")]
[QueueOutput("output-queue")]
public static string[] Run([QueueTrigger("input-queue")] Book myQueueItem,

    FunctionContext context)

トリガー属性は、トリガーの種類を指定し、メソッド パラメーターに入力データをバインドします。 前の例の関数はキュー メッセージによってトリガーされ、そのキュー メッセージは myQueueItem パラメーターでメソッドに渡されます。

Function 属性は、関数のエントリ ポイントとしてメソッドをマークします。 名前はプロジェクト内で一意であり、文字で始まり、英数字、_- のみが含まれ、127 文字以下にする必要があります。 プロジェクト テンプレートでは、多くの場合、Run という名前のメソッドが作成されますが、有効な C# メソッド名であればメソッド名として使用できます。

.NET 分離プロジェクトは別個のワーカー プロセスで実行されるため、バインドで ICollector<T>IAsyncCollector<T>CloudBlockBlob などの豊富なバインド クラスを利用できません。 また、DocumentClientBrokeredMessage など、基になるサービス SDK から継承された型に対する直接のサポートもありません。 代わりに、バインドは文字列、配列、および単純な従来のクラス オブジェクト (POCO) のようなシリアル化可能型に依存します。

HTTP トリガーの場合、要求と応答のデータにアクセスするには、HttpRequestDataHttpResponseData を使用する必要があります。 これは、アウトプロセスでの実行時には、元の HTTP 要求および応答オブジェクトにアクセスできないためです。

アウトプロセスの実行時にトリガーとバインドを使用するためのリファレンス サンプルの完全なセットについては、バインド拡張機能のリファレンス サンプルを参照してください。

入力バインディング

関数には、関数にデータを渡すことができる入力バインディングを 0 個以上含めることができます。 トリガーと同様、入力バインディングは、入力パラメーターにバインディング属性を適用することによって定義します。 関数を実行すると、ランタイムはバインディングで指定されたデータを取得しようとします。 要求されるデータは、多くの場合、バインディング パラメーターを使用してトリガーから提供される情報に依存します。

出力バインディング

出力バインディングに書き込むには、関数メソッドに、バインドされたサービスへの書き込み方法を定義した出力バインディング属性を適用する必要があります。 メソッドによって返される値は、出力バインディングに書き込まれます。 たとえば、次の例では、出力バインディングを使用して、myqueue-output という名前のメッセージ キューに文字列値を書き込みます。

[Function("QueueFunction")]
[QueueOutput("output-queue")]
public static string[] Run([QueueTrigger("input-queue")] Book myQueueItem,

    FunctionContext context)
{
    // Use a string array to return more than one message.
    string[] messages = {
        $"Book name = {myQueueItem.Name}",
        $"Book ID = {myQueueItem.Id}"};
    var logger = context.GetLogger("QueueFunction");
    logger.LogInformation($"{messages[0]},{messages[1]}");

    // Queue Output messages
    return messages;
}

複数の出力バインディング

出力バインディングに書き込まれるデータは、常に関数の戻り値です。 複数の出力バインディングに書き込む必要がある場合は、カスタムの戻り値の型を作成する必要があります。 この戻り値の型では、出力バインディング属性はクラスの 1 つ以上のプロパティに適用されていなければなりません。 HTTP トリガーからの次の例は、HTTP 応答とキュー出力バインディングの両方に書き込みます:

public static class MultiOutput
{
    [Function("MultiOutput")]
    public static MyOutputType Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req,
        FunctionContext context)
    {
        var response = req.CreateResponse(HttpStatusCode.OK);
        response.WriteString("Success!");

        string myQueueOutput = "some output";

        return new MyOutputType()
        {
            Name = myQueueOutput,
            HttpResponse = response
        };
    }
}

public class MyOutputType
{
    [QueueOutput("myQueue")]
    public string Name { get; set; }

    public HttpResponseData HttpResponse { get; set; }
}

HTTP トリガーからの応答は常に出力と見なされるため、戻り値の属性は必要ありません。

HTTP トリガー

HTTP トリガーは、受信した HTTP 要求メッセージを、関数に渡される HttpRequestData オブジェクトに変換します。 このオブジェクトは、HeadersCookiesIdentitiesURL、およびメッセージ Body (オプション) を含む、要求からのデータを提供します。 このオブジェクトは、HTTP 要求オブジェクトを表現したものであり、要求そのものではありません。

同様に、関数が返す HttpResponseData オブジェクトも、メッセージ Headers、およびメッセージ Body (オプション) を含む、HTTP 応答を作成するために使用するデータを提供します。

次のコードは、HTTP トリガーです

[Function("HttpFunction")]
public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req,
    FunctionContext executionContext)
{
    var logger = executionContext.GetLogger("HttpFunction");
    logger.LogInformation("message logged");

    var response = req.CreateResponse(HttpStatusCode.OK);
    response.Headers.Add("Date", "Mon, 18 Jul 2016 16:06:00 GMT");
    response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
    
    response.WriteString("Welcome to .NET 5!!");

    return response;
}

ログ記録

.NET 分離では、ILogger インスタンスを使用してログに書き込むことができます。このインスタンスは、関数に渡される FunctionContext オブジェクトから取得します。 GetLogger メソッドを呼び出します。その際に、ログが書き込まれるカテゴリの名前を表す文字列値を渡します。 カテゴリは通常、ログの書き込み元となる特定の関数の名前です。 カテゴリについて詳しくは、監視に関する記事をご覧ください。

次の例は、ILogger を取得してログを関数内で書き込む方法を示しています。

var logger = executionContext.GetLogger("HttpFunction");
logger.LogInformation("message logged");

さまざまな ILogger のメソッドを使用して、LogError などの各種のログ レベルを記述します。 ログ レベルについて詳しくは、監視に関する記事をご覧ください。

ILogger は、依存関係の挿入を使用しているときにも提供されます。

.NET Framework をターゲットにするときのデバッグ

分離されたプロジェクトのターゲットが .NET Framework 4.8 の場合、現在のプレビュー スコープでは、デバッグを有効にするために手動の手順が必要です。 別のターゲット フレームワークを使用する場合、これらの手順は必要ありません。

アプリは、最初の操作として FunctionsDebugger.Enable(); への呼び出しで開始する必要があります。 これは、HostBuilder を初期化する前に Main() メソッドで発生します。 Program.cs ファイルは次のようになります。

using System;
using System.Diagnostics;
using Microsoft.Extensions.Hosting;
using Microsoft.Azure.Functions.Worker;
using NetFxWorker;

namespace MyDotnetFrameworkProject
{
    internal class Program
    {
        static void Main(string[] args)
        {
            FunctionsDebugger.Enable();

            var host = new HostBuilder()
                .ConfigureFunctionsWorkerDefaults()
                .Build();

            host.Run();
        }
    }
}

次に、.NET Framework デバッガーを使用してプロセスに手動でアタッチする必要があります。 Visual Studio は、分離されたプロセス .NET Framework アプリに対してはまだ自動的にこれを行いません。"デバッグの開始" 操作は避ける必要があります。

プロジェクト ディレクトリ (またはそのビルド出力ディレクトリ) で、次を実行します。

func host start --dotnet-isolated-debug

これでワーカーが開始され、プロセスは次のメッセージを表示して停止します。

Azure Functions .NET Worker (PID: <process id>) initialized in debug mode. Waiting for debugger to attach...

ここで、<process id> はワーカー プロセスの ID です。 これで、Visual Studio を使用して、プロセスに手動でアタッチできるようになりました。 この操作の手順については、実行中のプロセスにアタッチする方法に関する記事を参照してください。

デバッガーがアタッチされると、プロセスの実行が再開され、デバッグできるようになります。

.NET クラス ライブラリ関数との相違点

このセクションでは、インプロセスで実行される .NET クラス ライブラリ関数と比較した場合の、.NET 5.0 でアウトプロセスで実行される機能面と動作面での違いの現在の状態について説明します:

機能/動作 インプロセス アウトプロセス
.NET のバージョン .NET Core 3.1
.NET 6.0
.NET 6.0
.NET 7.0 (プレビュー)
.NET Framework 4.8 (プレビュー)
コア パッケージ Microsoft.NET.Sdk.Functions Microsoft.Azure.Functions.Worker
Microsoft.Azure.Functions.Worker.Sdk
バインディング拡張機能パッケージ Microsoft.Azure.WebJobs.Extensions.* Microsoft.Azure.Functions.Worker.Extensions.*
Durable Functions サポートされています サポートされています (パブリック プレビュー)
バインディングによって公開されるモデルの型 単純型
JSON シリアル化可能な型
配列/列挙型
BlobClient などのサービス SDK の型
IAsyncCollector (出力バインディング用)
単純型
JSON シリアル化可能な型
配列/列挙型
HTTP トリガー モデルの型 HttpRequestObjectResult HttpRequestDataHttpResponseData
出力バインディングのインタラクション 戻り値 (単一出力のみ)
out パラメーター
IAsyncCollector
戻り値 (単一または複数の出力を持つ拡張モデル)
命令型バインディング1 サポートされています サポートされていません
依存関係の挿入 サポートされています サポートされています
ミドルウェア サポートされていません サポートされています
ログ記録 ILogger が関数に渡される
依存関係の挿入による Ilogger<T>
FunctionContext から、または依存関係の挿入により取得した ILogger/ILogger<T>
Application Insights の依存関係 サポートされています サポートされています (パブリック プレビュー)
キャンセル トークン サポートされています サポートされていません
コールド スタート時間2 (ベースライン) さらに、プロセスの起動も含まれます
ReadyToRun サポート状況 TBD

1 実行時に決定されたパラメーターを使用してサービスとやり取りする必要がある場合は、命令型バインディングを使用するよりも、対応するサービス SDK を直接使用することをお勧めします。 SDK はより簡潔で、より多くのシナリオをカバーすることができ、エラー処理やデバッグの目的にも適しています。 この推奨事項は、両方のモデルに適用されます。

2 Windows で一部の .NET プレビュー バージョンを使用する場合、プレビュー フレームワークの Just-In-Time 読み込みが原因で、コールド スタート時間がさらなる影響を受ける可能性があります。 これは、インプロセス モデルとアウトプロセス モデルの両方に適用されますが、異なるバージョン間で比較すると特に違いが顕著になる可能性があります。 このプレビュー バージョンの遅延は、Linux プランには存在しません。

Visual Studio を使用したリモート デバッグ

分離プロセス アプリは Functions ランタイムの外部で実行されるため、リモート デバッガーを別のプロセスにアタッチする必要があります。 Visual Studio を使用したデバッグの詳細は、「リモート デバッグ」をご覧ください。

次のステップ