Ticomix.WebJobs 2025.12.11.1
Ticomix.WebJobs
Purpose Statement
A .NET library that provides comprehensive background job scheduling and execution capabilities for web applications, including support for both scheduled jobs and on-demand background tasks. Built with Quartz.NET for robust job scheduling with distributed locking, progress tracking, and SignalR integration for real-time updates. Ideal for enterprise applications requiring reliable background processing.
Database Models Used
The package requires the following database models to be added to your application:
- JobStatus: Main job configuration and scheduling settings
- JobRun: Individual job execution history and status tracking
- SysJobStatus: System job status lookup values
- ErrorLog: Comprehensive error logging and monitoring (for logging job errors)
Additional context file required:
ApplicationDbContextWebJob.cs
Classes and Functions
Core Job Management
- CommonJobBase
: Abstract base class for all background jobs - JobHelper
: Utility class for job execution and management - JobManager
: Static manager for job discovery, execution, and lifecycle - JobHostedService
: Hosted service for automatic job scheduling
Background Tasks
- IBackgroundTask: Interface for long-running background tasks with progress reporting
- BackgroundTaskHelper: Manager for executing background tasks with progress tracking
- TaskState: State management for background task execution
SignalR Integration
- TaskProgressHub: SignalR hub for real-time progress updates
- TaskProgressHubClient: Client for sending progress updates to connected clients
Quartz Integration
- TicomixJobRunner
: Quartz job runner for scheduled job execution - TicomixQuartzExt: Extension methods for Quartz configuration
Attributes
- JobSettingsAttribute: Metadata attribute for job configuration (Category, DisplayName)
Installation Instructions
Prerequisites
Ensure your application has the following NuGet packages installed:
Ticomix.WebJobsTicomix.EFCoreTicomix.CommonTicomix.Attachments.CommonTicomix.EmailNote
Database Setup
- Add the required database models (JobStatus, JobRun, SysJobStatus) to your project
- Include the context file
ApplicationDbContextWebJob.csin your\Datafolder - Run database migrations to create the necessary tables
ASP.NET Core Configuration
Startup.cs / Program.cs Changes
- Add Required Using Statements:
using Ticomix.WebJobs;
using Ticomix.WebJobs.QuartzJob;
- Configure Services:
// Background Task Helper
services.AddSingleton<IBackgroundTaskHelper, BackgroundTaskHelper>();
// Distributed Locking (required for job execution)
services.AddSingleton<IDistributedLockProvider>((services) =>
new SqlDistributedSynchronizationProvider(Configuration.GetConnectionString("DefaultConnection")));
// Quartz Scheduler with Ticomix Integration
services.AddTicomixQuartz(RunJobsSeconds); // RunJobsSeconds is interval in seconds
// Hosted Service for automatic job scheduling (alternative to Quartz)
services.AddHostedService<JobHostedService<int>>();
// SignalR for real-time progress updates (optional)
services.AddSignalR();
services.AddSingleton<TaskProgressHubClient>();
- Configure Middleware (if using SignalR):
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<TaskProgressHub>("/taskProgressHub");
// ... other endpoints
});
- Setup Jobs on Application Start:
// In Configure method
await JobManager<int>.SetupJobs(app.ApplicationServices);
// Configure Quartz Logging (optional)
Quartz.Logging.LogProvider.SetCurrentLogProvider(new ILoggerLogProvider(app.ApplicationServices));
Database Context Implementation
Create ApplicationDbContextWebJob.cs in your Data folder:
using Ticomix.Common.EFCore;
using Ticomix.Common.Models;
using Ticomix.WebJobs.Data;
namespace YourProject.Data
{
public partial class ApplicationDbContext : IWebJobDbContext<int>
{
GenericDbSet<IJobRun<int>> IWebJobDbContext<int>.JobRun =>
new GenericDbSet<IJobRun<int>>(this, this.JobRun);
GenericDbSet<IJobStatus<int>> IWebJobDbContext<int>.JobStatus =>
new GenericDbSet<IJobStatus<int>>(this, this.JobStatus);
}
}
Implementation Examples
Creating a Scheduled Job
Example implementation of a scheduled background job:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ticomix.Common.Helpers;
using Ticomix.WebJobs;
namespace YourProject.Jobs
{
[JobSettings(Category = "Maintenance", DisplayName = "Cleanup Old Records")]
public class CleanupJob : CommonJobBase<int>
{
public CleanupJob(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
public override async Task<JobResult> RunJob(CancellationToken cancellationToken)
{
try
{
Logger.LogMessageBatch(LogEntryErrorLevel.Information, $"{JobName} Started.");
// Your job logic here
var recordsToDelete = await db.SomeTable
.Where(x => x.CreatedDate < DateTime.Now.AddDays(-30))
.CountAsync(cancellationToken);
// Process in batches to avoid timeout
int batchSize = 1000;
int deletedCount = 0;
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
var batch = await db.SomeTable
.Where(x => x.CreatedDate < DateTime.Now.AddDays(-30))
.Take(batchSize)
.ToListAsync(cancellationToken);
if (batch.Count == 0)
break;
db.SomeTable.RemoveRange(batch);
await db.SaveChangesAsync(cancellationToken);
deletedCount += batch.Count;
}
Logger.LogMessageBatch(LogEntryErrorLevel.Information,
$"{JobName} Finished. Deleted {deletedCount} records.");
return new JobResult()
{
Success = true,
Message = $"Successfully deleted {deletedCount} old records",
Batch = Logger.Batch
};
}
catch (Exception ex)
{
Logger.LogErrorBatch(ex);
return new JobResult()
{
Success = false,
Message = ex.Message,
Batch = Logger.Batch,
Exception = ex
};
}
}
}
}
Creating a Background Task with Progress
Example implementation of a background task with progress reporting:
using System;
using System.Threading;
using System.Threading.Tasks;
using Ticomix.WebJobs;
namespace YourProject.BackgroundTasks
{
public class DataImportTask : IBackgroundTask
{
private readonly IServiceProvider serviceProvider;
private readonly string filePath;
public event EventHandler<BackgroundTaskProgressEventArgs> ProgressChanged;
public DataImportTask(IServiceProvider serviceProvider, string filePath)
{
this.serviceProvider = serviceProvider;
this.filePath = filePath;
}
public async Task<object> RunTask(CancellationToken cancellationToken = default)
{
var records = await LoadRecordsFromFile(filePath);
int totalRecords = records.Count;
int processedRecords = 0;
foreach (var record in records)
{
cancellationToken.ThrowIfCancellationRequested();
// Process individual record
await ProcessRecord(record);
processedRecords++;
// Report progress
decimal progress = (decimal)processedRecords / totalRecords;
ProgressChanged?.Invoke(this, new BackgroundTaskProgressEventArgs(
new BackgroundTaskProgress
{
progress = progress,
state = $"Processed {processedRecords} of {totalRecords} records"
}));
// Small delay to prevent overwhelming the system
await Task.Delay(10, cancellationToken);
}
return $"Successfully imported {processedRecords} records from {filePath}";
}
private async Task<List<object>> LoadRecordsFromFile(string path)
{
// Your file loading logic here
await Task.Delay(100); // Simulate async operation
return new List<object>(); // Return actual records
}
private async Task ProcessRecord(object record)
{
// Your record processing logic here
await Task.Delay(50); // Simulate processing time
}
}
}
Controller Implementation for Background Tasks
Example controller for managing background tasks:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Ticomix.WebJobs;
using YourProject.BackgroundTasks;
namespace YourProject.Controllers
{
[Authorize]
public class BackgroundTaskController : Controller
{
private readonly IBackgroundTaskHelper backgroundTaskHelper;
public BackgroundTaskController(IBackgroundTaskHelper backgroundTaskHelper)
{
this.backgroundTaskHelper = backgroundTaskHelper;
}
[HttpPost]
public TaskState StartImport(string filePath)
{
var result = backgroundTaskHelper.RunTaskInBackground((sp) =>
{
return new DataImportTask(sp, filePath);
});
return result;
}
[HttpGet]
public TaskState GetTaskStatus(string taskId)
{
var result = backgroundTaskHelper.GetTask(taskId, false);
// Auto-remove completed tasks
if (result?.TaskInfo.type == "BackgroundTaskFinished" ||
result?.TaskInfo.type == "BackgroundTaskError")
{
backgroundTaskHelper.RemoveTask(taskId);
}
return result;
}
}
}
Job Status Controller (MVC Applications)
Example MVC controller for managing scheduled jobs with Kendo UI integration.
Note: This example is specifically for MVC applications. For Angular applications, use the @ticomix/job-status NPM package which provides complete Angular components and services.
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Kendo.Mvc;
using Kendo.Mvc.Extensions;
using Kendo.Mvc.UI;
using Ticomix.WebJobs;
using YourProject.Data;
using YourProject.ViewModels;
namespace YourProject.Controllers
{
[Authorize]
public class JobStatusController : Controller
{
private readonly ApplicationDbContext db;
private readonly IServiceScopeFactory serviceScopeFactory;
public JobStatusController(IHttpContextAccessor accessor, ApplicationDbContext context,
IServiceScopeFactory serviceScopeFactory)
{
db = context;
db.SetUserName(accessor.HttpContext);
this.serviceScopeFactory = serviceScopeFactory;
}
public ActionResult Index()
{
return View();
}
public async Task<JsonResult> JobStatus_Read([DataSourceRequest] DataSourceRequest request)
{
// Ensure all discovered jobs exist in database
var jobs = JobManager<int>.GetJobs();
var dbJobStatus = await db.JobStatus.ToListAsync();
foreach (var job in jobs)
{
if (!dbJobStatus.Any(a => a.JobName == job.Name))
{
var model = new JobStatus()
{
JobName = job.Name
};
db.JobStatus.Add(model);
await db.SaveChangesAsync();
}
}
// Build job status view model with runtime information
var data = await (from a in db.JobStatus
join r in db.JobRun on a.LastJobRunID equals r.IDJobRun into _r
from r in _r.DefaultIfEmpty()
join s in db.SysJobStatus on r.JobStatusSys equals s.SysValue into _s
from s in _s.DefaultIfEmpty()
select new JobStatusViewModel
{
Active = a.Active,
IDJobStatus = a.IDJobStatus,
JobName = a.JobName,
LastErrorDate = a.LastErrorDate,
LastRun = r.StartDateTime,
LastStatus = s.Description,
StatusMessage = r.StatusMessage,
Interval = a.Interval,
Frequency = a.Frequency,
NextScheduledRun = a.NextScheduledRun
}).ToListAsync();
// Enhance with runtime status and job attributes
foreach (var d in data)
{
d.Hidden = !jobs.Any(a => a.Name == d.JobName);
d.Running = await JobManager<int>.IsRunning(d.JobName, db);
if (d.LastStatus == "Running" && !d.Running)
{
d.LastStatus = "Error";
d.StatusMessage = "Web application stopped by system";
}
else if (d.Running)
{
d.LastStatus = "Running";
}
var type = jobs.Where(a => a.Name == d.JobName).FirstOrDefault();
if (type != null)
{
var attr = JobManager<int>.GetJobAttribute(type);
if (attr != null)
{
d.DisplayName = attr.DisplayName ?? d.JobName;
d.Category = attr.Category;
}
}
}
DataSourceResult result = data.ToDataSourceResult(request);
return Json(result);
}
public async Task<ActionResult> RunJob(int IDJobStatus)
{
var jobStatus = await db.JobStatus.FindAsync(IDJobStatus);
bool isRunning = await JobManager<int>.IsRunning(jobStatus.JobName, db);
if (isRunning)
{
return Json(new JobResult()
{
Success = false,
Message = "Job already running"
});
}
var guid = Guid.NewGuid();
var result = JobManager<int>.RunJobBackground(serviceScopeFactory, jobStatus.JobName);
return Json(new JobResult()
{
Success = true,
Message = "Job started",
guid = guid
});
}
[HttpPost]
public ActionResult<StopJobResult> StopJob(string JobName)
{
var result = JobManager<int>.StopJob(JobName);
return result;
}
}
}
Creating a Custom JobBase Class
Create a base class for your specific application jobs:
using System;
using YourProject.Data;
using Ticomix.WebJobs;
namespace YourProject.Helpers
{
public abstract class JobBase : CommonJobBase<int>
{
public JobBase(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
public new ApplicationDbContext db
{
get { return (ApplicationDbContext)base.db; }
}
}
}
TypeScript Integration (Frontend)
Example TypeScript helper for working with background tasks:
namespace BackgroundTask {
export async function RunBackgroundTask(settings: JQueryAjaxSettings): Promise<TaskStatus> {
let result = await $.ajaxAsync<TaskStatus>(settings);
return await HandleResult(result);
}
async function GetStatus(taskId: string): Promise<TaskStatus> {
return $.ajaxAsync<TaskStatus>({
url: "/BackgroundTask/GetTaskStatus?taskId=" + taskId,
type: "GET"
});
}
async function HandleResult(result: TaskStatus): Promise<TaskStatus> {
if (result.TaskInfo.type === "BackgroundTaskFinished" ||
result.TaskInfo.type === "BackgroundTaskError") {
return result;
} else {
await new Promise(resolve => setTimeout(resolve, 1000));
let updatedResult = await GetStatus(result.TaskInfo.taskId);
return await HandleResult(updatedResult);
}
}
interface TaskStatus {
TaskInfo: {
taskId: string;
type: string;
message?: string;
progress?: number;
result?: any;
};
}
}
Configuration Options
JobHostedServiceOptions
Configure the job runner interval:
services.Configure<JobHostedServiceOptions>(options =>
{
options.RunJobsSeconds = 30; // Check for scheduled jobs every 30 seconds
});
Job Scheduling
Jobs are automatically discovered and can be configured in the database:
- Active: Enable/disable the job
- Interval: "MINUTES", "HOURS", "DAYS", "WEEKS", "MONTHS"
- Frequency: Number of intervals (e.g., 5 for every 5 minutes)
- StartDateTime: When the job should start running
- ActiveEnvironments: Comma-separated list of environments (e.g., "Development,Production")
- Daily time restrictions: Configure specific time windows for job execution
Dependencies
Core Dependencies
- Quartz (3.13.0) - Job scheduling framework
- Quartz.Extensions.Hosting (3.13.0) - ASP.NET Core integration
- DistributedLock.SqlServer (1.0.5) - Distributed locking for job coordination
- Microsoft.Extensions.Hosting.Abstractions (8.0.0) - Hosted service support
Ticomix Dependencies
- Ticomix.EFCore (via project reference)
- Ticomix.Attachments.Common (via project reference)
- Ticomix.EmailNote (via project reference)
Angular UI Components
For Angular applications requiring job status monitoring and management capabilities, install the companion NPM package:
NPM Package: @ticomix/job-status
This package provides:
- Job status monitoring components
- Real-time progress tracking via SignalR
- Job management interfaces
- Background task progress indicators
Version Compatibility
- .NET 6.0+
- .NET 8.0+
- Entity Framework Core 6.0+
- SQL Server 2016+
- Angular 12+ (for UI components)
No packages depend on Ticomix.WebJobs.
.NET 8.0
- Ticomix.Attachments.Common (>= 2025.12.11.1)
- Ticomix.EFCore (>= 2025.12.11.1)
- Ticomix.EmailNote (>= 2025.12.11.1)
- DistributedLock.SqlServer (>= 1.0.5)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.0)
- Quartz (>= 3.13.0)
- Quartz.Extensions.Hosting (>= 3.13.0)
| Version | Downloads | Last updated |
|---|---|---|
| 2025.12.11.1 | 0 | 12/11/2025 |
| 2025.12.10.2 | 0 | 12/10/2025 |
| 2025.12.10.1 | 0 | 12/10/2025 |
| 2025.12.9.3 | 0 | 12/9/2025 |
| 2025.12.9.2 | 1 | 12/9/2025 |
| 2025.12.9.1 | 0 | 12/9/2025 |
| 2025.12.8.2 | 1 | 12/8/2025 |
| 2025.12.7.1 | 1 | 12/7/2025 |
| 2025.12.5.2 | 1 | 12/5/2025 |
| 2025.12.5.1 | 1 | 12/5/2025 |
| 2025.12.3.2 | 1 | 12/3/2025 |
| 2025.12.3.1 | 1 | 12/3/2025 |
| 2025.11.25.1 | 1 | 11/25/2025 |
| 2025.11.24.1 | 12 | 11/24/2025 |
| 2025.11.22.1 | 8 | 11/22/2025 |
| 2025.11.21.2 | 3 | 11/22/2025 |
| 2025.11.21.1 | 2 | 11/21/2025 |
| 2025.11.20.4 | 3 | 11/20/2025 |
| 2025.11.20.3 | 2 | 11/20/2025 |
| 2025.11.20.2 | 2 | 11/20/2025 |
| 2025.11.20.1 | 2 | 11/20/2025 |
| 2025.11.18.2 | 4 | 11/18/2025 |
| 2025.11.18.1 | 4 | 11/18/2025 |
| 2025.11.4.1 | 3 | 11/4/2025 |
| 2025.10.31.1 | 13 | 10/31/2025 |
| 2025.10.29.1 | 24 | 10/29/2025 |
| 2025.10.22.1 | 21 | 10/22/2025 |
| 2025.10.15.1 | 71 | 10/15/2025 |
| 2025.10.9.1 | 14 | 10/10/2025 |
| 2025.10.3.2 | 20 | 10/3/2025 |
| 2025.10.3.1 | 3 | 10/3/2025 |
| 2025.10.1.4 | 24 | 10/1/2025 |
| 2025.10.1.3 | 3 | 10/1/2025 |
| 2025.10.1.2 | 3 | 10/1/2025 |
| 2025.9.24.1 | 33 | 9/24/2025 |
| 2025.9.2.3 | 75 | 9/2/2025 |
| 2025.8.21.1 | 5 | 8/21/2025 |
| 2025.8.19.3 | 41 | 8/19/2025 |
| 2025.8.19.2 | 5 | 8/19/2025 |
| 2025.7.25.1 | 162 | 7/25/2025 |
| 2025.7.21.3 | 109 | 7/21/2025 |
| 2025.7.16.1 | 40 | 7/16/2025 |
| 2025.7.15.1 | 21 | 7/15/2025 |
| 2025.7.11.2 | 26 | 7/11/2025 |
| 2025.6.25.3 | 26 | 6/25/2025 |
| 2025.6.24.1 | 10 | 6/24/2025 |
| 2025.6.23.1 | 8 | 6/23/2025 |
| 2025.6.20.2 | 97 | 6/20/2025 |
| 2025.6.18.1 | 27 | 6/18/2025 |
| 2025.6.16.5 | 12 | 6/16/2025 |
| 2025.6.5.2 | 52 | 6/5/2025 |
| 2025.6.4.1 | 83 | 6/4/2025 |
| 2025.6.2.1 | 19 | 6/2/2025 |
| 2025.5.22.1 | 115 | 5/22/2025 |
| 2025.5.1.1 | 75 | 5/1/2025 |
| 2025.4.18.12 | 39 | 4/18/2025 |
| 2025.4.15.1 | 95 | 4/15/2025 |
| 2025.4.10.1 | 37 | 4/10/2025 |
| 2025.4.8.4 | 11 | 4/8/2025 |
| 2025.4.4.10 | 11 | 4/4/2025 |
| 2025.4.4.7 | 29 | 4/4/2025 |
| 2025.3.28.1 | 34 | 3/28/2025 |
| 2025.3.25.3 | 78 | 3/25/2025 |
| 2025.3.25.2 | 23 | 3/25/2025 |
| 2025.3.20.1 | 26 | 3/20/2025 |
| 2025.3.19.5 | 20 | 3/19/2025 |
| 2025.3.19.3 | 13 | 3/19/2025 |
| 2025.3.18.5 | 17 | 3/18/2025 |
| 2025.3.18.4 | 14 | 3/18/2025 |
| 2025.3.18.3 | 10 | 3/18/2025 |
| 2025.3.18.2 | 10 | 3/18/2025 |
| 2025.3.12.1 | 39 | 3/12/2025 |
| 2025.3.7.1 | 24 | 3/7/2025 |
| 2025.3.4.1 | 25 | 3/4/2025 |
| 2025.2.17.2 | 42 | 2/17/2025 |
| 2025.2.17.1 | 12 | 2/17/2025 |
| 2025.2.14.2 | 62 | 2/14/2025 |
| 2025.2.7.1 | 27 | 2/7/2025 |
| 2025.1.30.1 | 11 | 1/30/2025 |
| 2025.1.29.2 | 11 | 1/29/2025 |
| 2025.1.29.1 | 10 | 1/29/2025 |
| 2025.1.28.2 | 9 | 1/28/2025 |
| 2025.1.28.1 | 47 | 1/28/2025 |
| 2025.1.27.4 | 14 | 1/27/2025 |
| 2025.1.27.3 | 20 | 1/27/2025 |
| 2025.1.27.2 | 14 | 1/27/2025 |
| 2025.1.27.1 | 10 | 1/27/2025 |
| 2025.1.6.1 | 11 | 1/6/2025 |
| 2024.12.31.2 | 45 | 12/31/2024 |
| 2024.12.31.1 | 12 | 12/31/2024 |
| 2024.12.30.1 | 15 | 12/30/2024 |
| 2024.12.20.2 | 26 | 12/20/2024 |
| 2024.12.17.18 | 28 | 12/17/2024 |
| 2024.12.17.2 | 15 | 12/17/2024 |
| 2024.12.11.3 | 30 | 12/11/2024 |
| 2024.12.10.1 | 18 | 12/10/2024 |
| 2024.12.5.3 | 20 | 12/5/2024 |
| 2024.12.5.2 | 13 | 12/5/2024 |
| 2024.12.4.10 | 11 | 12/4/2024 |
| 2024.12.4.9 | 14 | 12/4/2024 |
| 2024.11.15.4 | 48 | 11/16/2024 |
| 2024.11.15.1 | 14 | 11/15/2024 |
| 2024.11.6.3 | 34 | 11/6/2024 |
| 2024.11.6.1 | 14 | 11/6/2024 |
| 2024.11.5.6 | 16 | 11/5/2024 |
| 2024.11.5.4 | 17 | 11/5/2024 |
| 2024.10.28.3 | 30 | 10/28/2024 |
| 2024.10.28.2 | 14 | 10/28/2024 |
| 2024.10.28.1 | 15 | 10/28/2024 |
| 2024.10.24.1 | 15 | 10/24/2024 |
| 2024.10.23.1 | 13 | 10/23/2024 |
| 2024.10.17.2 | 21 | 10/17/2024 |
| 2024.10.8.1 | 30 | 10/8/2024 |
| 2024.8.20.1 | 78 | 8/20/2024 |
| 2024.8.6.1 | 48 | 8/6/2024 |
| 2024.7.17.1 | 57 | 7/17/2024 |
| 2024.7.15.1 | 20 | 7/15/2024 |
| 2024.7.9.4 | 31 | 7/9/2024 |
| 2024.7.9.3 | 20 | 7/9/2024 |
| 2024.7.9.2 | 24 | 7/9/2024 |
| 2024.7.2.1 | 26 | 7/2/2024 |
| 2024.7.1.4 | 21 | 7/1/2024 |
| 2024.6.14.1 | 25 | 6/14/2024 |
| 2024.6.12.2 | 39 | 6/12/2024 |
| 2024.6.10.1 | 20 | 6/10/2024 |
| 2024.5.31.2 | 32 | 5/31/2024 |
| 2024.5.22.2 | 34 | 5/22/2024 |
| 2024.5.21.3 | 16 | 5/21/2024 |
| 2024.5.21.1 | 15 | 5/21/2024 |
| 2024.4.25.1 | 44 | 4/25/2024 |
| 2024.4.19.3 | 244 | 4/19/2024 |
| 2024.4.17.1 | 35 | 4/17/2024 |
| 2024.4.12.3 | 23 | 4/12/2024 |
| 2024.4.9.1 | 32 | 4/9/2024 |
| 2024.4.5.3 | 27 | 4/5/2024 |
| 2024.4.4.2 | 28 | 4/4/2024 |
| 2024.4.3.5 | 17 | 4/3/2024 |
| 2024.4.2.1 | 18 | 4/2/2024 |
| 2024.3.27.2 | 19 | 3/27/2024 |
| 2024.3.19.4 | 28 | 3/19/2024 |
| 2024.3.18.2 | 15 | 3/18/2024 |
| 2024.3.13.5 | 28 | 3/13/2024 |
| 2024.3.13.2 | 31 | 3/13/2024 |
| 2024.3.8.2 | 35 | 3/8/2024 |
| 2024.3.8.1 | 21 | 3/8/2024 |
| 2024.3.6.1 | 28 | 3/6/2024 |
| 2024.2.27.1 | 45 | 2/27/2024 |
| 2024.2.19.1 | 31 | 2/19/2024 |