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