NodeObserver

From Sense/Net Wiki
Jump to: navigation, search
  •  
  •  
  •  
  •  
  • 100%
  • 6.0
  • Enterprise
  • Community
  • Planned

Overview

In Sense/Net you can define tasks to be executed when a content is changed (or created or deleted), whose execution is not the responsibility of any content. There are numerous examples for tasks like these in the base system, but it is also desired to be able to define such custom logic as part of any business process. For example if the size of a folder exceeds a certain limit an automatic backup procedure is to be executed. For these sort of tasks we can use node observers. Using node observers you can define functions in an aspect-oriented way that will be executed on content changes. Although node observers are triggered by content changes they are not part of content and are not related in any way.

Details

Node observers inherit from the base NodeObserver abstract class. The system collects all observers not disabled in the web.config on startup and instantiates a single instance from every observer type. When a content event is triggered the system initiates a call to every single node observer in an unspecified order.


Node observer functions

An observer practically uses stateless mechanisms. Observer functions are very similar to .NET event handlers: a sender and an object inheriting from EventArgs is passed to them upon call. Sender is always the triggering content (object inheriting from Node) except for the OnStart and OnReset methods in which case sender is null. Type and attached data of the EventArgs object depends on the triggered method. Observer function calls are not protected which means that some observers could not get fired in case of an error. Triggered methods are the following:

NodeObserver methods
Method name EventArgs type Description
OnReset EventArgs Type system restarts.
OnStart EventArgs Type system starts.
OnNodeCreating CancellableNodeEventArgs Saving a brand new node.
OnNodeCreated NodeEventArgs A brand new node has been saved.
OnNodeModifying CancellableNodeEventArgs Saving a node.
OnNodeModified NodeEventArgs A node has been saved.
OnNodeDeleting CancellableNodeEventArgs A node is being deleted.
OnNodeDeleted NodeEventArgs A node has been deleted.
OnNodeDeletingPhysically CancellableNodeEventArgs A node is being deleted avoiding the trash
OnNodeDeletedPhysically NodeEventArgs A node has been deleted avoiding the trash
OnNodeMoving CancellableNodeOperationEventArgs A node is being moved (called on the subtree root)
OnNodeMoved NodeOperationEventArgs A node has been moved (called on the subtree root)
OnNodeCopying CancellableNodeOperationEventArgs A node is being copied (called on every item)
OnNodeCopied NodeOperationEventArgs A node has been copied (called on every item)
OnPermissionChanging CancellableNodeEventArgs Node permissions are being changed.
OnPermissionChanged NodeEventArgs Node permissions have changed.

Attached data of the EventArgs parameters are the following:

  • NodeEventArgs (parameter of the OnNodeCreated, OnNodeDeleted, OnNodeDeletedPhysically, OnPermissionChanged events)
    • SourceNode: the node that triggered this call.
    • User: the current user.
    • Time: original calling time (every observer is called in the same time).
    • EventType: NodeEvent enumeration value.
    • OriginalSourcePath: original path if the node was renamed or moved.
    • ChangedData: provides all changed value in a collection. Every item contains the name of the field, original and new value.
    • CustomData: this is a state object that you provided (optionally) in the ing suffixed counterpart event (e.g. OnNodeCreating, see example below).
  • NodeOperationEventArgs (parameter of the OnNodeMoved, OnNodeCopied events)
    • All properties of NodeEventArgs.
    • TargetNode: node that is the target of the in a copying or moving operation.
  • CancellableNodeEventArgs (appears in the OnNodeCreating, OnNodeModifying, OnNodeDeleting, OnNodeDeletingPhysically, OnPermissionChanging events)
    • Cancel: if you want to interrupt the operation, set value to true.
    • SourceNode: the node that triggered this call.
    • User: the current user.
    • Time: original calling time (every observer is called in the same time).
    • EventType: NodeEvent enumeration value.
    • ChangedData: provides all changed value in a collection. Every item contains the name of the field, original and new value.
    • CustomData: you can set a state object that you will get back in the ed suffixed counterpart event (e.g. OnNodeCreated, see example below).
  • CancellableNodeOperationEventArgs (parameter of the OnNodeMoving, OnNodeCopying events)
    • All properties of CancellableNodeEventArgs.
    • TargetNode: node that is the target of the in a copying or moving operation.

Cancellable methods

The EventArgs object of cancellable methods (with method name ending to -ing) contain a Cancel property. Setting this to true will cancel the current operation.

Custom data - from version 6.3

Most events are grouped to pairs. The pairs have two events for executing custom code before (with -ing suffix) and after (with -ed suffix) the matched operation. The "before" events are always cancellable and can receive a custom data. With the custom data you can collect information that is available before the operation and you can use it in the counterpart event. Important to know: because the "before" event is cancellable, it is not guaranteed that the second event will be executed. Therefore the right way of the executing custom additional task is gathering information and placeing it into the CustomData property in the "before" event and executing your custom code in the "after" event using CustomData.

