Aicrosoft.Scheduling 8.0.0-beta.251110.1

This is a prerelease version of Aicrosoft.Scheduling.
dotnet add package Aicrosoft.Scheduling --version 8.0.0-beta.251110.1
                    
NuGet\Install-Package Aicrosoft.Scheduling -Version 8.0.0-beta.251110.1
                    
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="Aicrosoft.Scheduling" Version="8.0.0-beta.251110.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Aicrosoft.Scheduling" Version="8.0.0-beta.251110.1" />
                    
Directory.Packages.props
<PackageReference Include="Aicrosoft.Scheduling" />
                    
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 Aicrosoft.Scheduling --version 8.0.0-beta.251110.1
                    
#r "nuget: Aicrosoft.Scheduling, 8.0.0-beta.251110.1"
                    
#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 Aicrosoft.Scheduling@8.0.0-beta.251110.1
                    
#: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=Aicrosoft.Scheduling&version=8.0.0-beta.251110.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Aicrosoft.Scheduling&version=8.0.0-beta.251110.1&prerelease
                    
Install as a Cake Tool
   __        _            ___          _                   
   \ \  ___ | |__  ___   / __\_ _  ___| |_ ___  _ __ _   _ 
    \ \/ _ \| '_ \/ __| / _\/ _` |/ __| __/ _ \| '__| | | |
 /\_/ / (_) | |_) \__ \/ / | (_| | (__| || (_) | |  | |_| |
 \___/ \___/|_.__/|___/\/   \__,_|\___|\__\___/|_|   \__, |
                                                     |___/ 

Keywords: jobsFactory, jobs, task, job, routine, schedule, setup, startup, interval, cronexpression, jobagent

JobsFactory is a task scheduling framework that supports multiple types of jobs(tasks) and is easily extensible. It is written in dotnet8. It is an upgrade and refactoring of my earlier open-source projects BeesTask and SwarmTask.

An implementation example of the JobsFactory framework: JobAgent



JobsFactory Architecture

You can visit Architecture UML to view the UML class diagram.

Supported Job Types

Before using this framework, please determine whether it is suitable for your needs based on the supported job types.
Triggered by the value configured in Job.Trigger.

Value Type Example Value Description
Setup Installation and Upgrade "" Runs after startup
Startup Startup "2000" Runs before all tasks after Setup, with a delay of 2 seconds. May run continuously.
Event Event Trigger "DirChangeEventer" Triggered when the specified directory changes
Interval Interval Execution "00:00:20" Runs every 20 seconds
Schedulable Cron Expression "* 0/10 * * * ?" Cron expression: Executes every 10 minutes

NOTE: Job type extensions are not supported at the moment.

Quick Start (Simple Example)

Implement a Job

Code Job Description
public class Interval10Sec : Job
{
    public Interval10Sec()
    {
        Trigger = "00:00:10";
    }
    public override string? WorkerName => nameof(SimapleWorker);
}
Configuration Job Description
{
 "Jobs": {
   // SampleJobs is the module name, used to distinguish Jobs with the same Job name in multiple plugin modules.
   "SampleJobs": [
     {
       "name": "Interval10Sec",
       "trigger": "00:00:10",
       "workerName": "SimapleWorker"
     },
   ]
 }
}

Implement a Business Worker for a Job

Worker is Executing while Job Trigger (Job.WorkerName)

By using the following code, the SimpleWorker.ExecuteAsync method will be executed every 10 seconds.

[KeyedName]
public sealed class SimapleWorker(IServiceProvider serviceProvider) : Worker<JobContext>(serviceProvider)
{
    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        await Task.Delay(500, cancellationToken); //mock execute 0.5 second.
        await ExecuteCallbackAsync(true);// work finish callback. It's not necessary.
    }
   
    protected override async Task ExecuteCallbackAsync<TResult>(TResult? result) where TResult : default
    {
        if (result is true)
            Logger.LogDebug($"Worker are success.");
        await Task.CompletedTask;
    }
}

Note the handling of the CancellationToken signal, which will receive a cancellation signal when the application stops.

Key Components in the Framework

To better utilize the JobFactory framework and to extend and customize it more deeply, it is essential to understand the relevant components within the framework.

You can visit Architecture UML to view the UML class diagram.

Job - Task Configuration

  • A Job is a description of a task, through which you can set its job type and the business logic unit to be executed.
  • There are two ways to configure it: typically in the application configuration appsettings.json or in the configuration of a plugin module moduleAssemblyName.json. Alternatively, you can achieve the same effect by inheriting from the Job class directly in code.
  • Once the type of the Job's Trigger is set, do not change it.
Configuration in appsettings.json
{
 "Jobs": {
   // SampleJobs is the module name, used to distinguish Jobs with the same Job name in multiple plugin modules.
   "SampleJobs": [
     {
       "name": "Interval10Sec",
       "trigger": "00:00:10",
       "workerName": "SimapleWorker"
     },
   ]
 }
}
Code Implementation
public class Interval10Sec : Job
{
    public Interval10Sec()
    {
        Trigger = "00:00:10";
    }
    public override string? WorkerName => nameof(SimapleWorker);
}

JobContext - State and Context During Task Execution

  • JobContext is the context and state corresponding to each Job, which will be persisted to the default /states/ directory after the first run.
  • When the task runs again, it will read the last persisted state and restore it as the current JobContext.
  • Each JobContext must implement the corresponding JobFactory to create the context.
  • You can also use it directly without creating a custom JobContext. You can set and retrieve different working states through the Data property.
Custom CustomJobContext
public sealed class CustomJobContext : JobContext
{
    public bool Done
    {
        get; set;
    }
}

public sealed class CustomJobContextFactory(IServiceProvider serviceProvider)
    : JobContextServiceBase(serviceProvider), IJobContextFactory<CustomJobContext>, ITransient
{
    public override CustomJobContext LoadOrCreate(IJob job)
    {
        var ctx = LoadOrCreateNew(job, Create);
        return ctx;
    }

    protected override CustomJobContext Create() => new();
}

Worker - The Business Logic Execution Unit

  • Workers are registered to the DI container using their class name via KeyedName, or you can specify a string name. If no name is specified, the full class name will be registered in the DI container.
  • When implementing your own Worker business class, you need to point the WorkerName of the Job to it.
  • Each Worker requires a corresponding JobContext to determine the context type.
Simple Implementation
[KeyedName]
public sealed class SimapleWorker(IServiceProvider serviceProvider) : Worker<JobContext>(serviceProvider)
{
    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        await Task.Delay(500, cancellationToken); //mock execute 0.5 second.
        await ExecuteCallbackAsync(true);// work finish callback. It's not necessary.
    }
   
    protected override async Task ExecuteCallbackAsync<TResult>(TResult? result) where TResult : default
    {
        if (result is true)
            Logger.LogDebug($"Worker are success.");

        await Task.CompletedTask;
    }
}

