Inter Plugin Event Notification

Description

The plugin manager contains an interface which enables inter plugin notifications. Individual plugins can register one or multiple classes which can be used to obtain notifications throughout the application lifecycle. This can be useful when you need to receive updates or notify listeners that a specific event has occurred.

Notifications are completely user defined, the event id used is a unique string.

Being able to send and receive notifications can enables different plugins to communicate in a generic manner, whilst allowing for custom data to also be communicated. This could be useful in many circumstances, for instance: A plugin module caches certain information for speed of retrieval, another plugin could update this static information however, without any notifications the cache would end up with stale data. The plugin module which caches the data can register an instance of INotificationListener which listens for an event called “CacheUpdated”, when the data is updated the updating plugin module obtains an instance of INotificationService and calls the RaiseEvent method. The listening plugin module receives the notification and empties the cache.

The following sample code is taken from SieraDeltaGeoIp plugin module and demonstrates registering an INotificationListener as part of initialisation.

public Initialisation(INotificationService notificationService)
{
    GeoIpStatistics = new GeoIpStatistics();
    notificationService.RegisterListener(GeoIpStatistics);
}

The GeoIpStatistics class used above contains two fields which hold the amount of time taken to load the geo ip data and the number of records loaded. This is used in the SystemAdmin plugin module to provide statistics.

internal class GeoIpStatistics : INotificationListener
{
    #region Private Members
 
    private uint _recordsLoaded;
    private TimeSpan _loadTime;
 
    #endregion Private Members
 
    #region INotificationListener Methods
 
    public bool EventRaised(in string eventIdin object param1in object param2ref object result)
    {
        switch (eventId)
        {
            case Constants.NotificationEventGeoIpLoadTime:
                result = _loadTime;
                return true;
 
            case Constants.NotificationEventGeoIpRecordCount:
                result = _recordsLoaded;
                return true;
        }
 
        return false;
    }
 
    public void EventRaised(in string eventIdin object param1in object param2)
    {
        switch (eventId)
        {
            case Constants.NotificationEventGeoIpLoadTime:
                _loadTime = (TimeSpan)param1;
                return;
 
            case Constants.NotificationEventGeoIpRecordCount:
                _recordsLoaded = (uint)param1;
                return;
        }
    }
 
    public List<stringGetEvents()
    {
        return new List<string>
        {
            Constants.NotificationEventGeoIpLoadTime,
            Constants.NotificationEventGeoIpRecordCount,
        };
    }
 
    #endregion INotificationListener Methods
}

Quite simply this class registers two events (both defined as constants), one is for the record count and one is for the load time. There are two EventRaised methods, the first is used to return a value, the second is used to receive a value.

A consumer who wanted to retrieve the load time and number of records loaded would obtain an instance of INotificationService from the DI container and raise an event notification like:

object result = null;
 
if (_notificationService.RaiseEvent(Constants.NotificationEventGeoIpLoadTime, nullnullref result))
    loadTime = (TimeSpan)result;
 
if (_notificationService.RaiseEvent(Constants.NotificationEventGeoIpRecordCount, nullnullref result))
    recordsLoaded = (uint)result;

The service who would set the values for load time and number of records would look like:

//send results
_notificationService.RaiseEvent(Constants.NotificationEventGeoIpLoadTime, Convert.ToInt64(span.TotalMilliseconds));
_notificationService.RaiseEvent(Constants.NotificationEventGeoIpRecordCount, (uint)_tempIpCity.Count);

Threaded Notifications

The default implementation of INotificationService works in two ways, events that do not require an immediate response are placed into a list and processed by a seperate thread, this means that INotificationListener implementations need to ensure they are thread safe. Using a seperate thread improves application performance as the request pipeline is not blocked why events are raised.

Events that require an immediate response do not use a seperate thread and therefore block the pipeline request whilst the event is processed. Careful consideration should be given to blocking events to ensure they are suitably optimised.

This simple notification service and listener allows plugin modules to easily talk to each other, pass messages and respond to events as they happen. There are no limits to the number of listeners that can be within an application.