Let's see the workflow of the delete operation. It has the following steps:

  • checks the conditions
  • fires the OnDeleting event
  • checks the cancellation
  • stores the custom state object
  • does the actual deletion
  • fires the OnDeleted event and passes the custom data

For example the customer expects the size of the deleted files in a log entry. One of the possible solution is the following: create a NodeObserver and override the OnNodeDeletingPhysically and OnDeleted methods. In OnNodeDeletedPhysically event you can get the file sizes from the still existing structure and store the summary in the CustomData property. If the OnDeleting event is called you can get the summary and you can write it into the log. Let's see this:

public class DeletedSizeLogger : NodeObserver
{
    protected override void OnNodeDeletingPhysically(object sender, CancellableNodeEventArgs e)
    {
        base.OnNodeDeletingPhysically(sender, e);
        var sizeOfFiles = Content.All
            .DisableAutofilters()               // include system files and folders
            .OfType<File>()                     // type filtering
            .Where(c => c.InTree(e.SourceNode)) // only in this subtree
            .AsEnumerable()                     // executing the query
            .Sum(f=>f.Binary.Size);             // summarizing
        e.CustomData = sizeOfFiles;             // storing the summary
    }
    protected override void OnNodeDeletedPhysically(object sender, NodeEventArgs e)
    {
        base.OnNodeDeletedPhysically(sender, e);
        SenseNet.Diagnostics.Logger.WriteInformation(e.CustomData + " bytes deleted");
    }
}

Disabling observers

You can switch off node observers in the web.config with the following key:

<add key="DisabledNodeObservers" value="type1;type2" />

You need to enlist the observers to be disabled separated by ';'. Types should be namespace qualified class names, for example:

<add key="DisabledNodeObservers" value="SenseNet.ContentRepository.Storage.AppModel.AppCacheInvalidator;SenseNet.ContentRepository.Storage.AppModel.RepositoryEventRouter" />

Performance considerations

The more observers are defined in the system the more synchronous function calls will be executed for every triggered event. For this reason it is advisable to minimise the number of observers and that their code be implemented optimally. Unnecessary observers should always be switched off in the web.config.

Summary

You can use the node observer technique for implementing tasks that need to be executed when an arbitrary content is changed. Since in the Sense/Net Content Repository every stored data is a content, this technique can be used in a wide range.

Example/Tutorials

The following code snippet shows an example observer for backing up containers whose size exceed a given limit. The size of a container may grow if a content is created, modified, moved or copied in it. Deleting a content cannot trigger size growth. The container node is identified by path which is retrieved from a configuration object. Size calculation and backup creation is not strictly the task of the observer, for this reason we omitted those codes.

    public class FolderSizeObserver : NodeObserver
    {
        private string _observedPath;
        private long _limit;
 
        public FolderSizeObserver()
        {
            // Pinning  and preparing configured variables for performance considerations
            _limit = Configuration.FolderSizeObserver_LimitTrigger;
 
            // The observed path must ends with the "/".
            _observedPath = Configuration.FolderSizeObserver_ObservedPath;
            if (!_observedPath.EndsWith(RepositoryPath.PathSeparator))
                _observedPath += RepositoryPath.PathSeparator;
        }
 
        // Override only the relevant methods
        protected override void OnNodeCreated(object sender, NodeEventArgs e)
        {
            base.OnNodeCreated(sender, e);
            // Check the path of source node
            BackupIfNeeded(e.SourceNode.Path);
        }
        protected override void OnNodeModified(object sender, NodeEventArgs e)
        {
            base.OnNodeModified(sender, e);
            // Check the path of source node
            BackupIfNeeded(e.SourceNode.Path);
        }
        protected override void OnNodeCopied(object sender, NodeOperationEventArgs e)
        {
            base.OnNodeCopied(sender, e);
            // Check the path of target subtree. Source tree is not changed
            BackupIfNeeded(e.TargetNode.Path);
        }
        protected override void OnNodeMoved(object sender, NodeOperationEventArgs e)
        {
            base.OnNodeMoved(sender, e);
            // Check the path of source and target subtree.
            BackupIfNeeded(e.SourceNode.Path);
            BackupIfNeeded(e.TargetNode.Path);
        }
 
        // This is the worker method
        private void BackupIfNeeded(string path)
        {
            // Do not work if the given path is out of the observed path
            if (!path.StartsWith(_observedPath, StringComparison.OrdinalIgnoreCase))
                return;
            // Do backup if the size reaches the limit
            var size = Tools.GetFolderSize(path);
            if (size >= _limit)
                BackupManager.Backup(_observedPath);
        }
    }

Related links

References

There are no related external articles for this article.