Creating an Outdated Content Notification Task in Kentico
Having expired content on your site is the quickest way to kill traffic and interest. If users don’t see regular updates to your content, they’ll lose interest and likely not return. Ensuring their experience is fresh and relevant is the key to keeping your visitors interested and coming back. In this article, I’ll show you how to create a Scheduled Task in Kentico to notify your content owners when their pages are getting stale to ensure you provide the most engaging experience possible.
You can implement every SEO trick in the book, but if you’re content is old, you’re going to lose visitors. People frequent sites they love for a reason: to get new information. If your site isn’t providing that experience, you can be sure that your user base is going to find their information somewhere else. Keeping your content fresh and updated is a critical step in this engagement process.
Almost every bit of content in Kentico has a date for when it was last updated. This allows you to quickly identify the last time a page or form was modified by your editors. Using the Outdated pages module, you can view content across your site and see exactly what needs to be updated.
Note: There is also the Page Reports / Expired pages report in the Reporting module, which you can subscribe users to.
While these options are great for some scenarios, I wanted to make the process a little more automated and personalized for my editors.
A Scheduled Task could surely help things, right?
Creating the task
The first step of the process was to create a new custom task for my functionality. I created a new class and added some basic code for reporting. I used the TaskInfo object to pass values into my custom code. I used these values to determine how the task functioned and what the results would be. In my case, I used the following values in the TaskInfo parameters:
- (int) Number of days to check
- (bool) Flag for determining if the task should send owner notifications
- (bool) Flag for determining if detailed reporting should be in the task result
public class OutdatedContentNotifierTask : ITask
{
/// <summary>
/// Executes the task.
/// </summary>
/// <param name="ti">Info object representing the scheduled task</param>
public string Execute(TaskInfo ti)
{
StringBuilder sb = new StringBuilder();
try
{
// Get the task parameters
string[] taskparams = ti.TaskData.Split(',');
int intExpirationDays = ValidationHelper.GetInteger(taskparams[0], 0);
bool blnSendNotification = ValidationHelper.GetBoolean(taskparams[1], false);
bool blnDetail = ValidationHelper.GetBoolean(taskparams[2], false);
}
catch (Exception ex)
{
sb.Append("ERROR: " + ex.Message);
}
return sb.ToString();
}
}
You find out more about creating custom tasks here.
Retrieving the outdated content
The next step was to add my task functionality. I wanted to accomplish the following:
- Get the list of outdated documents based on a specific number of days
- Get a unique list of owners
- Send an email to each owner with their list of outdated content
To accomplish these goals, I planned to use the DocumentQuery functionality. This API would return the documents I search for, along with the node information. Because I wanted to send an email to the owners, I knew I would have to get the user information, as well. To achieve this, I created a new “coupled” class for my documents and owners called OutdatedDocument.
public class OutdatedDocument
{
public string DocumentName { get; set; }
public string NodeAliasPath { get; set; }
public DateTime DocumentModifiedWhen { get; set; }
public int OwnerID { get; set; }
public string OwnerUsername { get; set; }
public string OwnerEmail { get; set; }
public string OwnerFullname { get; set; }
}
I created a new List<OutdatedDocument> object to hold my content.
List<OutdatedDocument> lstDocs = new List<OutdatedDocument>();
I then used the DocumentHelper.GetDocuments to retrieve my outdated content. For each document, I created a new OutdatedDocument object and retrieved to the UserInfo information for the NodeOwner. I then added the new object to my list. In retrieving the documents, I specified only the columns I would need.
// Get all the documents older than the specified number of days
var outdateddocs = DocumentHelper.GetDocuments()
.Columns("DocumentName", "DocumentModifiedWhen", "NodeOwner", "NodeAliasPath")
.WhereLessThan("DocumentModifiedWhen", DateTime.Now.AddDays(-intExpirationDays))
.OnCurrentSite();
// Loop through the documents
foreach (var outdateddoc in outdateddocs)
{
// Create a new object for each docuemnt, combining the document and owner data
OutdatedDocument doc = new OutdatedDocument();
doc.DocumentName = outdateddoc.DocumentName;
doc.DocumentModifiedWhen = outdateddoc.DocumentModifiedWhen;
doc.NodeAliasPath = outdateddoc.NodeAliasPath;
doc.OwnerID = outdateddoc.NodeOwner;
// Get the owner info
UserInfo uiOwner = UserInfoProvider.GetUserInfo(ValidationHelper.GetInteger(outdateddoc.NodeOwner, 0));
if (uiOwner != null)
{
doc.OwnerUsername = uiOwner.UserName;
doc.OwnerFullname = uiOwner.FullName;
doc.OwnerEmail= uiOwner.Email;
}
// Add the new object to the list
lstDocs.Add(doc);
}
I added the Total Outdated Content Items count to my task response.
// Set the total outdated content
sb.Append("Total Outdated Content Items: " + outdateddocs.Count);
Creating a report for each owner
With my retrieved outdated content, I wanted to create a report for each owner. This would allow me to notify them individually with a list of all the documents owned by them that were older than the specified number of days.
First, I grouped my list of OutdatedDocument objects by NodeOwner.
// Get the list of unique owners from the collection
var owners = lstDocs.GroupBy(t => t.OwnerUsername)
.Select(grp => grp.First())
.ToList();
Next, I looped through each owner and filtered the list of objects for the owner. I then sorted the list by the DocumentModifiedWhen date. I also leveraged my blnDetail TaskInfo value to add the owner counts to the task response.
// Loop through each owner to generate a list of ther documents
foreach (var owner in owners)
{
// Get all of docuemnts for the owner
var docs = lstDocs.FindAll(n => n.OwnerID == owner.OwnerID);
// Reorder list by oldest first
docs.Sort((x, y) => x.DocumentModifiedWhen.CompareTo(y.DocumentModifiedWhen));
if (blnDetail)
{
// Add each owner's username/count to the task results
sb.Append(" | ");
if (owner.OwnerUsername != null)
{
sb.Append(owner.OwnerUsername);
}
else
{
sb.AppendLine("UNASSIGNED");
}
sb.Append(" (" + docs.Count + ") | ");
}
...
}
Next, I looped through my filtered and sorted objects to build up the list for the owner.
// Build the list of outdated content for the owner
sbOwner.Clear();
foreach (var doc in docs)
{
sbOwner.Append(doc.DocumentModifiedWhen.ToString() + " - " + doc.DocumentName + "<br />" + doc.NodeAliasPath);
sbOwner.Append("<br /><br />");
}
Send the owner a report
The last step of the process was to send an email to the user with a list of their outdated content. To do this, I wanted to leverage an email template. For my demo, this was a basic design with a few variables passed in.
With this template in place, I added the following code to my task to pass the values to the MacroResolver and send the email.
// Determine if notifications should be sent
if ((blnSendNotification) && (owner.OwnerUsername != null))
{
// Send the report to the owner
EmailTemplateInfo eti = EmailTemplateProvider.GetEmailTemplate("OutdatedContentTemplate", SiteContext.CurrentSiteName);
// Add the custom variables to the MacroResolver
MacroResolver mcr = MacroResolver.GetInstance();
mcr.SetNamedSourceData("NAME", owner.OwnerFullname);
mcr.SetNamedSourceData("NUMBER_OF_DAYS", intExpirationDays);
mcr.SetNamedSourceData("OUTDATED_CONTENT_COUNT", docs.Count);
mcr.SetNamedSourceData("OUTDATED_CONTENT_LIST", sbOwner.ToString());
// Create message object
EmailMessage message = new EmailMessage();
// Get sender from settings
message.EmailFormat = EmailFormatEnum.Both;
message.From = eti.TemplateFrom;
// Do not send the e-mail if there is no sender specified
if (message.From != "")
{
// Initialize message
message.Recipients = owner.OwnerEmail;
message.Subject = eti.TemplateSubject;
// Send email via Email engine API
EmailSender.SendEmailWithTemplateText(SiteContext.CurrentSiteName, message, eti, mcr, true);
}
}
Testing
To test the functionality, I registered the custom task and set the TaskInfo parameters. For the values, I used the following:
- 365 (Number of days to check)
- true (Enables sending notification emails to owners)
- true (Enables detailed reporting in the task results
I ran the task and confirmed I got the correct Last result values.
I then confirmed the email was created and sent to the owner with the list of outdated content.
Moving forward
Leveraging Scheduled Tasks is a great way to provide automation to your application. Using the above task, a schedule could be created to check your content on a weekly or monthly basis and inform owners when their content is expiring. This can help you keep your content fresh and ensure your users are getting the most out of their visits to your site. Good luck!
Get the code
Exported Email Template
Exported Scheduled Task
This blog is intended for informational purposes only and provides an example of one of the many ways to accomplish the described task. Always consult Kentico Documentation for the best practices and additional examples that may be more effective in your specific situation.