Azure WebJobs In .NET Core – Part 2

This is part 2 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


App Configuration Of Our .NET Core Web Job

Previously we managed to get our WebJob all up and running as a simple console application. But the reality is that in almost every application, we are going to want some sort of configuration swap happening when we deploy up to Azure. In full framework we might have done something as simple as a web.config transform, but here we are using appsettings.json, so things are a little different. Let’s get cracking!

I’m not going to explain what each one of these does, but I would just recommend installing all of the following nuget packages into your WebJob/Console Application. Adding these will give you an experience that you are probably used to from building .NET Core web projects.

Install-Package Microsoft.Extensions.Options.ConfigurationExtensions
Install-Package Microsoft.Extensions.Configuration.FileExtensions
Install-Package Microsoft.Extensions.Configuration.Json
Install-Package Microsoft.Extensions.Configuration.Binder
Install-Package Microsoft.Extensions.Configuration.EnvironmentVariables

Now let’s go ahead and create two configuration files in our project. One we will call appsettings.json, and the other appsettings.production.json

Both of these files should be set to “Copy If Newer” so that they are output to the bin directory both when running locally, and when publishing.

The contents of the appsettings.json file will be :

{
  "message" :  "I'm running locally!" 
}

And for our production file :

{
  "message": "I'm running in Azure!"
}

You can probably see where this is going!

And now we will change the console app code to look a bit like so :

class Program
{
    static void Main(string[] args)
    {
        var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");

        Console.WriteLine($"Current Environment : {(string.IsNullOrEmpty(environment) ? "Development" : environment)}");

        var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables()
            .Build();

        Console.WriteLine(config["message"]);
    }
}

So let’s break this down a little.

First we go ahead and grab the environment available of “ASPNETCORE_ENVIRONMENT”, we’ll look at how we actually set this later. We also write this out to the console just for a bit of debugging.

Next we load configuration files. We load the default appsettings.json, and we load one that will be appsettings.environment.json, where environment is whatever was in the environment variable from earlier. Obviously if we set this to “production”, then we will pull in our production app settings which will override the default file. Perfect!

Finally, we print out the “message” app setting. Locally we see the following output :

Current Environment : Development
I'm running locally!

Let’s publish our Web Job up to Azure (Using the zip method from Part 1), and see what we get. After uploading, we check the WebJob logging out :

[10/09/2018 22:38:14 > d0fd33: INFO] Current Environment : Development
[10/09/2018 22:38:14 > d0fd33: INFO] I'm running locally!

Bleh! That didn’t work, it still thinks we are running in Development?! It’s because we didn’t set the environment variable!

Head to the “Application Settings” configuration panel of your Azure Web App. You need to add the variable “ASPNETCORE_ENVIRONMENT” with the setting of “production”.

If we head back to our WebJob logging output :

[10/09/2018 22:40:20 > d0fd33: INFO] Current Environment : production
[10/09/2018 22:40:20 > d0fd33: INFO] I'm running in Azure!

Awesome! So now our WebJob has the concept of both configuration, and which “environment” it’s running in so that it can swap configuration out at runtime.

Using .NET Core Dependency Injection

A pretty big part of .NET Core is it’s out-of-the-box dependency injection support. While usage of DI in a quick and dirty console application is probably pretty rare, if you are using shared libraries between the web project and your web job, you are probably going to need it to wire up a couple of services here and there. If you’ve already added DI into a .NET Core console application before, this may not be totally new for you, but it’s still worth stepping through it anyway.

First we need the nuget package that actually has the .NET Core dependency injection classes in it :

Install-Package Microsoft.Extensions.DependencyInjection

Our first point of order is actually going to wire up our configuration properly. Previously when we accessed our configuration, we were doing it on the raw ConfigurationRoot object, which isn’t too great. I personally prefer using POCO’s to hold configuration. So let’s create something to hold our “Message” :

public interface IWebJobConfiguration
{
    string Message { get; }
}

public class WebJobConfiguration : IWebJobConfiguration
{
    public string Message { get; set; }
}

We need to modify our appsettings.json files (Both the default and production) to better match our structure :

{
  "WebJobConfiguration": {
    "message": "I'm running locally!"
  }
}

Now we need to fire up our service collection, and bind up our configuration. To do this, in our main method of our program.cs. Underneath the configuration setup, add the following :

IServiceCollection services = new ServiceCollection();
services.AddSingleton<IWebJobConfiguration>(config.GetSection("WebJobConfiguration").Get<WebJobConfiguration>());

What this does is it creates a new service collection, and binds our configuration for the IWebJobConfiguration interface. Not that helpful (yet!), but follow on.

Next we are going to do something a little funky. We are going to create a new “Entry Point” for our application. So far all of our setup *and* our actual “work” has been done within the main method of our program.cs. This is fine for now, but what we actually need is an application root that can be the kick off point for our WebJob to actually start. Now I want to point out, that later in this series we are going to move this “Entry Point” and instead use a library that essentially does it for us, but it’s still good to know.

Go ahead and create a class that looks like the following :

public class WebJobEntryPoint
{
    private readonly IWebJobConfiguration _webJobConfiguration;

    public WebJobEntryPoint(IWebJobConfiguration webJobConfiguration)
    {
        _webJobConfiguration = webJobConfiguration;
    }

    public void Run()
    {
        Console.WriteLine(_webJobConfiguration.Message);
    }
}

Pretty simple, we just have a single method that prints out our configuration message (Remember, this changes depending if we are local or in production).

Now we need to wire this up, we add the following to our main method of our program.cs at the very bottom.

services.AddTransient<WebJobEntryPoint>();

var provider = services.BuildServiceProvider();
var entryPoint = provider.GetService<WebJobEntryPoint>();
entryPoint.Run();

So essentially we are adding WebJobEntryPoint as a service we can build. We are then requesting that service back (Which in the process injects in our configuration), and then we call Run on it. The reason we do this is it provides a way that the more we add into that entry point in terms of dependencies or services, the actual call to “new” everything up doesn’t change. All in all, our main method should end up looking like :

static void Main(string[] args)
{
    var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");

    Console.WriteLine($"Current Environment : {(string.IsNullOrEmpty(environment) ? "Development" : environment)}");

    var config = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
        .AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
        .AddEnvironmentVariables()
        .Build();

    IServiceCollection services = new ServiceCollection();
    services.AddSingleton<IWebJobConfiguration>(config.GetSection("WebJobConfiguration").Get<WebJobConfiguration>());
    services.AddTransient<WebJobEntryPoint>();

    var provider = services.BuildServiceProvider();
    var entryPoint = provider.GetService<WebJobEntryPoint>();
    entryPoint.Run();
}

Publish this up to Azure and we get our usual output! Now of course, we haven’t really achieved much in total by adding in dependency injection to our console app, but that’s because this this a simple application without too many dependencies. If we had a much more complicated service layer and a tonne of business logic, especially if it’s buried away in shared DLLs, we wouldn’t have such a headache in getting things up and running.

What’s Next?

So far we’ve been fiddling around with individual console applications that we’ve deployed as zips. Next we are going to take a look at how we might be able to deploy a web job as part of a web application. By that I mean when we deploy out web app to Azure, we automatically push the web job with it. Again this is something that is pretty standard when building Web Jobs in .NET full framework, but does not work straight out of the box when working with .NET Core. You can check our Part 3 right here!

Leave a Comment