CONTENTSTART
EXCLUDESTART EXCLUDEEND

Create your own Global Events

Part of what makes any CMS worth its salt is the ability to extend its functionality.  Kentico has done this through Global Event hooks, which allow you to intercept and modify the behavior of the internal workings at certain key points.  All the Info objects inherit this and have an Insert/Update/Delete Before/After event hooks, and on top of that Kentico offers many other custom hook points.

All of these use a couple base Kentico classes that handle the creation and delegation of these events, and since these classes aren’t hidden away, that means we can leverage them as well to make our custom modules easily extensible.

If you are creating a marketplace submission, or even an internal module that you plan on using on multiple sites, global events are probably something you’re going to benefit from.

Flexibility vs. Complexity

The more flexible a tool is, the more places it can be used.  However, this usually comes at a big cost, and that cost is complexity.  Trying to imagine all the different use cases, the different scenarios, and coding in logic to handle all of those can be a nightmare, and ultimately you can never truly account for EVERY scenario.

However, by adding in some custom event hooks, you can allow individuals who use this tool to modify it just how they need it, in their own unique situations.  This is the beauty of the event hook system. 

Setting up the Event

Creating a custom event is quite simple:
  1. Create your CMSEventArgs class (event properties)
  2. Create your EventHandler
  3. Create your Event Class
Let’s look at the code for each, I’ll be using the DynamicRoutingEvents.GetPage as an example.

Create your CMSEventArgs class

The first step is to decide what you’ll pass to the Events.  This can be any class you wish but should contain any data the user may need to perform relative actions.  This class must inherit from CMSEventArgs for Kentico to allow it to be used. 

namespace DynamicRouting
{
    public class GetPageEventArgs : CMSEventArgs
    {
        /// <summary>
        /// The Page that is found, this is what will be returned from the GetPage function, set this.
        /// </summary>
        public ITreeNode FoundPage { get; set; }

        /// <summary>
        /// The Request's Relative Url (no query strings), cleaned to be proper lookup format
        /// </summary>
        public string RelativeUrl { get; set; }

        /// <summary>
        /// The Request's Culture
        /// </summary>
        public string Culture { get; set; }

        /// <summary>
        /// The Site's default culture
        /// </summary>
        public string DefaultCulture { get; set; }

        /// <summary>
        /// The current SiteName
        /// </summary>
        public string SiteName { get; set; }

        /// <summary>
        /// If Kentico's Preview mode is active (Preview/Edit)
        /// </summary>
        public bool PreviewEnabled { get; set; }

        /// <summary>
        /// The User's requested Columns to return with the page data
        /// </summary>
        public string ColumnsVal { get; set; }

        /// <summary>
        /// The full HttpRequest object
        /// </summary>
        public HttpRequest Request { get; set; }

        /// <summary>
        /// If an exception occurred between the Before and After (while looking up), this is the exception. Can be used for custom logging.
        /// </summary>
        public Exception ExceptionOnLookup { get; set; }
    }
}

Create your EventHandler

The next step is to create your Event Handler class.  This needs to inherit the AdvancedHandler<YourEventHandler, YourEventArgs> class.  The only thing worth mentioning on this is since the base Finish method is hidden, you need to add your own Finish Event to call this, which triggers the After event.

namespace DynamicRouting
{
    public class GetPageEventHandler : AdvancedHandler<GetPageEventHandler, GetPageEventArgs>
    {
        public GetPageEventHandler()
        {

        }

        public GetPageEventHandler StartEvent(GetPageEventArgs PageArgs)
        {
            return base.StartEvent(PageArgs);
        }

        public void FinishEvent()
        {
            base.Finish();
        }
    }
}

Create your Event Class

The Last step is to create a static class that will expose your handler, making it easy for the user to add their own hooks to it.  Just follow the below example’s format.

namespace DynamicRouting
{

    public static class DynamicRoutingEvents
    {
        /// <summary>
        /// Overwrites the handling of finding a page based on the request.
        /// </summary>
        public static GetPageEventHandler GetPage;

        /// <summary>
        /// Allows overwrite of how to get the current culture
        /// </summary>
        public static GetCultureEventHandler GetCulture;

        /// <summary>
        /// Allows you to adjust the MVC Routing by modifying the Request Context
        /// </summary>
        public static RequestRoutingEventHandler RequestRouting;

        static DynamicRoutingEvents()
        {
            GetPage = new GetPageEventHandler()
            {
                Name = "DynamicRoutingEvents.GetPage"
            };

            GetCulture = new GetCultureEventHandler()
            {
                Name = "DynamicRoutingEvents.GetCulture"
            };

            RequestRouting = new RequestRoutingEventHandler()
            {
                Name = "DynamicRoutingEvents.RequestRouting"
            };
        }
    }
}

Leveraging the Event in our Code

Now that we created our event, let’s go through using it in our code, using this outline:
  1. Create your Event Argument
  2. Initialize the Event (Before Event triggered)
  3. Perform your normal logic (possibly considering if the user used the Before trigger)
  4. Call the Finished method (After Event triggered)
  5. Finish your logic/method.
