FractalDataWorks.Services.Scheduling 0.9.0-alpha.1011.ged0a6c6e98

This is a prerelease version of FractalDataWorks.Services.Scheduling.
dotnet add package FractalDataWorks.Services.Scheduling --version 0.9.0-alpha.1011.ged0a6c6e98
                    
NuGet\Install-Package FractalDataWorks.Services.Scheduling -Version 0.9.0-alpha.1011.ged0a6c6e98
                    
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="FractalDataWorks.Services.Scheduling" Version="0.9.0-alpha.1011.ged0a6c6e98" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FractalDataWorks.Services.Scheduling" Version="0.9.0-alpha.1011.ged0a6c6e98" />
                    
Directory.Packages.props
<PackageReference Include="FractalDataWorks.Services.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 FractalDataWorks.Services.Scheduling --version 0.9.0-alpha.1011.ged0a6c6e98
                    
#r "nuget: FractalDataWorks.Services.Scheduling, 0.9.0-alpha.1011.ged0a6c6e98"
                    
#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 FractalDataWorks.Services.Scheduling@0.9.0-alpha.1011.ged0a6c6e98
                    
#: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=FractalDataWorks.Services.Scheduling&version=0.9.0-alpha.1011.ged0a6c6e98&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=FractalDataWorks.Services.Scheduling&version=0.9.0-alpha.1011.ged0a6c6e98&prerelease
                    
Install as a Cake Tool

FractalDataWorks.Services.Scheduling

Overview

The Scheduling framework provides ServiceType auto-discovery for job scheduling and task execution with unified interfaces that work across different scheduling providers and execution engines.

Features

  • ServiceType Auto-Discovery: Add scheduling packages and they're automatically registered
  • Universal Scheduling Interface: Same API works with all scheduling providers
  • Dynamic Scheduler Creation: Scheduling services created via factories
  • Source-Generated Collections: High-performance scheduler lookup

Quick Start

1. Install Packages

<ProjectReference Include="..\FractalDataWorks.Services.Scheduling\FractalDataWorks.Services.Scheduling.csproj" />
<ProjectReference Include="..\FractalDataWorks.Services.Scheduling.Quartz\FractalDataWorks.Services.Scheduling.Quartz.csproj" />

2. Register Services

// Program.cs - Zero-configuration registration
builder.Services.AddScoped<IGenericSchedulingProvider, GenericSchedulingProvider>();

// Single line registers ALL discovered scheduling types
SchedulingTypes.Register(builder.Services);

3. Configure Scheduling

{
  "Scheduling": {
    "QuartzScheduler": {
      "SchedulerType": "Quartz",
      "MaxConcurrentJobs": 10,
      "MisfireThreshold": 60000,
      "EnablePersistence": true,
      "ConnectionString": "Server=localhost;Database=Quartz;Integrated Security=true;"
    }
  }
}

4. Use Universal Scheduling

public class ReportService
{
    private readonly IGenericSchedulingProvider _schedulingProvider;

    public ReportService(IGenericSchedulingProvider schedulingProvider)
    {
        _schedulingProvider = schedulingProvider;
    }

    public async Task<IGenericResult> ScheduleDailyReportAsync()
    {
        var schedulerResult = await _schedulingProvider.GetScheduler("QuartzScheduler");
        if (!schedulerResult.IsSuccess)
            return GenericResult.Failure(schedulerResult.Error);

        using var scheduler = schedulerResult.Value;

        // Universal job scheduling - works with any scheduler
        var job = new ScheduledJob
        {
            Name = "DailyReport",
            Group = "Reports",
            JobType = typeof(DailyReportJob),
            Schedule = CronSchedule.Daily(hour: 6, minute: 0), // 6:00 AM daily
            Data = new Dictionary<string, object>
            {
                ["ReportType"] = "Daily",
                ["Recipients"] = new[] { "admin@company.com" }
            }
        };

        var result = await scheduler.ScheduleJobAsync(job);
        return result;
    }

