Global Event System

A key element of the extensibility of Lynicon is the global event system.  Essentially any code can tell the EventHub to fire an event for which any other code can register a handler.  Data is passed with the event which can be modified by the handlers. Technically, this is the Interceptor design pattern.

There is a variation with the Lynicon event hub in that the order in which the handlers fire is neither fully determined nor completely undefined.  Instead, a handler can be registered with ordering constraints specifying its position in the processing sequence relative to handlers (or groups of handlers) that it is aware of.  This allows modules from different authors with limited knowledge of each other to coordinate their processing chain without the builder of the client application having to work out and specify how to sequence them all.

EventHub class

EventHub is a singleton, the below methods are generally called on EventHub.Instance

EventHubData ProcessEvent(string eventName, object sender, object eventData)

This method is called to fire an event.  The eventName is a namespace-style name. The sender maintains a reference to the object which raised the event.  The eventData is a custom object which may be updated by the event handlers in succession.  The returned EventHubData class simply contains these three parameters as properties, as updated by the handlers.

void RegisterEventProcessor(string eventName, Func<EventHubData, object> processor, string moduleId, OrderConstraint constraint)

This method is called to register an event handler (or processor).  The eventName here can match any raised event according to the namespace-style name rules.  The processor argument is a function which takes an EventHubData object and returns the (potentially) modified version of its Data property.  The moduleId argument should be the name of the module from which this processor is called, however it can be an arbitrary string if needed, or if the processor is called by non-module code. The constraint argument represents any ordering rules you wish to supply for when this processor is called in the chain of processors handling this event.  Ordering rules use the moduleId of other processors to specify that this one should be called before or after them.

Examples

As an example, here is how the event processor of the Auditing module works:

public override bool Initialise()
{
  EventHub.Instance.RegisterEventProcessor("Repository.Set", ProcessSet, "Auditing.Basic", new OrderConstraint("Auditing.Basic", ConstraintType.ItemsAfter, "Caching"));
  return true;
}
 
public object ProcessSet(EventHubData ehd) { ... }

The processor runs on all events in the Repository.Set group which include Repository.Set.Add, Repository.Set.Update and Repository.Set.Delete.  These are raised by the data API any time a record is changed, before the change occurs.  The moduleId is Auditing.Basic which is the name of the module in which this code occurs.  Notice how only the name of the ProcessSet method on the same class is required as the 'processor' argument.

The OrderConstraint object specifies that the handler for this module must come after that for any Caching group of modules.

var eventData = new RepositoryEventData(ci, bypassChecks);
var eventResult = EventHub.Instance.ProcessEvent("Repository.Set.Delete", this, eventData);

This code from the ContentRepository shows where it raises the event when an item is deleted.  The first line builds the data about the deletion from the content item deleted and the bypassChecks flag.  It then runs the processors for the Repository.Set.Delete event, passing in itself as the source and the eventData. Later in the code (not shown) it checks whether the eventData now contains a null contentitem to see whether any of the processors aborted the delete.