Event - Customizing the Triggering Event for Event-Driven Jobs

  • This is the event trigger for the Job's triggering type.
  • Each event is different, so it needs to be implemented based on specific business requirements.
  • The class name of the event is the name of the class that starts the event detection.
Simple Implementation
[KeyedName]
public sealed class RouterEventer(IServiceProvider serviceProvider) : Eventer<TimeJobContext>(serviceProvider)
{
    public override async Task StartAsync(TimeJobContext? jobContext, CancellationToken cancellationToken)
    {
        await base.StartAsync(jobContext, cancellationToken);
        var receiver = new UdpReceiver(ServiceProvider, cancellationToken);
        receiver.OnMessage += async (s, e) =>
        {
            var workerName = jobContext.GetWorkerName();
            var worker = ServiceProvider.GetKeyedService<IWorker<TimeJobContext>>(workerName);
            if (worker == null)
            {
                Logger.LogError($"{this} No Worker[{workerName}] was found.");
                return;
            }

            var dnsrst = jobContext!.GetData<DDNSState>() ?? new DDNSState();
            jobContext.SetData(dnsrst);
            await worker.StartAsync(jobContext, cancellationToken); //trigger the worker.
        };
        await Task.CompletedTask;
    }
}

Watcher - Each Watcher Monitors Certain Types of Jobs

  • Each Watcher monitors one or more Jobs.
  • Each Watcher corresponds to a specific JobContext.
  • It creates the corresponding JobContext for the Job when it starts.
  • It is responsible for triggering and managing the Worker of the Job.
  • For Jobs of the same Trigger type, there may be multiple Watchers, with Watchers corresponding to Triggers and specific JobContexts.
  • You can implement a custom Watcher.
