DEV Community

Cover image for Building A .NET Core 3 Scheduled Job Worker Service
James Hickey
James Hickey

Posted on • Originally published at blog.jamesmichaelhickey.com

Building A .NET Core 3 Scheduled Job Worker Service

The .NET Core CLI comes with tons of pre-built project templates! One of the new templates that will be included with .NET Core 3 will be for building worker services.

Combining .NET Core worker services with Coravel can help you build lightweight background job scheduling applications very quickly. Let's take a look at how you can do this in just a few minutes!

Note: Worker services are lightweight console applications that perform some type of background work like reading from a queue and processing work (like sending e-mails), performing some scheduled background jobs from our system, etc. These might be run as a daemon, windows service, etc.

Installing .NET Core 3 Preview

At the writing on this article, .NET Core 3 is in preview. First, you must install the SDK. You can use Visual Studio Code for everything else in this article πŸ‘.

Coravel's Task Scheduling

Coravel is a .NET Core library that gives you advanced application features out-of-the-box with near-zero config. I was inspired by Laravel's ease of use and wanted to bring that simple and accessible approach of building web applications to .NET Core.

One of those features is a task scheduler that is configured 100% by code.

By leveraging Coravel's ease-of-use with the simplicity of .NET Core's worker service project template, I'll show you how easily and quickly you can build a small back-end console application that will run your scheduled background jobs!

Worker Service Template

First, create an empty folder to house your new project.

Then run:

dotnet new worker

Your worker project is all set to go! πŸ€œπŸ€›

Check out Program.cs and you'll see this:

