Back in 2017 I wrote a fairly popular (by my standards) post about Azure webjobs and getting it all to work in .net core 2.0. Well, fast forward to late 2018, and now MS have finished work on the azure webjobs sdk 3.0 - and it's much simpler now.

This new post goes through the same scenario - a .net core console app, and describes how to make it work with the new sdk. At the time of writing, documentation is very sparse around the v3 sdk, so hopefully this will be of use.


If you're not interested in my write-up, you can probably find everything you need in this sample here

My setup / requirements

Just to recap, I've got a bunch of .net core (2.1) console apps, with functions that I want to run as azure webjobs. I need:

  • Trigger methods via scheduled timers ([TimerTrigger])
  • Trigger methods via queue items ([QueueTrigger])
  • Use the same DI mechanism / logging as the web app
  • Use the same config setup as the web app

Getting it working

The great news is that with v3 of the SDK, Microsoft have gone for a similar pattern to core - you build up a host, configuring all the bits you need, much as you would build a IWebHostBuilder in

Even better, there are more similarities - configuration, DI Services, and logging are all VERY similar to core, and this time it's all there in the sdk, no messing about - all "built in".

First of all you need to grab the latest packages - here's what I have in my webjobs console app:

<PackageReference Include="Microsoft.Azure.WebJobs" Version="3.0.1" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="3.0.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.1" />
<PackageReference Include="Microsoft.Azure.WebJobs.Host.Storage" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.0" />

Then, inside your console app (typically in your program.cs), you can build the webjob host like this:

var builder = new HostBuilder()
    .ConfigureWebJobs(b =>
            // This is for QueueTrigger support
            .AddAzureStorage(options => 
                options.BatchSize = 1;
                options.MaxPollingInterval = TimeSpan.FromSeconds(10);
                options.VisibilityTimeout = TimeSpan.FromSeconds(30);
        // This is for TimerTrigger support
    .ConfigureAppConfiguration(b =>
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
    .ConfigureLogging((context, b) =>

        // If this key exists in any config, use it to enable App Insights
        string appInsightsKey = context.Configuration.GetSection("ApplicationInsights")["WebJobsInstrumentationKey"];
        if (!string.IsNullOrEmpty(appInsightsKey))
            b.AddApplicationInsights(o => o.InstrumentationKey = appInsightsKey);
    .ConfigureServices((context, services) =>
        services.AddTransient<SomeQueueTriggerFunctions, SomeQueueTriggerFunctions>();
        // All your Di-able classes can be set up here.

var host = builder.Build();
using (host)
    await host.RunAsync();

So what's going on here then? We're building up the webjob host, and adding all the configuration we need.

  1. We specify the environment (development or production)
  2. We configure the web jobs - here we're setting up AzureStorage for queue trigger support, and specifying things like the batch size. We also add timer trigger support
  3. We configure logging - this is just like it is in core, hooray!
  4. We configure the services. This is where you build up your DI - you need to add all your classes that contain your azure functions here, and also any dependencies THEY have need to be configured here too.
    Be careful here - if you use AddScoped, they'll behave as singleton, because there is no "scoped" lifetime defined for console apps.

And.. well that's it really. It's replaced a lot of the cruft I described in my previous post, and the whole experience is much nicer and consistent.