Simple Implementation of an Event Watcher
public sealed class MyEventWatcher(IServiceProvider serviceProvider) : EventWatcherBase<MyJobContext>(serviceProvider)
{
}
Implementation of a Custom Watcher
public sealed class MyWatcher(IServiceProvider serviceProvider) : Watcher<MyJobContext>(serviceProvider), ITransient
{
    protected override TriggerStyle TriggerStyle => TriggerStyle.Setup;

    protected override async Task ExecuteAsync()
    {
        var ctxs = JobContexts;
        using var logDis = Logger.BeginScopeLog(out string scopedId);
        try
        {
            var runjxs = ctxs.Where(x => !x.Done).OrderBy(x => x.OrderVersion);
            foreach (var run in runjxs)
            {
                if (AppCancellationToken.IsCancellationRequested) break;
                var name = run.GetWorkerName();
                var worker = ServiceProvider.GetKeyedService<IWorker<SetupJobContext>>(name);
                if (worker == null)
                    Logger.LogError($"{this} No Worker[{name}] was found.");
                else
                    await worker.StartAsync(run, AppCancellationToken);
            }
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, $"{this} has exception:{ex.Message}");
        }
    }
}

Usage

Starting from Scratch

Create a New dotnet8 Console Project
  • Add the required package references.
NuGet\Install-Package Aicrosoft.Scheduling
NuGet\Install-Package Aicrosoft.Extensions.Hosting
Modify Program.cs

using Aicrosoft.Logging;
using Aicrosoft.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NLog.Extensions.Logging;

Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory;
var start = Environment.TickCount64;
var logger = LogHelper.GetCurrentClassLogger();
LogHelper.De(logger);

#if !DEBUG
LogHelper.SetConfigurationVariable("logLevel", "Info");
#endif

try
{
    start = Environment.TickCount64;
    using IHost host = Host.CreateDefaultBuilder(args)
        .AddJobsFactory() //use jobsfactory component.
        .AddAsWindowsService("JobAgent")
        .AddAppSettingService() //appsettings.json 
        .AddServices(true,typeof(JobBiz).Assembly) //add entry assembly and biz assebmly to DI.
        .AddNLog() //log component
        //.AddPluginsService() //enable plugins support.
        .AddServiceRegisterDebug() //show DI table.
        .Build()
        ;
    ServiceLocator.ServiceProvider = host.Services; //DI Service hook.
    await host.RunAsync();
    return 0;
}
catch (Exception ex)
{
    logger.Error(ex, "Build and run IHost has a exception");
    return -9;
}
finally
{
    LogHelper.Shutdown();
}


Subsequent Steps
  • Add Job configurations or code.
  • Implement the business logic Worker.
  • If extending JobContext, implement the corresponding JobContextFactory and Worker.
  • Use the existing open-source JobAgent.
  • Create a new library project and add the reference.
NuGet\Install-Package Aicrosoft.Scheduling
NuGet\Install-Package Aicrosoft.Extensions.Hosting
  • The plugin assembly is your business module, where you can fully implement complex business logic.
  • As long as the corresponding Job description is implemented, it will be called by the framework.
  • ⚠️ Add <EnableDynamicLoading>true</EnableDynamicLoading> to the project properties of the plugin assembly to copy all the package DLLs.
Plugin Configuration Class
  • It automatically adds services from the plugin assembly to the DI container.
  • It defaults to reading the configuration from AssemblyName.json.
public class JobAppSetup : PluginSetupBase
{
}
Override the ConfigureServices Method
  • If you have custom configuration instances, override the ConfigureServices method.