public static void Main(string[] args)
{
    CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices(services =>
        {
            services.AddHostedService<Worker>();
        });
Enter fullscreen mode Exit fullscreen mode

Configuring Coravel

Let's add Coravel by running dotnet add package coravel.

Next, in Program.cs, we'll modify the generic code that was generated for us and configure Coravel:

public static void Main(string[] args)
{
    IHost host = CreateHostBuilder(args).Build();
    host.Services.UseScheduler(scheduler => {
        // We'll fill this in later ;)
    });
    host.Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices(services =>
        {
            services.AddScheduler();
        });
};
Enter fullscreen mode Exit fullscreen mode

Since Coravel is a native .NET Core set of tools, it just worksβ„’ with zero fuss!

Adding An Invocable

One of Coravel's fundamental concepts is Invocables.

Each invocable represents a self-contained job within your system that Coravel leverages to make your code much easier to write, compose and maintain.

Next, then, create a class that implements Coravel.Invocable.IInvocable:

public class MyFirstInvocable : IInvocable
{
    public Task Invoke()
    {
        Console.WriteLine("This is my first invocable!");
        return Task.CompletedTask;
    }
}
Enter fullscreen mode Exit fullscreen mode

Since we are going to simulate some async work, we'll just log a message to the console and then return Task.CompletedTask to the caller.

Scheduling Your Invocable

Here's where Coravel really shines πŸ˜‰.

Let's schedule our new invocable to run every 5 seconds. Inside of our Program.cs main method we'll add:

host.Services.UseScheduler(scheduler => {
    // Yes, it's this easy!
    scheduler
        .Schedule<MyFirstInvocable>()
        .EveryFiveSeconds();
});
Enter fullscreen mode Exit fullscreen mode

Don't forget to register your invocable with .NET Core's service container:

.ConfigureServices(services =>
{
    services.AddScheduler();
    // Add this πŸ‘‡
    services.AddTransient<MyFirstInvocable>();
});
Enter fullscreen mode Exit fullscreen mode

In your terminal, run dotnet run.

You should see the output in your terminal every five seconds!

Real-World Invocable

Sure, writing to the console is great - but you are going to be making API calls, database queries, etc. after all.

Let's modify our invocable so that we can do something more interesting:

public class SendDailyReportEmailJob : IInvocable
{
    private IMailer _mailer;
    private IUserRepository _repo;

    public SendDailyReportEmailJob(IMailer mailer, IUserRepository repo)
    {
        this._mailer = mailer;
        this._repo = repo;
    }

    public async Task Invoke()
    {
        var users = await this._repo.GetUsersAsync();

        foreach(var user in users)
        {
            var mailable = new DailyReportMailable(user);
            await this._mailer.SendAsync(mailable);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Since this class will hook into .NET Core's service container, all the constructor dependencies will be injected via dependency injection.

If you wanted to build a lightweight background application that processes and emails daily reports for all your users then this might be a great option.

Configuring As A Windows Service

While beyond the scope of this article, you can take a look at how .NET Core 3 will allow configuring your worker as a windows service.

And, apparently, there's upcoming support for systemd too!

Conclusion

What do you guys think about .NET Core's worker services?

I find they are so easy to get up-and-running. Coupled with the accessibility designed into Coravel, I find these two make an awesome pair for doing some cool stuff!

All of Coravel's features can be used within these worker services - such as queuing tasks, event broadcasting, mailing, etc.

One thing I'd love to try is to integrate Coravel Pro with a worker service. One step at a time though 🀣.

Keep In Touch

Don't forget to connect with me on:

You can also find me at my web site www.jamesmichaelhickey.com.

Navigating Your Software Development Career Newsletter

An e-mail newsletter that will help you level-up in your career as a software developer! Ever wonder:

βœ” What are the general stages of a software developer?
βœ” How do I know which stage I'm at? How do I get to the next stage?
βœ” What is a tech leader and how do I become one?
βœ” Is there someone willing to walk with me and answer my questions?

Sound interesting? Join the community!

Top comments (12)

Collapse
 
kaos profile image
Kai Oswald

I really like the concept of Invocables! It keeps the code clean.

For scheduling I've always used hangfire, which works really well, but things can get messy really fast since scheduling in hangfire works with anonymous method invocation

var jobId = BackgroundJob.Schedule(
    () => Console.WriteLine("Delayed!"),
    TimeSpan.FromDays(7));

Is there a way to update/cancel a scheduled task with Coravel (like in the above example where a jobId is returned?
In my side project I'm currently using hangfire, but Coravel looks a lot cleaner and I've already wondered how I can clean up these hangfire invocations.

Collapse
 
jamesmh profile image
James Hickey

Hangfire is def the defacto right now. Coravel was never built as a direct alternative, but many have pointed out that it's much easier to use.

Also, Coravel supports true async jobs, whereas Hangfire doesn't actually support true async jobs. So all that I/O in your background jobs will actually block your threads πŸ€ͺ.

See here for more

So, the answer to your question is "yes and no". There's an open issue here that I have on the todo list.

I offered a temporary/potential solution for now in that issue. Basically, you would just manage the tasks in a collection yourself. Coravel gives you some lower-level methods to start/stop any jobs you want (although, it's a workaround of sorts until there is an actual feature added πŸ˜‚).

Collapse
 
jamesmh profile image
James Hickey

You should check out builtwithdot.net/ - it's a showcase of projects built with .NET. You can filter the results by using the first filter on the top left to limit it to .NET Core.

Collapse
 
tfitz237 profile image
tfitz237

Interesting!

We've always used Window's Task Scheduler for our CRON jobs.

  • Let the app not have to worry about it being a scheduled task, and let Windows worry about that instead.
  • Deployment configuration creates the scheduled task and determines time of day / enabled status.

This seems like an interesting alternative, and allows more control at the application level. But comparatively in our current configuration, to change the time of day or enabled/disabled status, we don't have to deploy a new version of the application to PROD, just change it in the task scheduler.

The Pro dashboard seems a lot more user friendly way of dealing with the tasks too, and wouldn't require as many user permissions.

Thanks for the tutorial

Collapse
 
jamesmh profile image
James Hickey

Thanks for the feedback!

One of the issues it seems many devs run into with Window's task scheduler are, as you said, basic user permissions πŸ˜‚.

Plus, if you want to move all your infrastructure to serverless / container services, etc. then with something like Coravel you literally have nothing to change. Using Windows Task Scheduler def. couples you to using a full VM or bare-metal infra.

And yes, Coravel Pro stores all the schedules in your DB so you can just change schedules on PROD with no deployments πŸ‘

.NET Core 3 will have lots of cool stuff! Looking forward to it.

Collapse
 
codenthusiast profile image
Popoola Gafar Babatunde

Thanks for creating this beautiful solution. This is the future. Mailer, events and queue? This is really amazing!!!

Collapse
 
jamesmh profile image
James Hickey

Thanks!

Collapse
 
lumogox profile image
Luis Molina

Seams like something I can use in my next project!, nice work :)

Do you have any recommendations on how to implement this with logging with a database for .net core 3?

Collapse
 
jamesmh profile image
James Hickey

I think you can inject ILogger<T> into any class you want when using the built-in DI system.

For a database, you could use something like Dapper or integrate EF core into the library (this article might help)?

Collapse
 
gfrgomes profile image
Gustavo Gomes

Great article just what I needed! Thank you!

Collapse
 
jamesmh profile image
James Hickey

Thanks!

Collapse
 
gyurisc profile image
gyurisc

Great article! Thanks for writing it!