Scheduled Tasks

In this tutorial we will add a scheduled task to email a shop summary at the time of execution.

Let's start by creating the following "Migration16.cs" file:

using FluentMigrator;

namespace DemoShop.Shop.Migrations;

[Migration(201908141900)]
public class Migration16 : AutoReversingMigration {
    public override void Up() {
        Insert.IntoTable("EmailTemplates").Row(new { Name = "Scheduled Task: Shop Summary", From = "no-reply@mysite.com", Subject = "Shop Summary", Message = @"<p>There are currently {{ NumProducts }} product(s).</p>", UseDefaultSignature = true, Key = "Shop_Summary", TemplateName = "Standard", IsActive = true });

        Insert.IntoTable("ScheduledTaskTypes").Row(new { Name = "Shop Summary", Key = "Shop_Summary" });
    }
}

Note: It is a good idea to prefix the scheduled task and email template keys with the module name to avoid conflicts.

Next we'll add the emailing and scheduling abstraction packages to our module. Open the project file and add the following:

<PackageReference Include="Kit.Emailing.Abstractions" Version="1.*" />
<PackageReference Include="Kit.Scheduling.Abstractions" Version="1.*" />

Next add a "Tasks" folder in the root of the module. Within that folder add the following file called "ShopSummaryScheduledTask.cs":

using System.Collections.Generic;
using System.Threading.Tasks;
using DemoShop.Shop.Models;
using Kit.Data;
using Kit.Emailing;
using Kit.Scheduling;

namespace DemoShop.Shop.Tasks;

public class ShopSummaryScheduledTask : IScheduledTask {
    private readonly IDataContext _dataContext;
    private readonly IEmailManager _emailManager;

    public ShopSummaryScheduledTask(IDataContext dataContext, IEmailManager emailManager) {
        _dataContext = dataContext;
        _emailManager = emailManager;
    }

    public string Key => "Shop_Summary";

    public virtual async Task ExecuteAsync() {
        // Send the email.
        await _emailManager.SendAsync("Shop_Summary", new Dictionary<string, object?>() {
            { "NumProducts", await _dataContext.Repository<Product>().All().CountAsync() }
        });
    }
}

Note: The key for both the scheduled task and the email template should match the ones specified in the migrations file.

Finally we just have to register the scheduled task within the container. Open the module's “HostingStartup.cs” file and add the following within the “ConfigureServices” method:

services.AddTransient<IScheduledTask, ShopSummaryScheduledTask>();

You can now go ahead and add your scheduled task. You'll need to make sure you add yourself to the distribution list for the email template to receive the email.

Similarly to widgets, often you'll need to customise the scheduled task by some parameters. We'll extend the scheduled task above and add an additional person to email as currently we can only email admins (on the distribution list).

First we'll update the scheduled task type we added above to specify the parameters type. Create the following "Migration17.cs" file:

using System;
using FluentMigrator;

namespace DemoShop.Shop.Migrations;

[Migration(201908141902)]
public class Migration17 : Migration {
    public override void Up() {
        Update.Table("ScheduledTaskTypes").Set(new { ParametersTypeAssembly = "DemoShop.Shop", ParametersTypeClass = "DemoShop.Shop.Models.ShopSummaryParameters", ParametersTypeAction = "ShopSummary", ParametersTypeController = "TaskParameters", ParametersTypeArea = "DemoShop.Shop" }).Where(new { Key = "Shop_Summary" });
    }

    public override void Down() {
        throw new NotImplementedException();
    }
}

Now we'll add the following model called "ShopSummaryParameters.cs" to the "Models" folder:

using System.ComponentModel.DataAnnotations;

namespace DemoShop.Shop.Models;

public class ShopSummaryParameters {
    [Required]
    public string Email { get; set; } = default!;
}

Next we'll add a “TaskParameters” controller to host all of our task parameters. Add the following file called "TaskParametersController.cs" to the "Controllers" folder:

using DemoShop.Shop.Models;
using Kit.Mvc.Filters;
using Kit.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc;

namespace DemoShop.Shop.Controllers;

[Area("DemoShop.Shop"), Route("parameters/tasks"), HtmlFieldPrefix("Parameters")]
public class TaskParametersController : Controller {
    [AjaxOrChildActionOnly, HttpGet("shop-summary")]
    public IActionResult ShopSummary([BindRouteData] ShopSummaryParameters parameters) {
        return PartialView(parameters);
    }
}

Now we need to add a view for the "ShopSummary" action. Create folder called "TaskParameters" within the "Views" folder and then create a file called "ShopSummary.cshtml" within that folder with the following content:

@model DemoShop.Shop.Models.ShopSummaryParameters
<div class="card shadow">
    <div class="card-header"><h2>@Text["Parameters"]</h2></div>
    <div class="card-body">
        <div class="form-group">
            <label asp-for="Email" class="form-label"></label>
            <editor for="Email" />
        </div>
    </div>
</div>

Finally let's update our scheduled task to handle the parameters. Open the "ShopSummaryScheduledTask.cs" file and replace the execute method with the following (adding any missing namespaces):

public virtual async Task ExecuteAsync(object @params) {
    var parameters = (ShopSummaryParameters)@params;

    // Send the email.
    await _emailManager.SendAsync("Shop_Summary", new Dictionary<string, object?>() {
        { "NumProducts", await _dataContext.Repository<Product>().All().CountAsync() }
    }, parameters.Email);
}

Note: We have fed the email address from our parameters when calling the “SendAsync” method against the email manager.

Now you can go ahead and update your scheduled task with the additional email address.

Content Items »