public class JobAppSetup : PluginSetupBase
{
    protected override void ConfigureServices(HostBuilderContext hostBuilderContext, IServiceCollection services)
    {
        base.ConfigureServices(hostBuilderContext, services);
        services.Configure<DDNSOption>(hostBuilderContext.Configuration.GetSection(DDNSOption.SectionName));

        // Add custom service to DI
        services.AddSingleton<IMyBiz, MyBiz>();
    }
}
Subsequent Steps
  • Copy the files generated in the bin directory of the assembly to the Bin\Plugins\PluginName\ directory of JobAgent.

Configuration

Overview

  • The appsettings.json in JobAgent is not mandatory. Without it, all plugin modules will be loaded.
  • Different modules in the Plugins directory of JobAgent will load their correspondingly named configuration files by default.
  • If the Aicrosoft.Extensions.NLog module is used, nlog.config is the configuration for NLog.

Job Configuration

🚀 Job configuration is the core of the entire system

You can configure it in the appsettings.json under the root directory of JobAgent, or in the correspondingly named configuration under the plugin directory.

Job Configuration Example
{
  "Jobs": {
    // SampleJobs is the module name, used to distinguish Jobs with the same Job name in multiple plugin modules.
    "SampleJobs": [
      {
        "enable": false,
        "name": "Interval-Woker-Sample1",
        "trigger": "00:00:05",
        "workerName": "SampleJobs.Aicrosoft.SimpleIntervalWorker, SampleJobs"
      },
      {
        "enable": true,
        "name": "L11",
        "trigger": "* 0/10 * * * ?",
        "priority": "lowest",
        "workerName": "LoopSampleWorker"
      },
    ]
  }
}

Job Parameter Description
  • Jobs - A structure keyed by module name, with Job configurations as arrays.
  • name - The name of the Job, not mandatory.
  • enable - Whether to enable this task.
  • trigger - The triggering method, refer to Supported Job Types.
  • workerName - The name in the DI container for the business logic class to be executed after the Job is triggered.
  • timeout - Default is 30 seconds. It is the timeout for executing the specific business logic. Jobs of the Startup type are not limited by this.
  • extend - A dictionary of type Dictionary<string, string> that provides additional property extensions for this Job.
  • Subclasses of Job can implement equivalent configurations in the configuration.
Configuration for TimeWatcher
{
    "TimeWatcher": {
        "Delay": "00:00:30" 
  },
}

  • The node can be omitted and the default value will be used.
  • TimeWatcher will run after a 30-second delay by default.
  • TimeWatcher monitors Jobs of the Interval and Schedulable types.
Plugin Configuration
{
    "Plugins": {
        "PluginsRoot": "Plugins",
        "DisableAutoload": true, 
        "AssemblyNames": {
            "SampleJobs": true,
            "DDNSJob": false
        }
    }
}
  • PluginsRoot - The root directory name for plugins, default is Plugins.
  • DisableAutoload - When set to true, it will load plugins based on the AssemblyNames configuration instead of automatically loading them.
  • AssemblyNames - Specifies which plugins to load.
    • If false, it will automatically load all plugins under the PluginsRoot directory.
Common Parameter Configuration
{
 "Resources": [
   {
     "name": "NetFile",
     "type": "Http",
     "value": "http://127.0.0.1/{0:yyyyMMdd}.txt"
   },
   {
     "name": "LocalFile",
     "type": "Local",
     "value": "Data\\{0:yyyyMMdd}_{1}.txt",
     "params": {
       "encoding": "gb2312",
       "account": "admin",
       "password": "admin1234.com"
     }
   },
   {
     "name": "RankDb",
     "type": "SqlServer",
     "value": "Data Source=192.168.1.50;Initial Catalog=DbRank;User ID=sa;Password=sa;App=WST_SG2Rank V1.0"
   }
 ]
}
Obtain Configuration Information
var resources = Services.GetService<IOptions<ResourcesOption>>().Value;

Dynamic Configuration Loading

Dynamic configuration loading is not supported at the moment. If you modify the configuration, you must restart the application.

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 was computed.  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 was computed.  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
8.0.0-beta.251110.1 333 11/10/2025