We’ll summarize and then go into detail on each step.  We will be using the example of my DynamicRouteHelper.GetPage() method, which leverages the DynamicRoutingEvents.GetPage event.

1: Create your Event Argument

The first step is to create and populate your EventArgs class.  This will be passed to the Before event, and will give the user the information they need, and possibly also properties they may need to modify in order to customize.

// Create GetPageEventArgs Event ARgs
            GetPageEventArgs Args = new GetPageEventArgs()
            {
                RelativeUrl = Url,
                Culture = Culture,
                DefaultCulture = DefaultCulture,
                SiteName = SiteName,
                PreviewEnabled = PreviewEnabled,
                ColumnsVal = ColumnsVal,
                Request = HttpContext.Current.Request
            };

2: Initialize the Event

Next is to Initialize the event itself by creating a new instance of your EventHandler (using a using block is probably preferable in this case).  The act of creating a new instance automatically will trigger the Before event, which allows the users to customize.

// Run any GetPage Event hooks which allow the users to set the Found Page
ITreeNode FoundPage = null;
using (var DynamicRoutingGetPageTaskHandler = DynamicRoutingEvents.GetPage.StartEvent(Args))
{
...
}

3: Perform your normal logic

At this point, now you must decide what to do.  You should account for anything the user may have done in their hook and adjust accordingly.  For the GetPage() method, I do a check to see if the TreeNode is null or not, if it is not null then the user must have found the page already in their implementation, so I don’t need to do my default logic of trying to find the page myself.  But if it is null, then I need to perform my normal logic to try to get the current page based on the UrlSlugs.  How you leverage your arguments will be up to you of course, this is just an example:

// Run any GetPage Event hooks which allow the users to set the Found Page
ITreeNode FoundPage = null;
using (var DynamicRoutingGetPageTaskHandler = DynamicRoutingEvents.GetPage.StartEvent(Args))
{
	if (Args.FoundPage == null)
	{
		try
		{
			Args.FoundPage = CacheHelper.Cache<TreeNode>(cs =>
			{
				// Whole bunch of logic here

				// Return Page Data
				return PageQuery.FirstOrDefault();
			   
			}, new CacheSettings((PreviewEnabled ? 0 : 1440), "DynamicRoutine.GetPage", Url, Culture, DefaultCulture, SiteName, PreviewEnabled, ColumnsVal));
		}
		catch (Exception ex)
		{
			// Add exception so they can handle
			DynamicRoutingGetPageTaskHandler.EventArguments.ExceptionOnLookup = ex;
		}
	}
	...
}

4: Call the After Event

Last step is to call the EventHandler’s Finished event, which will trigger the After event hook.  This should allow users another change to modify the results/behavior.  The current EventArgs are passed again to the After event hooks. 

// Run any GetPage Event hooks which allow the users to set the Found Page
ITreeNode FoundPage = null;
using (var DynamicRoutingGetPageTaskHandler = DynamicRoutingEvents.GetPage.StartEvent(Args))
{
	if (Args.FoundPage == null)
	{
		//Previous Logic
	}
	// Finish event, this will trigger the After
        DynamicRoutingGetPageTaskHandler.FinishEvent();
}

5: Finish

Now that everything is complete, you can finish your action.  In my case, I’m returning the TreeNode found (either by my default logic, or the user’s intervention).

Leveraging the Global Event Hooks

Now that your event hooks I created and you have added it to your logic, others can hook into it.  Here’s just a sample of what it would look like for someone to modify the GetPage logic, in this case if the found page is the CMS.Root page (user hits just the domain), to replace it with the home page.

[assembly: RegisterModule(typeof(Generic_LoaderModule))]
 
namespace Generic
{
    public class Generic_LoaderModule : Module
    {
        // Module class constructor, the system registers the module under the name "CustomInit"
        public Generic_LoaderModule()
            : base("Generic_LoaderModule_MVC")
        {
        }
 
        // Contains initialization code that is executed when the application starts
        protected override void OnInit()
        {
            base.OnInit();
            DynamicRouting.DynamicRoutingEvents.GetPage.After += GetPage_After;
        }
 
        private void GetPage_After(object sender, DynamicRouting.GetPageEventArgs e)
        {
            if(e.FoundPage != null && e.FoundPage.ClassName.Equals("CMS.root", StringComparison.InvariantCultureIgnoreCase))
            {
                e.FoundPage = DocumentHelper.GetDocuments("Custom.HomePage").FirstOrDefault();
            }
        }
}

Conclusion

I hope this little guide will be helpful to all those who are working hard to help expand Kentico further.  Know I’ve implemented this in both the DynamicRouting and the Kentico Authorization modules, and plan on using this further in future tools as well.
 
Comments
Blog post currently doesn't have any comments.
Is one = seven ? (true/false)
CONTENTEND