Joshua Folkerts
Feb 16, 2019
visibility 6238
star star star star star
(4 votes)

EPiServer Notification For Expiring Pages

A client asked if they could recieve some form of notification that pages they have created are about to expire 7 days before it actually expired.   I asked the client if they wanted an email of the list of the pages that are about to be expired and their response was, we get so many emails a day, that it might get lost in the list.  So the next best thing is for me to use the notification service that will send a notification to the user in the cms in edit mode.  I know episerver uses the the notification service to tell us when their is a new version of tinymce, etc, but i wanted to use it to inform the user that these x number of pages are about to expire.  

With the thought of using the notication servier, I went perusing around the documentation to find some information on how to tie into the notification service.  After reading the documentation, I came up with a scheduled job that will grab all the pages in the site tree and check the stop publish date to see if it is about to expire.  When the page is about to expire, i add it to the notification queue to notifiy the editor that this page is about to expire.

There are many ways and services that allow you to do this exact thing but i wanted to keep it simple and allow the admin to control how often to check the expiriy of the the page.  My goal is to keep is self contained regardless what cloud service or on premise hosting enviroment you chose.  

So to give an overview on what I did to accomplish this, 

  1. Create a scheduled job that will parse the pages for expiring pages
  2. Implement the IUserNotificationRepository to create new notifications to send
  3. Send the notification to the user based on pages expirey

This is the scheduled job i used to send the expiration of the pages to the user.

using Blend.Episerver;
using EPiServer.Core;
using EPiServer.Editor;
using EPiServer.Notification;
using EPiServer.PlugIn;
using EPiServer.ServiceLocation;
using EPiServer.Web;
using EPiServer.Web.Routing;
using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Linq;

namespace EPiServerSamples.ScheduledJobs
{
    [ScheduledPlugIn(DisplayName = "Expiring Pages Notification")]
    public class ExpiringPagesNotification : Blend.Episerver.ScheduledJobs.BlendJobBase
    {
        bool _stopSignaled;

        readonly INotifier notifier;

        readonly IUserNotificationRepository userNotificationService;

        const string ExpiryChannelName = "epi.PageExpiry";

        public ExpiringPagesNotification()
        {
            IsStoppable = true;
            var locator = ServiceLocator.Current;
            notifier = locator.GetInstance<INotifier>();
            this.userNotificationService = locator.GetInstance<IUserNotificationRepository>();
        }

        /// <summary>
        /// Called when a user clicks on Stop for a manually started job, or when ASP.NET shuts down.
        /// </summary>
        public override void Stop()
        {
            _stopSignaled = true;
        }

        /// <summary>
        /// Called when a scheduled job executes
        /// </summary>
        /// <returns>A status message to be stored in the database log and visible from admin mode</returns>
        public override string Execute()
        {
            //Call OnStatusChanged to periodically notify progress of job for manually started jobs
            OnStatusChanged(string.Format("Starting execution of {0}", this.GetType()));

            var pages = this.GetExpiringPages();
            this.SetCounter("Expiring Pages", pages.Count());

            foreach (var page in pages)
            {
                var category = new Uri(UrlResolver.Current.GetUrl(page.ContentLink));
                var notifications = AsyncContext.Run(() => this.userNotificationService.GetUserNotificationsAsync(new UserNotificationsQuery()
                {
                    ChannelName = ExpiryChannelName,
                    Category = category
                }, 0, 20));

                if (!notifications.PagedResult.HasValue())
                {
                    AsyncContext.Run(() => notifier.PostNotificationAsync(new NotificationMessage()
                    {
                        ChannelName = ExpiryChannelName,
                        Content = $"{page.Name}(<a href=\"{ PageEditing.GetEditUrl(page.ContentLink)}\">{page.ContentLink}</a>) is about to Expire on {page.StopPublish.Value.ToShortDateString()} - {page.StopPublish.Value.ToShortTimeString()}",
                        Subject = "Expiration of Page Upcoming",
                        Recipients = new[] { new NotificationUser(page.CreatedBy) },
                        Sender = new NotificationUser("System"),
                        TypeName = "PageExpiring",
                        Category = new Uri(UrlResolver.Current.GetUrl(page.ContentLink))
                    }));
                    this.Increment("Messages Sent");
                }

            //For long running jobs periodically check if stop is signaled and if so stop execution
            if (_stopSignaled)
            {
                return "Stop of job was called";
            }
            }

            return this.CounterReport();
        }

        private IEnumerable<PageData> GetExpiringPages() =>
            this.contentLoader.GetItems(this.contentLoader.GetDescendents(SiteDefinition.Current.StartPage), new LoaderOptions())
            .Where(x => x is IVersionable && ((IVersionable)x).StopPublish.GetValueOrDefault(DateTime.MaxValue) < DateTime.Now.AddDays(8))
            .OfType<PageData>();
    }
}

So to recap on the job here.  

I first get a list of all pages that are expiring in the next 7 days.  The method "GetExpiringPages" just returns a list of pages that are about to be expired and that is the list of pages I look through.  I check to see if the notification has already been sent for this current page.  If the notification hasn't been sent, then i create a new postnotification to the creator of the page and send them a message.   

I hope this helps someone who might need a sample of using epi notifications.

Feb 16, 2019

Comments

Marcus B
Marcus B Feb 17, 2019 09:38 PM

Looks good. There are been a few questions on this recently on the forums. I hope that this helps them

Allan Thraen
Allan Thraen Feb 18, 2019 09:57 AM

Funny, I remember building some like this to help a client shortly after I joined Episerver in 2007 :-) So the demand is not new. Thanks for the write-up, Josh - looks good!

Janaka Fernando
Janaka Fernando Feb 18, 2019 06:56 PM

Good usage of extending the IUserNotificationRepository here!

Another option to complement this could be creating a custom Task called "Expiring Pages" similar to what Linus has demonstrated on this post - https://niteco.com/blogs/plugging-into-the-tasks-list/ 

error Please login to comment.
Latest blogs
Architecting an Enterprise-Grade Development Pipeline in Optimizely SaaS CMS

Most enterprise teams show up to Optimizely SaaS CMS with a clear roadmap for their release pipeline: DEV → QA → Stage → Prod. Four logical...

Vipin Banka | Jul 12, 2026

Bynder DAM Connector for Optimizely SaaS CMS: Improved Metadata Property Synchronization

While working with the Bynder DAM Connector for Optimizely SaaS CMS , one of the key areas I explored was how Bynder asset metadata is synchronized...

Vipin Banka | Jul 11, 2026

Optimizely DXP: Every Supported Culture, One Searchable Page

Quick one for anyone building multi-language sites on Optimizely DXP. I put together a reference tool listing all 806 supported cultures. More...

Adnan Zameer | Jul 10, 2026 |

A day in the life of an Optimizely OMVP: London Meetup 2026

On 2nd July 2026 the Optimizely London Developer Meetup returned to The Lightwell, and the running theme across the evening was less about individu...

Graham Carr | Jul 10, 2026

Optimizely’s Summer ’26 Roadmap: The CMS Is Starting to Look Less Like a Publishing Tool and More Like Marketing Infrastructure

Optimizely’s Summer ’26 Product Roadmap event was not just a list of product updates. At least, that is not the part I found most interesting. The...

Augusto Davalos | Jul 9, 2026

Optimizely Content JS SDK v2.1.0 — What's New and Why It Matters

  v2.1.0 of the Optimizely Content JS SDK and CLI landed on July 7, 2026. This is a substantial release bringing a wave of capabilities for...

Vipin Banka | Jul 8, 2026