    public async Task<IGenericResult> ScheduleOneTimeTaskAsync(DateTime executeAt)
    {
        var schedulerResult = await _schedulingProvider.GetScheduler("QuartzScheduler");
        if (!schedulerResult.IsSuccess)
            return GenericResult.Failure(schedulerResult.Error);

        using var scheduler = schedulerResult.Value;

        var job = new ScheduledJob
        {
            Name = $"OneTime_{Guid.NewGuid()}",
            Group = "OneTime",
            JobType = typeof(CustomTaskJob),
            Schedule = SimpleSchedule.At(executeAt),
            Data = new Dictionary<string, object>
            {
                ["TaskId"] = Guid.NewGuid(),
                ["Priority"] = "High"
            }
        };

        return await scheduler.ScheduleJobAsync(job);
    }
}

Available Scheduling Types

Package Scheduler Type Purpose
FractalDataWorks.Services.Scheduling.Quartz Quartz Quartz.NET scheduler integration
FractalDataWorks.Services.Scheduling.Hangfire Hangfire Hangfire background job processor
FractalDataWorks.Services.Scheduling.Timer Timer Simple timer-based scheduling

How Auto-Discovery Works

  1. Source Generator Scans: [ServiceTypeCollection] attribute triggers compile-time discovery
  2. Finds Implementations: Scans referenced assemblies for types inheriting from SchedulingTypeBase
  3. Generates Collections: Creates SchedulingTypes.All, SchedulingTypes.Name(), etc.
  4. Self-Registration: Each scheduling type handles its own DI registration

Adding Custom Scheduling Types

// 1. Create your scheduling type (singleton pattern)
public sealed class CustomSchedulingType : SchedulingTypeBase<IGenericScheduler, CustomSchedulingConfiguration, ICustomSchedulingFactory>
{
    // ❌ WRONG - Do NOT add Instance property (old/incorrect pattern)
    // public static CustomSchedulingType Instance { get; } = new();

    private CustomSchedulingType() : base(4, "Custom", "Scheduling Providers") { }

    public override Type FactoryType => typeof(ICustomSchedulingFactory);

    public override void Register(IServiceCollection services)
    {
        services.AddScoped<ICustomSchedulingFactory, CustomSchedulingFactory>();
        services.AddScoped<CustomJobStore>();
        services.AddScoped<CustomTriggerBuilder>();
    }
}

// 2. Add package reference - source generator automatically discovers it
// 3. SchedulingTypes.Register(services) will include it automatically

Common Scheduling Patterns

Recurring Jobs

public class MaintenanceService
{
    private readonly IGenericSchedulingProvider _schedulingProvider;

    public MaintenanceService(IGenericSchedulingProvider schedulingProvider)
    {
        _schedulingProvider = schedulingProvider;
    }

    public async Task<IGenericResult> ScheduleMaintenanceJobsAsync()
    {
        var schedulerResult = await _schedulingProvider.GetScheduler("QuartzScheduler");
        if (!schedulerResult.IsSuccess)
            return GenericResult.Failure(schedulerResult.Error);

        using var scheduler = schedulerResult.Value;

        // Database cleanup - weekly on Sunday at 2 AM
        var dbCleanup = new ScheduledJob
        {
            Name = "DatabaseCleanup",
            Group = "Maintenance",
            JobType = typeof(DatabaseCleanupJob),
            Schedule = CronSchedule.Weekly(DayOfWeek.Sunday, hour: 2, minute: 0)
        };

        var dbResult = await scheduler.ScheduleJobAsync(dbCleanup);
        if (!dbResult.IsSuccess)
            return dbResult;

        // Log rotation - daily at midnight
        var logRotation = new ScheduledJob
        {
            Name = "LogRotation",
            Group = "Maintenance",
            JobType = typeof(LogRotationJob),
            Schedule = CronSchedule.Daily(hour: 0, minute: 0)
        };

        return await scheduler.ScheduleJobAsync(logRotation);
    }
}

Job Monitoring and Management

public class JobManagementService
{
    private readonly IGenericSchedulingProvider _schedulingProvider;

    public JobManagementService(IGenericSchedulingProvider schedulingProvider)
    {
        _schedulingProvider = schedulingProvider;
    }

