5 minute read

With the recent release of Azure Functions Runtime 2.0, WebJobs SDK 3.0 got released alongside. WebJobs SDK is the backbone of Azure Functions, however it can be also used standalone to power Azure WebJobs which you can host alongside your App Service. SDK 3.0 brings the configuration much closer to ASP.NET Core, runs under both .NET Framework and .NET Core and for example supports Dependency Injection by default.

SDK 3.0 was released a while ago, however there is no changelog available (yet) neither the documentation or the samples were upgraded yet. I decided to upgrade anyways, since the SDK is opensource so if I hit some issue, I can troubleshoot it myself. Luckily, this wasn't much of a case.

First off, you want to start with upgrading Nuget packages to the latest versions. Then, you will need to modify the Program.cs setup. Like mentioned above, SDK 3.0 brings WebJobs much closer to ASP.NET Core configuration, which is pretty nice. You might want to take a look at the only sample at the time of writing which is included with the SDK's source.

However, you can notice few things, which I would say should not made it into the sample:

First off, the Envionment is configured statically by UseEnvironment - which I really don't like. In ASP.NET Core, you can configure Environment by ASPNETCORE_ENVIRONMENT env variable, here this one won't work. Since ASP.NET Core uses WebHostBuilder and WebJobs use the HostBuilder, there are few differences: In order to specify environment, you need to use environment variable name, just like that.

You can setup the environment variable in the Debug options of your project just like with ASP.NET Core.

Next, I really like the concept of User Secrets in ASP.NET Core for development, so why not use those here too? In order to do that, you will need to make two modifications. First, setup the UserSecretsId assembly attribute on the Program class:

[assembly: UserSecretsId("project-name")]

Thanks to this, the assembly will now contain the information about User Secrets. In ASP.NET Core, this is defined in .csproj - so far, I haven't found a way to do this with a WebJob - the build seems to ignore it. I will probably dedicate it a separate article. And then, you need to setup ConfigureHostConfiguration on the HostBuilder like so:

This is going to tell it to use Command Line arguments, Environment Variables and Secrets. Next up is the configuration of all the required triggers which you might be using in ConfigureWebJobs section:

.ConfigureHostConfiguration(config =>
{
    config.AddCommandLine(args);
    config.AddEnvironmentVariables();
    config.AddUserSecrets<Program>();
})
// ...
.ConfigureWebJobs(config =>
{
    config.AddAzureStorageCoreServices();
    config.AddTimers();
})

Basically, AddAzureStorageCoreServices makes sure that your WebJob is hooked to a storage account for persisting data, creating logs etc. AddTimers is from WebJobs.Extensions package and allows you to periodically trigger some tasks. You can also use other extensions to connect to Event Grid, Service Bus etc.

Then you should configure logging by ConfigureLogging. In the sample, they don't check the environment and simple set the debug level to Debug, however since we set the environment previously, we can set it based on the environment:

.ConfigureLogging((context, config) =>
{
    if (context.HostingEnvironment.IsDevelopment())
    {
        config.SetMinimumLevel(LogLevel.Debug);
        config.AddConsole();
    }
})

Then we need to configure the Dependency Injection if needed by ConfigureServices.

.ConfigureServices((context, services) =>
{
    services.AddMemoryCache();
    services.AddSingleton(context.Configuration);
    services.AddSingleton();
    services.AddScoped<Functions, Functions>();
})

Remember to always register your Functions class into services, if you don't do that, the depedendency injection will not work properly.

Here I have hit a thing which I need to investigate a bit further - the SDK 3.0 seems to support DI by default however, it doesn't seem to work:

Event tho the runtime registers DefaultJobActivator, it doesn't seem to resolve the services from the container and you end up with: System.MissingMethodException: No parameterless constructor defined for this object. error, so instead I decided to use my own IJobActivator implementation from SDK 2.0:

class JobActivator : IJobActivator
{
    private readonly IServiceProvider _service;

    public JobActivator(IServiceProvider service)
    {
        _service = service;
    }
    public T CreateInstance<T>()
    {
        return (T)_service.GetService(typeof(T));
    }
}

You simply register it into the DI and it is going to work fine. I am not quite sure why the default activator doesn't work for me yet - I will investigate it and update the article if I end up with some results.

Last thing to do is to start using .NET Core logging which basically means, that you replace TextWriter with ILogger and use it just like in an ASP.NET Core app. You can leave TextWriter as is since it will work, however I suggest you switch to ILogger so that you have unified logging across the app. You can optionally add Application Insights if needed - those are part of the sample.

Comments

Ed Baker

.NET Core WebJob SDK 3.x this is what my program looks like:

    static void Main(string[] args)
    {
        var builder = new HostBuilder()
            .UseEnvironment("Development")
            .ConfigureWebJobs(b =>
            {
                b.AddAzureStorageCoreServices()
                    .AddTimers();
            })
            .ConfigureAppConfiguration(SetupConfiguration)
            .ConfigureLogging((context, b) =>
            {
                b.SetMinimumLevel(LogLevel.Debug);
                b.AddConsole();

                // If this key exists in any config, use it to enable App Insights
                var appInsightsKey = Configuration.GetSection("ApplicationInsights").Value;
                if (!string.IsNullOrEmpty(appInsightsKey))
                {
                    b.AddApplicationInsights(o => o.InstrumentationKey = appInsightsKey);
                }
            })
            .ConfigureServices(ConfigureServices)
            .UseConsoleLifetime();

        var host = builder.Build();
        host.Run();
    }

    private static void SetupConfiguration(IConfigurationBuilder builder)
    {
        Configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddEnvironmentVariables()
            .Build();
    }

    private static void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(Configuration);
        services.AddSingleton<IJobActivator, JobActivator>();
    }
SeboStone

I did the upgrade too, but I get always an exception that means that my AzureStorageAccount connectionstring is null. I did not found any possibility to configure the connectionstring. How did you configure the AzureStorageAccount connectionstring?

SeboStone

ok, I found a stupid magic in this post: https://github.com/Azure/azure-webjobs-sdk/issues/1963#issue-370375542

Nicholas Mitchell

Thanks for writing this, but having screwed around with this latest version of Web Jobs for a couple of days, hitting bug after bug with no documentation, and being completely new to Web Jobs, I’m sticking with version two. Updating the Web Jobs project template makes everything break! I tried working with V1 of Azure Functions but there were so many package conflicts that I moved on to Version 2, but the new framework it’s based on (.NET Core or Standard, or whatever it is) wouldn’t work with my .NET web app code. The whole thing has just been a big dumpster fire of pain. I really can’t believe how unusable and undocumented these products are when ASP.NET and Azure have been so good in the past.

So, one thing I can’t for the life of me figure out, if, as you said, “SDK 3.0 brings WebJobs much closer to ASP.NET Core”, is it still the old .NET? Or is it .NET Core, or Standard? And assuming they fix the usability and documentation problems, will V3 work with old .NET Framework code?

To submit comments, go to GitHub Discussions.