Alternative Architecture DOJO

オルターブースのクラウドネイティブ特化型ブログです。

Azure Functions 3.xでフィルターを利用する方法(ただしプレビュー機能)

こんにちは。MLBお兄さんこと松村です。
Microsoft AzureのサーバーレスサービスであるAzure Functionsはイベントドリブンなコードを実行することができる便利なサービスです。

弊社でもAzure Functionsは多くの案件で活用しています。
私がAzure Functionsのコードを実装する場合、たいていはC#(クラスライブラリ形式)を使います。

C#でAzure Functionsを実装する場合、ランタイムバージョンの都合上 .NET Coreを使うことになります。(後述)
そこで .NET Coreの「フィルター」という機能を使いたくなる場面がありますので、実装方法について紹介します。

注意

本記事においてAzure Functionsのランタイムバージョンは3.x系を前提とした内容となっています。
また、以下で紹介するAzure Functionsのフィルター実装方法は記事執筆時点(2020/02/01)でプレビュー機能であるため、今後のバージョンアップによっては利用できなくなる方法となる可能性があります。

ランタイムバージョン

Azure Functionsにおけるランタイムバージョンと、対応する .NET Coreのバージョンは以下のようになっています。(2020/02/01現在)
詳しくはこちらにまとまっています。

ランタイムバージョン .NETのバージョン
3.x .NET Core 3.1
2.x .NET Core 2.2
1.x .NET Framework 4.6

最新のランタイムバージョンである3系は2020年1月23日に一般提供(General Availability/GA)となりましたので、これからAzure Functionsを実装する場合は3系のランタイムを選択することをオススメします。

Azure Functions runtime 3.0 is now generally available | Azure updates | Microsoft Azure

フィルターとは

Azure Functionsのコードの実行前後に処理を差し込むことができる機能です。
ASP.NET Coreのフィルターと似ている機能ですので、詳しくはASP.NET Coreのドキュメントを参照してください。

docs.microsoft.com

Azure Functionsでフィルターを実装する

前述の通りフィルターはプレビュー機能であるせいか、Azure Functionsのドキュメントにはフィルターに関するページはありませんが、ランタイムのベースとなっているAzure WebJobs SDKのGitHub Wikiにページがあります。

github.com

といってもWiki自体かなりあっさりしていますので、基本的にはサンプルコードから読み取ることになるでしょう。

サンプルコードで解説します

前提としてcsprojファイルの構成はこのようになっています。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.0.0" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.2" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
</Project>

関数の呼び出し前後にログを出力するフィルターを実装してみます。

using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;

namespace FunctionApp1
{
    public class CustomFunctionFilterAttribute : FunctionInvocationFilterAttribute
    {
        public override Task OnExecutingAsync(FunctionExecutingContext executingContext, CancellationToken cancellationToken)
        {
            ILogger logger = executingContext.Logger;
            logger.LogInformation($"This is {nameof(CustomFunctionFilterAttribute)}.{nameof(OnExecutingAsync)}.");
            return base.OnExecutingAsync(executingContext, cancellationToken);
        }

        public override Task OnExecutedAsync(FunctionExecutedContext executedContext, CancellationToken cancellationToken)
        {
            ILogger logger = executedContext.Logger;
            logger.LogInformation($"This is {nameof(CustomFunctionFilterAttribute)}.{nameof(OnExecutedAsync)}.");
            return base.OnExecutedAsync(executedContext, cancellationToken);
        }
    }
}

ちなみにVisual Studioでこのコードを書くと、FunctionInvocationFilterAttributeFunctionExecutingContextFunctionExecutedContextの部分に「Filters is in preview and there may be breaking changes in this area.」という警告が表示されます。
つまりこの警告が、フィルターがプレビュー機能であることを注意するメッセージになっています。

フィルターは適用したい関数に対して属性として設定します。

[CustomFunctionFilter] // ←ここでフィルターを適用する
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    string name = req.Query["name"];

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    name = name ?? data?.name;

    return name != null
        ? (ActionResult)new OkObjectResult($"Hello, {name}")
        : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}

この関数を実行すると以下のようなログが出力されます。

Executing HTTP request: {
  "requestId": "06776515-f406-49be-9742-e2d9a57f4473",
  "method": "GET",
  "uri": "/api/Function1"
}
Executing 'Function1' (Reason='This function was programmatically called via the host APIs.', Id=3b8c1d4c-2677-4514-9e65-a6b379ad7444)
This is CustomFunctionFilterAttribute.OnExecutingAsync. ←ここ
C# HTTP trigger function processed a request.
This is CustomFunctionFilterAttribute.OnExecutedAsync.  ←ここ
Executed 'Function1' (Succeeded, Id=3b8c1d4c-2677-4514-9e65-a6b379ad7444)

何度も言いますがAzure Functionsでフィルターを使う際はプレビュー機能であることを念頭に置いた設計を心掛けましょう。

www.alterbooth.com

www.alterbooth.com

cloudpointer.tech