Azure WebJobs In .NET Core – Part 5

This is part 5 of a series on getting up and running with Azure WebJobs in .NET Core. If you are just joining us, it’s highly recommended you start back on Part 1 as there’s probably some pretty important stuff you’ve missed on the way.


Azure WebJobs In .NET Core

Part 1 – Initial Setup/Zip Deploy
Part 2 – App Configuration and Dependency Injection
Part 3 – Deploying Within A Web Project and Publish Profiles
Part 4 – Scheduled WebJobs
Part 5 – Azure WebJobs SDK


Where Are We Up To?

Thus far, we’ve mostly been playing around with what amounts to a console app and running it in Azure. If anything it’s just been a tidy environment for running little utilities that we don’t want to spin up a whole VM for. But if you’ve used WebJobs using the full .NET Framework before, you know there are actually libraries to get even more out of Web Jobs. Previously this wasn’t available for .NET Core applications, until now!

Installing The WebJob Packages

Go ahead and create stock standard .NET Core console application for us to test things out on.

The first thing we need to do with our new app is install a nuget package from Microsoft that opens up quite a few possibilities.

Install-Package Microsoft.Azure.WebJobs

Note that this needs to be atleast version 3.0.0. At the time of writing this is version 3.0.2 so you shouldn’t have any issues, but just incase you are trying to migrate an old project.

Now something that changed from version 2.X to 3.X of the library is that you now need to reference a second library called “Microsoft.Azure.WebJobs.Extensions”. This is actually pretty infuriating because no where in any documentation does it talk about this. There is this “helpful” message on the official docs :

The instructions tell how to create a WebJobs SDK version 2.x project. The latest version of the WebJobs SDK is 3.x, but it is currently in preview and this article doesn’t have instructions for that version yet.

So it was only through trial and error did I actually get things up and running. So you will want to run :

Install-Package Microsoft.Azure.WebJobs.Extensions

I honestly have no idea what exactly you could do without this as it seemed like literally every single trigger/webjob function I tried would not work without this library. But alas.

Building Our Minimal Timer WebJob

I just want to stress how different version 3.X of WebJobs is to 2.X. It feels almost like a complete rework, and if you are trying to come from an older version, you may think some of this looks totally funky (And that’s because it is a bit). But just try it out and have a play and see what you think.

First thing we want to do is create a class that holds some of our WebJob logic. My code looks like the following :

public class SayHelloWebJob
{
    [Singleton]
    public static void TimerTick([TimerTrigger("0 * * * * *")]TimerInfo myTimer)
    {
        Console.WriteLine($"Hello at {DateTime.UtcNow.ToString()}");
    }
}

So a couple of notes :

  • The Singleton attribute just says that there should only ever be one of this particular WebJob method running at any one time. e.g. If I scale out, it should still only have one instance running.
  • The “TimerTrigger” defines how often this should be run. In my case, once a minute.
  • Then I’m just writing out the current time to see that we are running.

In our Main method of our console application, we want to add a new Host builder object and start our WebJob. The code for that looks like so :

static void Main(string[] args)
{
    var builder = new HostBuilder()
        .ConfigureWebJobs(webJobConfiguration =>
        {
            webJobConfiguration.AddTimers();
            webJobConfiguration.AddAzureStorageCoreServices();
        })
        .ConfigureServices(serviceCollection => serviceCollection.AddTransient<SayHelloWebJob>())
        .Build();

    builder.Run();
}

If you’ve used WebJobs before, you’re probably used to using the JobHostConfiguration  class to do all your configuration. That’s now gone and replaced with the HostBuilder . Chaining is all the rage these days in the .NET Core world so it looks like WebJobs have been given the same treatment.

Now you’ll notice that I’ve added a a call to the chain called AddAzureStorageCoreServices()  which probably looks a little weird given we aren’t using anything with Azure at all right? We are just trying to run a hello world on a timer. Well Microsoft says bollox to that and you must use Azure Blob Storage to store logs and a few other things. You see when you use Singleton (Or just in general what you are doing needs to run on a single instance), it uses Azure Blob Storage to create a lock so no other instance can run. It’s smart, but also sort of annoying if you really don’t care and want to throw something up fast. It’s why I always walk people through creating a Web Job *without* this library, because it starts adding a whole heap of things on top that just confuse what you are trying to do.

Anyway, you will need to create an appsettings.json file in the root of your project (Ensure it’s set to Copy Always to your output directory). It should contain (atleast) the following :

{
  "ConnectionStrings": {
    "AzureWebJobsDashboard": "AnAzureStorageConnectionString",
    "AzureWebJobsStorage": "AnAzureStorageConnectionString"
  }
}

For the sake of people banging their heads against the wall with this and searching Google. Here’s something to help them.

If you are getting the following error :

Unhandled Exception: System.InvalidOperationException: Unable to resolve service for type ‘Microsoft.Azure.WebJobs.DistributedLockManagerContainerProvider’ while attempting to activate ‘Microsoft.Azure.WebJobs.Extensions.Timers.StorageScheduleMonitor’.

This means that you haven’t added the call to AddAzureStorageCoreServices()

If you are instead getting the following error :

Unhandled Exception: Microsoft.Azure.WebJobs.Host.Listeners.FunctionListenerException: The listener for function ‘SayHelloWebJob.TimerTick’ was unable to start. —> System.ArgumentNullException: Value cannot be null.
Parameter name: connectionString
at Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(String connectionString)

