devdeer.Libraries.JobScheduler 0.4.4

There is a newer version of this package available.
See the version list below for details.
dotnet add package devdeer.Libraries.JobScheduler --version 0.4.4
                    
NuGet\Install-Package devdeer.Libraries.JobScheduler -Version 0.4.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="devdeer.Libraries.JobScheduler" Version="0.4.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="devdeer.Libraries.JobScheduler" Version="0.4.4" />
                    
Directory.Packages.props
<PackageReference Include="devdeer.Libraries.JobScheduler" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add devdeer.Libraries.JobScheduler --version 0.4.4
                    
#r "nuget: devdeer.Libraries.JobScheduler, 0.4.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package devdeer.Libraries.JobScheduler@0.4.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=devdeer.Libraries.JobScheduler&version=0.4.4
                    
Install as a Cake Addin
#tool nuget:?package=devdeer.Libraries.JobScheduler&version=0.4.4
                    
Install as a Cake Tool

devdeer.Libraries.JobScheduler

NuGet Downloads

Disclaimer

If you want to use this package you should be aware of some principles and practices we at DEVDEER use. So be aware that this is not backed by a public repository. The purpose here is to give out customers easy access to our logic. Nevertheless you are welcome to use this lib if it provides any advantages.

Summary

This package can be used to easily configure a bunch of jobs which should be started based on configurable CRON triggers. THis packages uses Quartz.NET to implement triggers.

The default scenario targetted by this library is to be used by console applications which act as a long-running background service. The idea is that you can run a single or multiple jobs easily just by implementing an interface and add some configuration.

Quickstart

Create a new console project and install the Nuget package:

dotnet new console -n Sample
cd Sample
dotnet add package devdeer.Libraries.JobScheduler --prerelease

Open the project in your favorite editor and replace the code of Program.cs with:

using devdeer.Libraries.JobScheduler;
using devdeer.Libraries.JobScheduler.Attributes;
using devdeer.Libraries.JobScheduler.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Quartz;

var builder = Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) =>
    {
        services.UseScheduler(
            hostContext,
            opt =>
            {
                // TODO configure options and callbacks here
            });
    });
var app = builder.Build();
await app.StartJobsAsync();

[Job("Sample")]
public class SampleJob : BaseBackgroundJob
{
    /// <inheritdoc />
    public SampleJob(ILogger<SampleJob> logger) : base(logger)
    {
    }

    /// <inheritdoc />
    protected override async Task<bool> InternalExecuteAsync(IJobExecutionContext context)
    {
        Logger.LogInformation("I'M RUNNING");
        await Task.Yield();
        return true;
    }
}

Add a file to the project-directory named appsettings.json and give it the following content:

{
    "JobScheduler": {
        "DisplayTimeZoneId": "W. Europe Standard Time",
        "Jobs": {
            "Sample": {
                "JobName": "Sample Job",
                "Schedule": "*/5 * * * * ?",
                "Enabled": true,
                "Namespace": "Demo",
                "RunImmediately": true
            }
        }
    }
}

Open the file Sample.csproj in an editor and add the following section:

<ItemGroup>
  <None Remove="appsettings.json" />
  <Content Include="appsettings.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>

The complete Sample.csproj should look like this:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net9.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Microsoft.Extensions.Hosting" />
    </ItemGroup>
    <ItemGroup>
        <ProjectReference Include="..\..\..\src\stage-3\JobScheduler\JobScheduler.csproj"/>
    </ItemGroup>
    <ItemGroup>
      <None Remove="appsettings.json" />
      <Content Include="appsettings.json">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </Content>
    </ItemGroup>
</Project>

Now go back to your terminal and execute

dotnet run

You should see a bunch of info-loggings. At the end you should see a new log entry every 5 seconds.

Details

The central idea is that you will provide classes which implement the interface IBackgroundJob. If you want to inherit more conveniently just inherit from BaseBackgroundJob. This way your will have a logger hooked up and you just override one method. One important thing is that you need to decorate your class with the JobAttribute:

[Jobs("MyKey")]
public class MyJob : BaseBackgroundJob
{
    public SampleJob(ILogger<SampleJob> logger) : base(logger)
    {
    }

    protected override async Task<bool> InternalExecuteAsync(IJobExecutionContext context)
    {
        Logger.LogInformation("Started");
        // TODO your code here
        return Task.FromResult(true);
    }
}

InternalExecuteAsync should return false if some internal error occured. This signals the control plane that this job run was not finished successfully.

