Improving the Save experience in CMS 10
One of the most common task in a CMS is to save, update or publish a content item. And while not exactly complicated, the Content Repository method Save together with the SaveAction enumeration has long been a source of confusion, especially for new developers.
And perhaps that is no wonder considering that you often end with calls such as:
// Why do I need to pass Save to a Save method? contentRepository.Save(content, SaveAction.Save);
It has also been plagued with slightly inconsistent behaviour in regards to when new versions are created and how the version status is changed.
An in addition it has sometimes been hard to know exactly when and which events are raised, often leading to developers attaching the same event handlers to multiple events just to be certain that the handler is executed when a content item is saved.
In a previous release we made a smaller attempt to consolidate the save behaviour when publishing, but this also let to a regression in the usability when someone wanted to update an existing version as it now required a check of the current status before knowing which SaveAction flag to provide to the Save method.
For CMS 10 we made a more thorough review of this behaviour and has tweaked things to work in a way that hopefully should make more sense to everyone. Not everything described in this article are behaviour that has changed in CMS 10 regardless, but I have tried to highlight new or changed functionality when not obvious.
For a full list of breaking changes in CMS 10, please see the release notes for this feature published with the CMS 10 release.
When you want to save a content item it should in many scenarios be sufficient to only pass the content to the Save method.
When no explicit SaveAction is provided to the Save method, which is the equivalent to passing in SaveAction.Default, the content version will maintain its current status. This is unless the Save results in a new version, in which case it will get a checked out status. More on new versions below.
Changing the version status
Whenever you want to publish a version you can simply call the Publish extension method, such as:
If you want to transition a version to a specific status when saving you will need to provide the SaveAction that corresponds to the desired version status. The save actions that control version status are sometimes referred to as the primary save action. Only one primary actions can be provided at once, but they can be combined with one or more option flags.
- CheckOut - Checks out a version to indicate that it is being worked on. (New in CMS 10)
- RequestApproval – Indicate that the version is ready for an approval review.
- Reject - Rejects a version. This is normally done after a review has been done.
- CheckIn - Checks in a version indicating that it is ready to be published
- Publish - Publishes a version. The currently published version will automatically transition to a previously published state.
- Schedule – Used to schedule a version for automatic publishing at a later date. (New in CMS 10)
The previously available DelayedPublish flag that had to be used in conjunction with CheckIn has now been deprecated in favour of the Schedule action.
The Save flag is still supported but no longer recommended to use as it has a somewhat irregular behaviour that doesn't conform to all of the behavior described in this article. It is expected to be deprecated in a future release.
Options to control when new versions are created
The normal behaviour of the Save method is to always update the current version unless the current status is Published or PreviouslyPublished. In these cases, changes are applied to a new version instead.
If you want to ensure that you don’t overwrite an existing version you can provide the ForceNewVersion save action flag.
If you on the other hand want to ensure that no additional version is created you can provide the ForceCurrentVersion action flag.
It’s not possible to update the status of a Published or Previously Published version and it is therefore invalid to combine the ForceCurrentVersion flag with a status changing primary action. The Publish action is allowed when updating Published versions, but Save will behave exactly the same without it.
Using both the ForceCurrentVersion and the ForceNewVersion flag at the same time is not valid and will result in an exception.
There are a couple of more options that can be provided to the Save method, neither of which has any effect on status or how new versions are created.
When a new version is created by the save method it’s normally set as the “common draft”. This is the version that is selected by default when editing the content in the CMS user interface. If the SkipSetCommonDraft flag is provided, this step will be omitted.
Normally when you save content a set of validators are executed. To bypass this validation, simply pass in the SkipValidation flag.
In case where you want to updated the current version regardless if it's valid or not there is also a Patch value that combines the ForceCurrentVersion with SkipValidation. This can be handy for when you are updating something in an event handler and want to ensure that you don't disrupt the users workflow in case there is a validation error.
Saving unversioned content
Content types that does not implement IVersionable are considered “unversioned”.
Saving unversioned types is normally done without providing any save action unless you want to skip validation. If an action is provided, any primary action must be Publish and options flags cannot include ForceNewVersion.
Every time the Save method is called there are a plethora of events raised by the service implementing the IContentEvents interface. The exact events raised depends on the current state of the content version and what status it will get after the save is completed.
The SavingContent and SavedContent will always be raised whenever Save is called as long as no event handler cancels the action beforehand. This is a slight change to before CMS 10 when the save events wasn’t raised for new content or when publishing content without any modifications.
The CreatingContent and CreatedContent will be raised before the save events if the content is new.
If the saved content is a new language branch of an already existing content, it will result in the CreatingLanguageBranch and CreatedLanguageBranch events being raised. This event is new in CMS 10.
Finally, addition status specific event are raised depending on the resulting status of the save. These can be handy if you only want to react to a specific transition such as when content is published, but if you want to respond to multiple status changes you can just listening to the save event and get additional information about any status transition from the Transition property now included on the save event argument class.
The status specific event are raised regardless if the status is changed or not. Saving a published version with ForceCurrentVersion will thus raise the publish events again.
The full list of status save events are:
- CheckingOutContent/CheckedOutContent (new in CMS 10)
- SchedulingContent/ScheduledContent (new in CMS 10)
The exact access rights required to perform the Save will depend on the current state of the content and what status it will get after the save is completed.
Create access is required if no previous version exists or if saving a new language branch.
Edit access is required to update properties of an existing content item. Edit access is also required to check-out, check-in, request approval or reject a version.
Publish access is required to publish a version or to schedule a version for publishing at a later date. Publish access is also require if you want to update a Published or Scheduled version without creating a new version.
If a different access right is required it can be provided to the Save method via the AccessRights parameter. If no authentication should be performed, just pass in the NoAccess value.
With the changes made to status transitions and events in CMS 10 it should now be even easier to use Save than before and much more predictable.
In most cases it should even be sufficient to call Save(content) or Publish(content) if you want to publish it. Or just call Save(content, SaveAction.ForceCurrentVersion) if you want to update the current version regardless of what the current status is.
Thanks for taking time to improve API :)
Thanks for sharing this. Good to see API improvement in new version.
Common draft is still a bit confusing status for me, especially when programmatically creating/updating the content in a multi-language site from the schedule job.
Thanks for your comments!
We had a look at improving the API around common drafts as well, but decided to leave that this time around. I will have a look at the documenation and see if that can be improved somewhat to make things more clear.
Thanks for sharing this.
On "Schedule For publish" I used following code:
// delayed publish
var myPage = contentRepository.GetDefault
myPage.PageName = "My new page";
myPage["PageStartPublish"] = DateTime.Now.AddDays(2); // f.ex "2017-01-04 08:00:00"
var saved = contentRepository.Save(myPage, SaveAction.Schedule, AccessLevel.NoAccess);
In case, the user want to edit the page after scheduling. I 'd tried with following code:
var delayedPublishPage = (BlogPostPage)ContentLoader.Service.Get
var writableClone = delayedPublishPage.CreateWritableClone();
// check out
var checkout = contentRepository.Save(writableClone, SaveAction.CheckOut, AccessLevel.NoAccess);
writableClone = contentRepository.Get
// modify Page Start Publish
writableClone.PageName = "My delayed Publish Page";
writableClone["PageStartPublish"] = DateTime.Now.AddHours(2); // f.ex "2017-01-02 10:00:00"
var saved = contentRepository.Save(writableClone, SaveAction.Schedule, AccessLevel.NoAccess);
After schedule job has run, the page got published status, but StartPublish did not update with new value. It still have the old value "2017-01-04 08:00:00"
How can I remove schedule and edit page progammatically?
What is the proper approach to doing this? Thanks.