This means that you did add the call to add the services, but it can’t find the appsetting for the connection strings. First check that you have them formatted correctly in your appsettings file, then ensure that your appsettings is actually being output to your publish directory correctly.

Moving on!

In the root of our project, we want to create a run.cmd  file to kick off our WebJob. We went over this in Part 1 of this series, so if you need a reminder feel free to go back.

I’m just going to go ahead and Publish/Zip my project up to Azure (Again, this was covered in Part 1 incase you need help!). And what do you know!

[12/05/2018 05:35:43 > d0fd33: INFO] Application started. Press Ctrl+C to shut down.
[12/05/2018 05:35:43 > d0fd33: INFO] Hosting environment: Production
[12/05/2018 05:35:43 > d0fd33: INFO] Content root path: D:\local\Temp\jobs\continuous\WebJobSDKExample\ydxruk4e.inh\publish\
[12/05/2018 05:36:00 > d0fd33: INFO] Hello at 12/5/2018 5:36:00 AM

Taking This Further

At this point, so much of this new way of doing WebJobs is simply trial and error. I had to guess and bumble my way through so much just to get to this point. As I talked about earlier, the documentation is way behind this version of the library which is actually pretty damn annoying. Especially when the current release on Nuget is version 3.X, and yet there is no documentation to back it up.

One super handy tool I used was to look at the Github Repo for the Azure Webjobs SDK. Most notably, there is a sample application that you can go and pick pieces of out of to get you moving along.

What’s Next?

To be honest, this was always going to be my final post in this series. Once you reach this point, you should already be pretty knowledgeable about WebJobs, and through the SDK, you can just super power anything you are working on.

But watch this space! I may just throw out a helpful tips post in the future of all the other annoying things I found about the SDK and things I wished I had known sooner!

14 thoughts on “Azure WebJobs In .NET Core – Part 5”

  1. Hi!
    I’m so happy and thankful I came across your site in search for Azure WebJob 3.XX tutorial. I’ve been trying to find a help for 3 months now to build my WebJob. I learned a lot from your series.
    I just have one question about getting the configuration settings, the connection string specifically. In the previous version of Azure WebJob using JobHostConfiguration(), we could just simply put the connection strings in App.config file. Can the same thing be applied using the latest version, instead of having appsettings.json?

    Thanks in advance and more power!!

    Reply
    • Hi Ariel,

      If I’m reading your question correctly, the answer would be no. Because .NET Core Projects don’t have a app.config file, only an appsettings.json file. They realistically perform the same function though. Is there a reason you need to use app.config instead of appsettings.json?

      – Wade

      Reply
  2. Thanks for the tutorial!

    I have moved my webjob to target .NET Core and everything, including configuration, works perfectly… with one small exception.

    await smtpClient.SendMailAsync()

    The webjob thread is immediately killed when it hits this line and no exception can be caught.

    The smtpClient is properly configured to use SendGrid credentials in precisely the same manner as in the web app.

    Do you understand what is happening? This line presented no problem before I moved the web job to .NET Core.

    Thanks in advance!

    – Seth

    Reply
    • Almost certainly it’s going to be to do with the fact you are using async. I would try playing around with this and changing it to use sync and see if that resolves the issue. If it does, then atleast you know where to look from there.

      Reply
  3. I think its incredibly importation to mention Azure Functions. There is huge overlap between Web Jobs and functions, and also one can do more than the other. https://docs.microsoft.com/en-us/azure/azure-functions/functions-compare-logic-apps-ms-flow-webjobs#compare-functions-and-webjobs

    This text is straight from the official Microsoft Documentation: “Azure Functions offers more developer productivity than Azure App Service WebJobs does. It also offers more options for programming languages, development environments, Azure service integration, and pricing. For most scenarios, it’s the best choice.”

    Reply
    • IMO they serve a slightly different purpose. WebJobs are small jobs that you tack onto a Website. Things like clearing cache, maybe doing the odd batch job etc. Functions are far more powerful but I feel they are used to build entire solutions, not just an odd job here and there. Because WebJobs are literally part of the website depoloyment, they are handy if you just need a single command running once a day, and you don’t want to set up an entire deployment pipeline to go with it.

      Reply
  4. Wade, excellent tutorial series. I’m very thankful.

    One thing has me stuck now. I created my webjob inside an App Service. I can’t see where the access keys are for this. It seems everyone is saying to get them from a storage account. Mine is not in a storage account.

    How do I set my connection strings then?

    Thanks in advance

    Michael

    Reply
  5. Many thanks for this, you saved me a lot of heartache here. Still hitting the same quirks as above in 2020. If you end up touring mainland Europe let me know I will get you a beer or 5.

    Reply
  6. Wade. Thanks for excellent tutorial. Question, can WebJob use the db connection and other settings from web app web.config?

    Reply
  7. Hey Wade, thanks for this tutorial!

    On part 5 I did get a little surprised about this line:

    .ConfigureServices(serviceCollection => serviceCollection.AddTransient())

    It seems like it’s using dependency injection for the SayHelloWebJob class, but that class just contains one static function, and it’s constructor is never hit.

    public static void TimerTick([TimerTrigger(“0 * * * * *”)]TimerInfo myTimer)

    Doesn’t that completely violate dependency injection rules? Why did MS build it this way, any clue?

    Jeroen

    Reply
  8. Thanks a lot Wade!!!! after weekssssss I could make my webJob works thanks to this full tutorial!!
    May God bless you !!!

    Reply

Leave a Comment