After you have implemented this the next step is to add some configuration data. You can use appsettings.json, environment variables, user secrets or Azure App Configuration. The internal implementation of the settings binder is using IOptionsSnapshot<> so that reloadable config stores are fully supported.

The simplest solution is to use the appsettings.json:

{
    "JobScheduler": {
        "DisplayTimeZoneId": "W. Europe Standard Time",
        "Jobs": {
            "MyJob": {
                "JobName": "Just a sample",
                "Schedule": "*/5 * * * * ?",
                "Enabled": true,
                "Namespace": "Demo",
                "RunImmediately": true
            }
        }
    }
}

The default key for configurations is the shown JobScheduler key. You can change this at will (see below). The DisplayTimeZoneId is used to provide a TimeZone-identifier so that the tooling understands and logs times related to that zone. The default time zone is UTC.

The second option Jobs is a dictionary. The key is a string which must match the key of the job that you defined in the attribute in the first step. The options provided are:

Option Mandatory Description
JobName x A human readable name mainly used for logging.
Namespace x A string which can be used to put jobs together in groups.
Schedule x A unix CRONTAB expression to define the rhythm with which to call the job.
Enabled A boolean value to switch this job on or off. Defaults to true.
RunImmediately A boolean value which indicates if this job should run as soon as the app starts.

You can add as many jobs as you like by just adding another class and then add a matching element to the Jobs-dictionary.

The final step is to call 2 extensions-methods in your app startup. The samples assume that your are familiar with the concept of .NET app hosting. If you struggle with this code you might want to read our blog article about Hosted Console Apps first.

However the following code is all you need to add:

var builder = Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) =>
    {
        services.UseScheduler(hostContext);
    });
var app = builder.Build();
await app.StartJobsAsync();

This represents the minimal code. There are 2 more overloads.

If you want to configure some specific settings use:

var builder = Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) =>
    {
        services.UseScheduler(hostContext, opt =>
        {

        });
    });

The options variable passed in (opt in the sample above) contains some properties:

Option Description
AfterJobFailAsync A callback that will be triggered after a job failed (i.e. the method returned false.
AfterJobSuccessAsync A callback that will be triggered after a job succeeded (i.e. the method returned true.
BeforeJobRunAsync A callback that will be triggered before a single job is executed.
FailOnJobError A boolean which if set to true (default is false) will let the whole process die if a job does not return true.
JobTriggers The data loaded from the .NET configuration.
OnJobException A callback that will be triggered if a job exception occured providing details about it.
QuartzOptions Gives direct access to the detailled options of the Quartz configuration.
ThrowExceptions A boolean which controls if exceptions from the control plane / jobs are rethrown to you.

If you want to store your settings under a key which differs from the default JobScheduler you can do this with:

BeforeJobRunAsync is a good place to run configuration reloads (for instance if you use Azure App Configuration). You could initialize config reloads like this:

var builder = Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) =>
    {
        services.UseScheduler(
            hostContext,
            opt =>
            {
                opt.BeforeJobRunAsync = async (provider, context) =>
                {
                    var refresher = provider.GetService<IConfigurationRefresher>();
                    if (refresher == null)
                    {
                        throw new InvalidOperationException("Could not retrieve configuration refresher service.");
                    }
                    await refresher.TryRefreshAsync();
                };
            });
    });

About DEVDEER

DEVDEER is a company from Magdeburg, Germany which is specialized in consulting, project management and development. We are very focussed on Microsoft Azure as a platform to run our products and solutions in the cloud. So all of our backend components usually runs in this environment and is developed using .NET. In the frontend area we are using react and a huge bunch of packages to build our UIs.

You can find us online:

Website

GitHub GitHub Org's stars

YouTube YouTube Channel Subscribers YouTube Channel Views

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.14 48 5/15/2026
0.4.13 100 5/11/2026
0.4.12 174 5/4/2026
0.4.11 80 5/4/2026
0.4.10 166 4/19/2026
0.4.8 123 4/5/2026
0.4.7 161 3/22/2026
0.4.6 107 3/6/2026
0.4.5 187 1/23/2026
0.4.4 153 1/14/2026
0.4.3 121 1/7/2026
0.4.2 120 12/30/2025
0.4.1 308 12/16/2025
0.4.0 165 12/13/2025
0.3.2 162 11/28/2025
0.3.1 473 11/20/2025
0.3.0 432 11/20/2025
0.2.3 158 11/7/2025
0.2.2 226 10/21/2025
0.2.0 211 10/21/2025
Loading failed

- Peeking job from queue when started to avoid double starts.
- Package updates.