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.WebJobs
  • Ticomix.EFCore
  • Ticomix.Common
  • Ticomix.Attachments.Common
  • Ticomix.EmailNote

Database Setup

  1. Add the required database models (JobStatus, JobRun, SysJobStatus) to your project
  2. Include the context file ApplicationDbContextWebJob.cs in your \Data folder
  3. Run database migrations to create the necessary tables

ASP.NET Core Configuration

Startup.cs / Program.cs Changes

  1. Add Required Using Statements:
using Ticomix.WebJobs;
using Ticomix.WebJobs.QuartzJob;
  1. 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>();
  1. Configure Middleware (if using SignalR):
app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<TaskProgressHub>("/taskProgressHub");
    // ... other endpoints
});
  1. 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.

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