tech.guitarrapc.cóm

Technical updates

.NET Core の Generic Host で WebJobs を利用する

Generic Host を使った場合でも、これまで .NET Core で書いてきた処理は問題なく組み込むことができます。 最近 Azure WebJobs を Generic Host で使う機会があったので見てみましょう。

※ 社内向けブログの転載なのでシリーズ化します。

目次

TL;DR;

Azure Storage Blob/Queue/Table などの更新を受けて処理を書くために自前でポーリングを書くのはそれなりに大変です。 これを担保するWebJobs という仕組みがすでにあります。これを使ってAzure Functions を書くのと同じ感覚で実装が追加できます。

ここでは.NET Core のコンソールアプリでWeb Jobs を追加して実装する方法を見てみましょう。

事前に読んでおきたい

tech.guitarrapc.com

WebJobs の追加

HostBuilder にWebJobsを追加してみましょう。

nuget パッケージの追加

HostBuilder にWebJobs 処理を追加するため、次のnugetパッケージを追加しておきます。

    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="3.0.1" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.2" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Logging.ApplicationInsights" Version="3.0.3" />

HostBuilder を組む

nugetパッケージを追加したことで、これで次のように AzureStorage をトリガーとした処理をかけます。

class Program
{
    static async Task Main(string[] args)
    {
        await new HostBuilder()
            .ConfigureWebJobs(b =>
            {
                // must before ConfigureAppConfiguration
                b.AddAzureStorageCoreServices()
                .AddAzureStorage();
            })
            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                // Configの追加
                hostContext.HostingEnvironment.EnvironmentName = System.Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "production";
                configApp.SetBasePath(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
                configApp.AddCommandLine(args);
                configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json");
            })
            .ConfigureServices(services =>
            {
               // サービス処理のDI
                services.AddSingleton<IFooService, FooService>();
            })
            .ConfigureLogging((context, b) =>
            {
                b.SetMinimumLevel(LogLevel.Debug);

               // Console ロガーの追加
                b.AddConsole();

                // NLog や Log4Net、SeriLog などを追加

                // あるいはApplication Insight の追加
            })
            .RunConsoleAsync()
    }
}

StorageのConnection Strings を設定ファイルに組む

コード上にべた書きするとビルド環境ごとの切り替えや差し替えが面倒ですし、リポジトリにあげるのもまずいでしょう。 Generic Host では、appsettings.json や任意のjson/xml などを読み込むことができます。

もちろんMSI を使えば ConnectionStrings も不要になるので、検討するといいでしょう。

ここでは、appsettings.<環境>.json を読むように組んだので、appsettings.Development.json にコネクションを追加しておきましょう。

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "ConnectionStrings": {
    "AzureWebJobsDashboard": "DefaultEndpointsProtocol=https;AccountName=xxxxxxxx;AccountKey=XXXXXXXXXXXXXX;EndpointSuffix=core.windows.net",
    "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=xxxxxxxx;AccountKey=XXXXXXXXXXXXXX;EndpointSuffix=core.windows.net"
  }
}

WebJobs のトリガーを書く

感覚的には、AzureFunctions のトリガーを書くのと同じです。

IFooService がDIされているので、これをコンストラクタで受けるFooFunctionクラスを書いてみましょう。

    [ErrorHandler]
    public class FooFunction
    {
        private readonly IFooService service;

        public FooFunction(IFooService service)
        {
            this.service = service;
        }

        [Singleton]
        public async Task QueueTrigger([QueueTrigger("yourqueue")] string queueItem, ILogger log)
        {
            log.LogInformation($"C# Queue trigger function processed: {queueItem}");
            // 何か処理
        }
    }

どうでしょうか? ごくごく普通のAzureFunctions な感じで書けて、Queue のポーリングも不要です。

同様の感覚でcronもかけたりするので、Queue を受けてXXXな処理をするというケースでは、WebJobs をベースすると楽でしょう。

まとめ

Generic Host でも Webjobs を組めるということは、Azure Storage をはじめとした Azure Functionsでやるような処理を自前でハンドルしたいときに、ホスティング環境を問わないということになります。 もし今後 .NET Core + WebJobs をする場合、これがデファクトになるんじゃないでしょうか。

Tips

HostBuilderの初期化時に、注意があります。 必ず ConfigureWebJobsConfigureAppConfiguration の前に置きましょう。初期化できずに死にます。

ok

        await new HostBuilder()
            .ConfigureWebJobs(b =>
            {
                // must before ConfigureAppConfiguration
                b.AddAzureStorageCoreServices()
                .AddAzureStorage();
            })
            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                // Configの追加
                hostContext.HostingEnvironment.EnvironmentName = System.Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "production";
                configApp.SetBasePath(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
                configApp.AddCommandLine(args);
                configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json");
            })

no

        await new HostBuilder()
            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                // Configの追加
                hostContext.HostingEnvironment.EnvironmentName = System.Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "production";
                configApp.SetBasePath(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
                configApp.AddCommandLine(args);
                configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json");
            })
            .ConfigureWebJobs(b =>
            {
                // must before ConfigureAppConfiguration
                b.AddAzureStorageCoreServices()
                .AddAzureStorage();
            })

Ref

Introducing Windows Azure WebJobs WebJobs in Azure with .NET Core 2.1 Azure/azure-webjobs-sdk SanderRossel/netcore-webjob