    public async Task<IGenericResult<List<JobStatus>>> GetJobStatusAsync()
    {
        var schedulerResult = await _schedulingProvider.GetScheduler("QuartzScheduler");
        if (!schedulerResult.IsSuccess)
            return GenericResult<List<JobStatus>>.Failure(schedulerResult.Error);

        using var scheduler = schedulerResult.Value;

        // Get all scheduled jobs
        var jobsResult = await scheduler.GetAllJobsAsync();
        if (!jobsResult.IsSuccess)
            return GenericResult<List<JobStatus>>.Failure(jobsResult.Error);

        var statuses = new List<JobStatus>();
        foreach (var job in jobsResult.Value)
        {
            var statusResult = await scheduler.GetJobStatusAsync(job.Name, job.Group);
            if (statusResult.IsSuccess)
                statuses.Add(statusResult.Value);
        }

        return GenericResult<List<JobStatus>>.Success(statuses);
    }

    public async Task<IGenericResult> PauseJobAsync(string jobName, string jobGroup)
    {
        var schedulerResult = await _schedulingProvider.GetScheduler("QuartzScheduler");
        if (!schedulerResult.IsSuccess)
            return GenericResult.Failure(schedulerResult.Error);

        using var scheduler = schedulerResult.Value;
        return await scheduler.PauseJobAsync(jobName, jobGroup);
    }

    public async Task<IGenericResult> ResumeJobAsync(string jobName, string jobGroup)
    {
        var schedulerResult = await _schedulingProvider.GetScheduler("QuartzScheduler");
        if (!schedulerResult.IsSuccess)
            return GenericResult.Failure(schedulerResult.Error);

        using var scheduler = schedulerResult.Value;
        return await scheduler.ResumeJobAsync(jobName, jobGroup);
    }
}

Dynamic Job Creation

public class DynamicJobService
{
    private readonly IGenericSchedulingProvider _schedulingProvider;

    public DynamicJobService(IGenericSchedulingProvider schedulingProvider)
    {
        _schedulingProvider = schedulingProvider;
    }

    public async Task<IGenericResult> ScheduleDataProcessingJobAsync(int dataSetId, TimeSpan delay)
    {
        var schedulerResult = await _schedulingProvider.GetScheduler("QuartzScheduler");
        if (!schedulerResult.IsSuccess)
            return GenericResult.Failure(schedulerResult.Error);

        using var scheduler = schedulerResult.Value;

        var executeAt = DateTimeOffset.UtcNow.Add(delay);
        var job = new ScheduledJob
        {
            Name = $"DataProcessing_{dataSetId}_{DateTimeOffset.UtcNow.Ticks}",
            Group = "DataProcessing",
            JobType = typeof(DataProcessingJob),
            Schedule = SimpleSchedule.At(executeAt),
            Data = new Dictionary<string, object>
            {
                ["DataSetId"] = dataSetId,
                ["ProcessingMode"] = "Batch",
                ["MaxRetries"] = 3
            }
        };

        return await scheduler.ScheduleJobAsync(job);
    }
}

Architecture Benefits

  • Scheduler Agnostic: Switch scheduling providers without code changes
  • Zero Configuration: Add package reference, get functionality
  • Type Safety: Compile-time validation of scheduling types
  • Performance: Source-generated collections use FrozenDictionary
  • Scalability: Each scheduling type manages its own job execution strategy

For complete architecture details, see Services.Abstractions README.

Product Compatible and additional computed target framework versions.
.NET 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.9.0-alpha.1011.ged0a6c6e98 41 11/18/2025
0.9.0-alpha.1010.gecd88aac50 42 11/18/2025
0.9.0-alpha.1009.g7f6817e985 37 11/18/2025
0.9.0-alpha.1006.gf287016c0c 38 11/18/2025
0.8.0-alpha.1011 36 11/18/2025
0.7.0-alpha.1022 128 11/3/2025
0.7.0-alpha.1021 131 11/3/2025
0.7.0-alpha.1008 104 11/2/2025
0.7.0-alpha.1006 127 10/30/2025
0.7.0-alpha.1005 130 10/30/2025
0.7.0-alpha.1004 126 10/30/2025
0.7.0-alpha.1001 138 10/29/2025
0.6.0-alpha.1006 132 10/29/2025
0.6.0-alpha.1005 128 10/28/2025
0.6.0-alpha.1004 129 10/28/2025