Skip to content

Scheduled Tasks

Create background tasks that run on a schedule in your nopCommerce plugins.

Overview

Scheduled tasks run in the background at configurable intervals. Use them for:

  • Syncing data with external systems
  • Sending batch emails
  • Cleaning up old records
  • Generating reports
  • Any periodic maintenance

Creating a Scheduled Task

Step 1: Implement IScheduleTask

csharp
// Tasks/MySyncTask.cs
using Nop.Services.ScheduleTasks;

namespace Nop.Plugin.Misc.MyPlugin.Tasks;

public class MySyncTask : IScheduleTask
{
    #region Fields

    private readonly IMyService _myService;
    private readonly ILogger<MySyncTask> _logger;

    #endregion

    #region Ctor

    public MySyncTask(
        IMyService myService,
        ILogger<MySyncTask> logger)
    {
        _myService = myService;
        _logger = logger;
    }

    #endregion

    #region Methods

    public async Task ExecuteAsync()
    {
        _logger.LogInformation("MySyncTask started at {Time}", DateTime.UtcNow);

        try
        {
            var recordsProcessed = await _myService.SyncDataAsync();
            _logger.LogInformation("MySyncTask completed. Processed {Count} records", recordsProcessed);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "MySyncTask failed");
            throw;
        }
    }

    #endregion
}

Step 2: Register the Task on Install

csharp
// MyPlugin.cs
public override async Task InstallAsync()
{
    // Register the scheduled task
    var scheduleTaskService = EngineContext.Current.Resolve<IScheduleTaskService>();
    
    var task = new ScheduleTask
    {
        Name = "My Plugin - Data Sync",
        Seconds = 3600, // Run every hour
        Type = typeof(MySyncTask).FullName,
        Enabled = true,
        StopOnError = false
    };
    
    await scheduleTaskService.InsertTaskAsync(task);
    
    await base.InstallAsync();
}

public override async Task UninstallAsync()
{
    // Remove the scheduled task
    var scheduleTaskService = EngineContext.Current.Resolve<IScheduleTaskService>();
    var task = await scheduleTaskService.GetTaskByTypeAsync(typeof(MySyncTask).FullName);
    
    if (task != null)
        await scheduleTaskService.DeleteTaskAsync(task);
    
    await base.UninstallAsync();
}

Task Configuration

Schedule Properties

PropertyDescription
NameDisplay name in admin
SecondsInterval between runs
TypeFull type name of task class
EnabledWhether task is active
StopOnErrorStop future runs on failure
LastStartUtcLast execution start time
LastEndUtcLast execution end time
LastSuccessUtcLast successful completion

Common Intervals

csharp
// Every 5 minutes
Seconds = 300

// Every hour
Seconds = 3600

// Every 6 hours
Seconds = 21600

// Once daily
Seconds = 86400

Advanced Patterns

Task with Progress Tracking

csharp
public class BatchProcessTask : IScheduleTask
{
    #region Fields

    private readonly IRepository<PendingItem> _repository;
    private readonly ILogger<BatchProcessTask> _logger;

    #endregion

    #region Ctor

    public BatchProcessTask(
        IRepository<PendingItem> repository,
        ILogger<BatchProcessTask> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    #endregion

    #region Methods

    public async Task ExecuteAsync()
    {
        var items = await _repository.GetAllAsync(q => 
            q.Where(i => !i.Processed).Take(100));

        var processed = 0;
        var failed = 0;

        foreach (var item in items)
        {
            try
            {
                await ProcessItemAsync(item);
                item.Processed = true;
                item.ProcessedOnUtc = DateTime.UtcNow;
                await _repository.UpdateAsync(item);
                processed++;
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Failed to process item {ItemId}", item.Id);
                failed++;
            }
        }

        _logger.LogInformation(
            "Batch completed: {Processed} processed, {Failed} failed", 
            processed, failed);
    }

    #endregion

    #region Utilities

    private async Task ProcessItemAsync(PendingItem item)
    {
        // Process logic here
    }

    #endregion
}

Task with External API

csharp
public class ExternalSyncTask : IScheduleTask
{
    #region Fields

    private readonly IHttpClientFactory _httpClientFactory;
    private readonly ISettingService _settingService;
    private readonly IRepository<SyncRecord> _repository;

    #endregion

    #region Ctor

    public ExternalSyncTask(
        IHttpClientFactory httpClientFactory,
        ISettingService settingService,
        IRepository<SyncRecord> repository)
    {
        _httpClientFactory = httpClientFactory;
        _settingService = settingService;
        _repository = repository;
    }

    #endregion

    #region Methods

    public async Task ExecuteAsync()
    {
        var settings = await _settingService.LoadSettingAsync<MyPluginSettings>();
        
        if (string.IsNullOrEmpty(settings.ApiKey))
            throw new InvalidOperationException("API key not configured");

        var client = _httpClientFactory.CreateClient();
        client.DefaultRequestHeaders.Add("X-Api-Key", settings.ApiKey);

        var response = await client.GetAsync(settings.ApiEndpoint);
        response.EnsureSuccessStatusCode();

        var data = await response.Content.ReadFromJsonAsync<List<ExternalItem>>();
        
        foreach (var item in data)
        {
            await SyncItemAsync(item);
        }
    }

    #endregion

    #region Utilities

    private async Task SyncItemAsync(ExternalItem item)
    {
        var existing = await _repository.GetAllAsync(q => 
            q.Where(r => r.ExternalId == item.Id).FirstOrDefault());

        if (existing == null)
        {
            await _repository.InsertAsync(new SyncRecord
            {
                ExternalId = item.Id,
                Data = item.Data,
                SyncedOnUtc = DateTime.UtcNow
            });
        }
        else
        {
            existing.Data = item.Data;
            existing.SyncedOnUtc = DateTime.UtcNow;
            await _repository.UpdateAsync(existing);
        }
    }

    #endregion
}

Configurable Task Interval

csharp
// Allow admin to configure interval
public class MyPluginSettings : ISettings
{
    public string ApiKey { get; set; }
    public int SyncIntervalMinutes { get; set; } = 60;
}

// Update on settings change
public async Task<IActionResult> Configure(ConfigurationModel model)
{
    var settings = await _settingService.LoadSettingAsync<MyPluginSettings>();
    settings.SyncIntervalMinutes = model.SyncIntervalMinutes;
    await _settingService.SaveSettingAsync(settings);

    // Update task interval
    var task = await _scheduleTaskService.GetTaskByTypeAsync(typeof(MySyncTask).FullName);
    if (task != null)
    {
        task.Seconds = model.SyncIntervalMinutes * 60;
        await _scheduleTaskService.UpdateTaskAsync(task);
    }

    return await Configure();
}

Manual Execution

Run a task manually from admin or via API:

csharp
// In a controller action
public async Task<IActionResult> RunSyncNow()
{
    var task = EngineContext.Current.Resolve<MySyncTask>();
    await task.ExecuteAsync();
    
    _notificationService.SuccessNotification("Sync completed successfully");
    return RedirectToAction("Configure");
}

Best Practices

  1. Keep tasks focused - One task, one responsibility
  2. Process in batches - Don't load all records at once
  3. Log progress - Track what's being processed
  4. Handle failures gracefully - Don't crash the entire task
  5. Use transactions carefully - Commit after each logical unit
  6. Respect timeouts - Tasks shouldn't run indefinitely
  7. Test thoroughly - Simulate long-running scenarios

Monitoring Tasks

View task status in Admin > System > Schedule Tasks:

  • Last run time and duration
  • Success/failure status
  • Enable/disable toggle
  • Manual run button

Next Steps

Released under the nopCommerce Public License.