ContentService Notifications

    The ContentService class is the most commonly used type when extending Umbraco using notifications. ContentService implements IContentService. It provides access to operations involving IContent.

    Usage

    Example usage of the ContentPublishingNotification:

    using Umbraco.Cms.Core.Events;
    using Umbraco.Cms.Core.Notifications;
    
    namespace MySite
    {
        public class DontShout : INotificationHandler<ContentPublishingNotification>
        {
            public void Handle(ContentPublishingNotification notification)
            {
                foreach (var node in notification.PublishedEntities)
                {
                    if (node.ContentType.Alias.Equals("announcement"))
                    {
                        var newsArticleTitle = node.GetValue<string>("title");
                        if (newsArticleTitle.Equals(newsArticleTitle.ToUpper()))
                        {
                            notification.CancelOperation(new EventMessage("Corporate style guideline infringement",
                                "Don't put the announcement title in upper case, no need to shout!",
                                EventMessageType.Error));
                        }
                    }
                }
            }
        }
    }
    

    Notifications

    Notification Members Description
    ContentSavingNotification
    • IEnumerable<IContent> SavedEntities
    • EventMessages Messages
    • IDictionary<string,object> State
    • bool Cancel
    Published when the IContentService.Save is called in the API.
    NOTE: It can be skipped completely if the parameter "raiseEvents" is set to false during the Save method call (true by default).
    SavedEntities: The collection of IContent objects being saved.
    NOTE: If the entity is brand new then HasIdentity will equal false.
    ContentSavedNotification
    • IEnumerable<IContent> SavedEntities
    • EventMessages Messages
    • IDictionary<string,object> State
    Published when IContentService.Save is called in the API and after data has been persisted.
    NOTE: It can be skipped completely if the parameter "raiseEvents" is set to false during the Save method call (true by default).
    NOTE: See here on how to determine if the entity is brand new
    SavedEntities: The saved collection of IContent objects.
    ContentPublishingNotification
    • IEnumerable<IContent> PublishedEntities
    • EventMessages Messages
    • IDictionary<string,object> State
    • bool Cancel
    Published when IContentService.Publishing is called in the API.
    NOTE: It can be skipped completely if the parameter "raiseEvents" is set to false during the Publish method call (true by default).
    NOTE: If the entity is brand new then HasIdentity will equal false.
    PublishedEntities: The collection of IContent objects being published.
    ContentPublishedNotification
    • IEnumerable<IContent> PublishedEntities
    • EventMessages Messages
    • IDictionary<string,object> State
    Published when IContentService.Publish is called in the API and after data has been published.
    NOTE: It can be skipped completely if the parameter "raiseEvents" is set to false during the Publish method call (true by default).
    NOTE: See here on how to determine if the entity is brand new
    PublishedEntities: The published collection of IContent objects.
    ContentUnpublishingNotification
    • IEnumerable<IContent> UnpublishedEntities
    • EventMessages Messages
    • IDictionary<string,object> State
    • bool Cancel
    Published when IContentService.UnPublishing is called in the API.
    UnpublishedEntities: The collection of IContent being unpublished.
    ContentUnpublishedNotification
    • IEnumerable<IContent> UnpublishedEntities
    • EventMessages Messages
    • IDictionary<string,object> State
    Published when IContentService.UnPublish is called in the API and after data has been unpublished.
    UnpublishedEntities: The collection of unpublished IContent.
    ContentCopyingNotification
    • EventMessages Messages
    • IDictionary<string,object> State
    • bool Cancel
    • IContent Original
    • IContent Copy
    • int ParentId
    Published when IContentService.Copy is called in the API.
    The notification is published after a copy object has been created and had its parentId updated and its state has been set to unpublished.
    1. Original: Gets the original IContent object.
    2. Copy: Gets the IContent object being copied.
    3. ParentId: Gets the Id of the parent of the IContent being copied.
    ContentCopiedNotification
    • EventMessages Messages
    • IDictionary<string,object> State
    • IContent Original
    • IContent Copy
    • int ParentId
    • bool RelateToOriginal
    Published when IContentService.Copy is called in the API.
    The notification is published after the content object has been copied.
    1. Original: Gets the original IContent object.
    2. Copy: Gets the IContent object being copied.
    3. ParentId: Gets the Id of the parent of the IContent being copied.
    4. RelateToOriginal: Boolean indicating whether the copy was related to the original
    ContentMovingNotification
    • IEnumerable<MoveEventInfo<IContent>> MoveInfoCollection
    • EventMessages Messages
    • IDictionary<string,object> State
    • bool Cancel
    Published when IContentService.Move is called in the API.
    NOTE: If the target parent is the Recycle bin, this notification is never published. Try the ContentMovingToRecycleBinNotification instead.
    MoveInfoCollection will for each moving entity provide:
    1. Entity: Gets the IContent object being moved
    2. OriginalPath: The original path the entity is moved from
    3. NewParentId: Gets the Id of the parent the entity will have after it has been moved
    ContentMovedNotification
    • IEnumerable<MoveEventInfo<IContent>> MoveInfoCollection
    • EventMessages Messages
    • IDictionary<string,object> State
    Published when IContentService.Move is called in the API. The notification is published after the content object has been moved.
    NOTE: If the target parent is the Recycle bin, this notification is never published. Try the ContentMovedToRecycleBinNotification instead.
    MoveInfoCollection will for each moving entity provide:
    1. Entity: Gets the IContent object being moved
    2. OriginalPath: The original path the entity is moved from
    3. NewParentId: Gets the Id of the parent the entity will have after it has been moved
    ContentMovingToRecycleBinNotification
    • IEnumerable<MoveEventInfo<IContent>> MoveInfoCollection
    • EventMessages Messages
    • IDictionary<string,object> State
    • bool Cancel
    Published when ContentService.MoveToRecycleBin is called in the API.
    MoveInfoCollection will for each moving entity provide:
    1. Entity: Gets the IContent object being moved
    2. OriginalPath: The original path the entity is moved from
    3. NewParentId: Gets the Id of the RecycleBin
    ContentMovedToRecycleBinNotification
    • IEnumerable<MoveEventInfo<IContent>> MoveInfoCollection
    • EventMessages Messages
    • IDictionary<string,object> State
    Published when ContentService.MoveToRecycleBin is called in the API. Is published after the content has been moved to the RecycleBin
    MoveInfoCollection will for each moving entity provide:
    1. Entity: Gets the IContent object being moved
    2. OriginalPath: The original path the entity is moved from
    3. NewParentId: Gets the Id of the RecycleBin
    ContentDeletingNotification
    • IEnumerable<IContent> DeletedEntities
    • EventMessages Messages
    • IDictionary<string,object> State
    • bool Cancel
    Published when ContentService.DeleteContentOfType, ContentService.Delete, ContentService.EmptyRecycleBin are called in the API.
    DeletedEntities: Gets the collection of IContent objects being deleted.
    ContentDeletedNotification
    • IEnumerable<IContent> DeletedEntities
    • EventMessages Messages
    • IDictionary<string,object> State
    • bool Cancel
    Published when ContentService.Delete, ContentService.EmptyRecycleBin are called in the API, and the entity has been deleted.
    DeletedEntities: Gets the collection of deleted IContent objects.
    ContentDeletingVersionsNotification
    • EventMessages Messages
    • IDictionary<string,object> State
    • bool Cancel
    • int Id
    • int SpecificVersion
    • DateTime DateToRetain
    • bool DeletePriorVersions
    Published when ContentService.DeleteVersion, ContentService.DeleteVersions are called in the API.
    1. Id: Gets the id of the IContent object being deleted.
    2. DateToRetain: Gets the latest version date.
    3. SpecificVersion: Gets the id of the IContent object version being deleted.
    4. DeletePriorVersions: False by default
    ContentDeletedVersionsNotification
    • EventMessages Messages
    • IDictionary<string,object> State
    • int Id
    • int SpecificVersion
    • DateTime DateToRetain
    • bool DeletePriorVersions
    Published when ContentService.DeleteVersion, ContentService.DeleteVersions are called in the API, and the version has been deleted.
    1. Id: Gets the id of the IContent object being deleted.
    2. DateToRetain: Gets the latest version date.
    3. SpecificVersion: Gets the id of the IContent object version being deleted.
    4. DeletePriorVersions: False by default
    ContentRollingBackNotification
    • IContent Entity
    • EventMessages Messages
    • IDictionary<string,object> State
    • bool Cancel
    Published when ContentService.Rollback is called in the API.
    Entity: Gets the IContent object being rolled back.
    ContentRolledBackNotification
    • IContent Entity
    • EventMessages Messages
    • IDictionary<string,object> State
    Published when ContentService.Rollback is called in the API, after the content has been rolled back.
    Entity: Gets the IContent object being rolled back.
    ContentSendingToPublishNotification
    • IContent Entity
    • EventMessages Messages
    • IDictionary<string,object> State
    • bool Cancel
    Published when ContentService.SendToPublication is called in the API.
    Entity: Gets the IContent object being sent to publish.
    ContentSentToPublishNotification
    • IContent Entity
    • EventMessages Messages
    • IDictionary<string,object> State
    Published when ContentService.SendToPublication is called in the API, after the entity has been sent to publication.
    Entity: Gets the IContent object being sent to publish.
    ContentEmptyingRecycleBinNotification
    • IEnumerable<IContent> DeletedEntities
    • EventMessages Messages
    • IDictionary<string,object> State
    • bool Cancel
    Published when ContentService.EmptyRecycleBin is called in the API.
    DeletedEntities: The collection of IContent objects being deleted.
    ContentEmptiedRecycleBinNotification
    • IEnumerable<IContent> DeletedEntities
    • EventMessages Messages
    • IDictionary<string,object> State
    Published when ContentService.EmptyRecycleBin is called in the API, after the RecycleBin has been emptied.
    DeletedEntities: The collection of deleted IContent object.
    ContentSavedBlueprintNotification
    • IContent SavedBlueprint
    • EventMessages Messages
    • IDictionary<string,object> State
    Published when ContentService.SavedBlueprint is called in the API.
    SavedBlueprint: Gets the saved blueprint IContent object
    ContentDeletedBlueprintNotification
    • IEnumerable<IContent> DeletedBlueprints
    • EventMessages Messages
    • IDictionary<string,object> State
    Published when ContentService.DeletedBlueprint is called in the API.
    DeletedBlueprints: The collection of deleted blueprint IContent

    Variants and Notifications

    Umbraco V8 introduced the concept of Variants for Document Types, initially to allow different language variants of particular properties within a Document Type to be edited/translated based on the languages configured in your instance of Umbraco.

    These variants can be saved, published and unpublished independently of each other. (Unpublishing a 'mandatory language' variant of a content item - will trigger all culture variants to be unpublished).

    This poses a problem when handling notifications from the ContentService - eg which culture got published? Do I want to run my 'custom' code that fires on save if it's only the Spanish version that's been published? Also, if only the Spanish variant is 'unpublished' - that feels like a different situation to if 'all the variants' have been 'unpublished'. Depending on which event you are handling there are helper methods you can call to find out.

    Saving

    When handling the ContentSavingNotification which will be published whenever a variant is saved. You can tell 'which' variant has triggered the save using an extension method on the ContentSavingNotification called 'IsSavingCulture'

    public bool IsSavingCulture(IContent content, string culture);
    

    As an example, you could check which cultures are being saved (it could be multiple if multiple checkboxes are checked)

    public void Handle(ContentSavingNotification notification)
    {
        foreach (var entity in notification.SavedEntities)
        {
            // Cultures being saved
            var savingCultures = entity.AvailableCultures
                .Where(culture => notification.IsSavingCulture(entity, culture)).ToList();
            // or
            if (notification.IsSavingCulture(entity, "en-GB"))
            {
                // Do things differently if the UK version of the page is being saved.
            }
        }
    }
    

    Saved

    With the Saved notification you can similarly use the 'HasSavedCulture' method of the 'ContentSavedNotification' to detect which culture caused the Save.

    public bool HasSavedCulture(IContent content, string culture);
    

    Unpublishing

    When handling the Unpublishing notification, it might not work how you would expect. If 'all the variants' are being unpublished at the same time (or the mandatory language is being unpublished, which forces this to occur) then the Unpublishing notification will be published as expected.

    public void Handle(ContentUnpublishingNotification  notification)
    {
    	foreach (var unPublishedEntity  in notification.UnpublishedEntities)
    	{
    		// complete unpublishing of entity, all cultures
    	}
    }
    

    However, if only one variant is being unpublished, the Unpublishing event will not be triggered. This is because the content item itself is not fully 'unpublished' by the action. Instead what occurs is a 'publish' action 'without' the variant that has been unpublished.

    You can therefore detect the Unpublishing of a variant in the publishing notification - using the IsUnpublishingCulture extension method of the ContentPublishingNotification

    public void Handle(ContentPublishingNotification notification)
    {
        foreach (var node in notification.PublishedEntities)
        {
            if (notification.IsUnpublishingCulture(node, "da-DK"))
            {
                // Bye bye DK!
            }
        }
    }
    

    Unpublished

    Again the Unpublished notification does not get published when a single variant is Unpublished, instead, the Published notification can be used, and the 'HasUnpublishedCulture' extension method of the ContentPublishedNotification can determine which variant being unpublished triggered the publish.

    public bool HasUnpublishedCulture(IContent content, string culture);
    

    Publishing

    When handling the ContentPublishingNotification which will be triggered whenever a variant is published (or unpublished - see note in the Unpublishing section above).

    You can tell 'which' variant has triggered the publish using a helper method on the ContentPublishingNotification called IsPublishingCulture.

    public bool IsPublishingCulture(IContent content, string culture);
    

    For example, you could check which cultures are being published and act accordingly (it could be multiple if multiple checkboxes are checked).

    public void Handle(ContentPublishingNotification notification)
    {
        foreach (var node in notification.PublishedEntities)
        {
            var publishingCultures = node.AvailableCultures
                .Where(culture => notification.IsPublishingCulture(node, culture)).ToList();
            
            var unPublishingCultures = node.AvailableCultures
                .Where(culture => notification.IsUnpublishingCulture(node, culture)).ToList();
            // or
            if (notification.IsPublishingCulture(node, "da-DK"))
            {
                // Welcome back DK!
            }
        }
    }
    

    Published

    In the Published notification you can similarly use the HasPublishedCulture and HasUnpublishedCulture methods of the 'ContentPublishedEventArgs' to detect which culture caused the Publish or the UnPublish if it was only a single non-mandatory variant that was unpublished.

    public bool HasPublishedCulture(IContent content, string culture);
    public bool HasUnpublishedCulture(ICotnent content, string culture);
    

    IContent Helpers

    In each of these notifications, the entities being Saved, Published, and Unpublished are IContent entities. There are some useful helper methods on IContent to discover the status of the content item's variant cultures:

    bool IsCultureAvailable(string culture);
    bool IsCultureEdited(string culture);
    bool IsCulturePublished(string culture);
    

    What happened to Creating and Created events?

    Both the ContentService.Creating and ContentService.Created events were removed, and therefore never moved to notifications. Why? Because these events were not guaranteed to trigger and therefore should not be used. This is because these events would only trigger when the ContentService.CreateContent method was used which is an entirely optional way to create content entities. It is also possible to construct a new content item - which is generally the preferred and consistent way - and therefore the Creating/Created events would not execute when constructing content that way.

    Furthermore, there was no reason to listen for the Creating/Created events. They were misleading since they didn't trigger before and after the entity was persisted. They triggered inside the CreateContent method which never persists the entity, it constructs a new content object.

    What do we use instead?

    The ContentSavingNotification and ContentSavedNotification will always be published before and after an entity has been persisted. You can determine if an entity is brand new in either of those notifications. In the Saving notification - before the entity is persisted - you can check the entity's HasIdentity property which will be 'false' if it is brand new. In the Saved notification you can check to see if the entity 'remembers being dirty'