Using ContentService.Saving event to remove Allowed child node types
Even though Jeroen pointed me in the right direction earlier to control the depth of nodes in the content tree: "Home>Child>Child" but stop the creation of "Home>Child>Child>Child" I can't seem to hook into the right event to have this happen?
private static void ContentService_Saving(IContentService sender, SaveEventArgs<IContent> e)
{
foreach (var node in e.SavedEntities.Where(
node =>
node.ContentType.Alias.InvariantEquals("NG_USK_StandardPage") ||
node.ContentType.Alias.InvariantEquals("NG_USK_NewsArticle") ||
node.ContentType.Alias.InvariantEquals("NG_USK_Downloads")
)
)
{
try
{
if (node.Level == 3)
{
RemoveAllowedContentTypes(node);
}
}
catch (Exception err)
{
// Get stack trace for the exception with source file information
var st = new StackTrace(err, true);
// Get the top stack frame
var frame = st.GetFrame(0);
// Get the line number from the stack frame
var line = frame.GetFileLineNumber();
LogHelper.Warn(typeof(CreateKitExtensions), "ERROR ON: ContentService_Publishing - " + node.ContentType.Alias + " ~ " + err.Message + "[" + line + "]:" + st);
}
}
}
private static void RemoveAllowedContentTypes(IContent node)
{
try
{
List<ContentTypeSort> allowedTypes = new List<ContentTypeSort>(node.ContentType.AllowedContentTypes);
foreach (var allowedChild in allowedTypes.Where(allowedChild => allowedChild.Alias.Equals("NG_USK_StandardPage")).Reverse())
{
allowedTypes.Remove(allowedChild);
}
node.ContentType.AllowedContentTypes = allowedTypes;
}
catch (Exception oops)
{
// Get stack trace for the exception with source file information
var st = new StackTrace(oops, true);
// Get the top stack frame
var frame = st.GetFrame(0);
// Get the line number from the stack frame
var line = frame.GetFileLineNumber();
LogHelper.Warn(typeof(CreateKitExtensions), "ERROR ON: RemoveAllowedContentTypes - " + node.ContentType.Alias + " ~ " + oops.Message + "[" + line + "]:" + st);
}
}
In this code example, I loop through the allowed types and use a reversed list to modify the types on the fly - this works according to VS when I set a breakpoint on "node.ContentType.AllowedContentTypes = allowedTypes;" the count has dropped by 1.
As you can see, I've attempted to use the saving event like this so it will still save the node even though the allowedChildTypes list has been modified?
It's not though as when I click the node in the content tree after the save event has completed and the page has refreshed the doctype I want to restrict is still showing in the allowed child listing.
As always, any and all help is greatly appreciated!
I was thinking more about using the content_new event or something like that. In there get the parent id and if that path is too deep cancel the event.
I was going the CS Saving route as the 3rd level node will always be created before a deeper level is and this approach removes the allowed child type before any editor can try to add anything down the road.
The code above works up to the point of save for the new node - here's the desired process:
create 2nd level standard page node
click to add 3rd level child and am presented with both child allowed child types
on-saving event fires and removes the allowed child node type discretely
click to add 4th level and am only presented with the single allowed type <- this isn't happening and both child types are still there!
I don't think you can change the allowed child nodes per node. It's only per document type so your event probably won't work. You could try the second suggestion I wrote about hijacking the web api.
I took a slightly different approach as I was slightly perplexed by the examples you linked to, it wasn't your code but my level of understanding!
With that said, and after all the research I had done on the content service api, I persevered with my attack route and was successful this afternoon with this code:
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Services;
namespace NG.USK.Helpers
{
public class CreateKitExtensions : ApplicationEventHandler
{
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
//Changed all events to Saved due to Infinite Looping
ContentService.Saved += CreateControlPanel;
ContentService.Saved += CheckDocumentTypes;
//ContentService.Publishing += ContentService_Publishing;
}
/// <summary>
/// Check to see if the node is one that we want to control - these are the only doctypes that can be below level 2
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void CheckDocumentTypes(IContentService sender, SaveEventArgs<IContent> e)
{
//
foreach (var node in e.SavedEntities.Where(
node =>
node.ContentType.Alias.InvariantEquals("NG_USK_StandardPage") ||
node.ContentType.Alias.InvariantEquals("NG_USK_NewsArticle") ||
node.ContentType.Alias.InvariantEquals("NG_USK_Downloads")
)
)
{
try
{
//Check to make sure the level is deep enough
if (node.Level == 3)
{
SwapDocumentType(node);
}
}
catch (Exception err)
{
// Get stack trace for the exception with source file information
var st = new StackTrace(err, true);
// Get the top stack frame
var frame = st.GetFrame(0);
// Get the line number from the stack frame
var line = frame.GetFileLineNumber();
LogHelper.Warn(typeof(CreateKitExtensions), "ERROR ON: ContentService_Publishing - " + node.ContentType.Alias + " ~ " + err.Message + "[" + line + "]:" + st);
}
}
}
/// <summary>
/// Attempt to remove the AllowedContentTypes from the specific content node
/// </summary>
/// <param name="node"></param>
/// <remarks>
/// This failed due to the lock on the AllowedContentTypes being per document type and not per document node
/// </remarks>
private static void RemoveAllowedContentTypes(IContent node)
{
try
{
//Create a listing of all the current Allowed Content Types
List<ContentTypeSort> allowedTypes = new List<ContentTypeSort>(node.ContentType.AllowedContentTypes);
//Loop thought the listing and find the flagged AllowedContentType
//use a reverse to copy the listing into a temporary collection and prevent the dreaded
//"System.InvalidOperationException: Collection was modified; enumeration operation may not execute" error
foreach (var allowedChild in allowedTypes.Where(allowedChild => allowedChild.Alias.Equals("NG_USK_StandardPage")).Reverse())
{
//Remove the flagged AllowedContentType
allowedTypes.Remove(allowedChild);
}
//Reload the AllowedContentTypes inthe the node
node.ContentType.AllowedContentTypes = allowedTypes;
}
catch (Exception oops)
{
// Get stack trace for the exception with source file information
var st = new StackTrace(oops, true);
// Get the top stack frame
var frame = st.GetFrame(0);
// Get the line number from the stack frame
var line = frame.GetFileLineNumber();
LogHelper.Warn(typeof(CreateKitExtensions), "ERROR ON: RemoveAllowedContentTypes - " + node.ContentType.Alias + " ~ " + oops.Message + "[" + line + "]:" + st);
}
}
/// <summary>
/// Since we can't modify the AllowedContentTypes of a node you can change the
/// content type of the node to another with different AllowedContentTypes instead!
/// </summary>
/// <param name="node"></param>
private static void SwapDocumentType(IContent node)
{
try
{
var contentService = ApplicationContext.Current.Services.ContentService;
var contentTypeService = ApplicationContext.Current.Services.ContentTypeService;
//Check is the node is dirty or not
if (node.HasIdentity)
{
//Given a 'ContentTypeService' object get the ContentType that we're changing to,
var contentType = contentTypeService.GetContentType("NG_USK_StandardPageChild");
//get the Content from the 'ContentService' for which we want to change ContentType for,
var content = contentService.GetById(node.Id);
//swap the ContentType over for the content node
content.ChangeContentType(contentType);
//and then saveand publish with status the Content throught the ContentService.
contentService.SaveAndPublishWithStatus(content);
}
else
{
//Save the node so it is dirty and loop back through the content service events
contentService.Save(node);
}
}
catch (Exception oops)
{
// Get stack trace for the exception with source file information
var st = new StackTrace(oops, true);
// Get the top stack frame
var frame = st.GetFrame(0);
// Get the line number from the stack frame
var line = frame.GetFileLineNumber();
LogHelper.Warn(typeof(CreateKitExtensions), "ERROR ON: SwapDocumentType - " + node.ContentType.Alias + " ~ " + oops.Message + "[" + line + "]:" + st);
}
}
/// <summary>
/// On Starter Kit installation - create a control panel node to the root level
/// then publish the homepage and children
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void CreateControlPanel(IContentService sender, SaveEventArgs<IContent> e)
{
//If the event level is not at the root leave
if (e.SavedEntities.All(x => x.Level != 1)) return;
//Create a ContentService Object
var service = ApplicationContext.Current.Services.ContentService;
//Use the CSO to get all the children at the root
var rootNodeChildren = service.GetChildren(-1);
// check if the root node's children has a node with the Control Panel's doctype
if (!rootNodeChildren.Any(x => x.ContentType.Alias.InvariantEquals("NG_Global__Options")))
{
// if not, call the method to create a Control Panel node
MakeControlPanel();
}
//Find the root node
var homeNode = service.GetRootContent().FirstOrDefault(x => x.ContentType.Alias.InvariantEquals("NG_USK_Homepage"));
//Add everything to the Content Tree - even the unpublished nodes!
service.PublishWithChildrenWithStatus(homeNode, GetCurrentUserId(), true);
}
/// <summary>
///
/// </summary>
private static void MakeControlPanel()
{
try
{
//Create a ContentService Object
var service = ApplicationContext.Current.Services.ContentService;
//Create a Control Panel node
var controlPanel = service.CreateContent("Control Panel", -1, "NG_Global__Options");
//Add it to the content tree
service.SaveAndPublishWithStatus(controlPanel);
// once saved/published, check that it has an ID...
if (controlPanel.HasIdentity)
{
// ... then create the Site Settings node
MakeSiteSettings(controlPanel);
}
}
catch (Exception ex)
{
LogHelper.Error<CreateKitExtensions>("MakeControlPanel", ex);
}
}
private static void MakeSiteSettings(IEntity node)
{
try
{
//Create a ContentService Object
var service = ApplicationContext.Current.Services.ContentService;
//Create the Site Settings node
var siteSettings = service.CreateContent("Site Settings", node.Id, "NG_Global__SiteSettings");
//Add it to the Content Tree
service.SaveAndPublishWithStatus(siteSettings);
}
catch (Exception ex)
{
LogHelper.Error<CreateKitExtensions>("MakeSiteSettings", ex);
}
}
/// <summary>
/// Get the Current User
/// </summary>
private static int GetCurrentUserId()
{
//Create a UserService Object
var userService = ApplicationContext.Current.Services.UserService;
//Get the id of the current user
return userService.GetByUsername(HttpContext.Current.User.Identity.Name).Id;
}
/// <summary>
/// This is based on code from Dan Lister (@dan_lister)
/// https://our.umbraco.org/member/46642
/// and can be found on the Our Umbraco forum post:
/// https://our.umbraco.org/forum/developers/api-questions/60263-Get-allowed-child-node-types-by-code
/// </summary>
/// <param name="alias"></param>
/// <returns></returns>
public IEnumerable<IContentType> GetAllowedContentTypes(string alias)
{
var retval = new List<IContentType>();
if (string.IsNullOrWhiteSpace(alias))
return retval;
// Use the content type service to find
// the content type passed to the method
var contentTypeService = ApplicationContext.Current.Services.ContentTypeService;
var contentType = contentTypeService.GetContentType(alias);
if (contentType == null)
return retval;
// If we have found the content type, loop through
// each of it's allowed content types and select
// the content type
retval.AddRange(
contentType.AllowedContentTypes.Select(
ct => contentTypeService.GetContentType(ct.Id.Value)));
return retval;
}
}
}
I've left the wrong code in in case anyone stumbles on this later!
i searched internet error message path is too deep and found this one, its really helpful, also i solved my issue "Long Path Tool". thanks guys for your awesome article, you can try with Long Path Tool it may help you
Using ContentService.Saving event to remove Allowed child node types
Even though Jeroen pointed me in the right direction earlier to control the depth of nodes in the content tree: "Home>Child>Child" but stop the creation of "Home>Child>Child>Child" I can't seem to hook into the right event to have this happen?
In this code example, I loop through the allowed types and use a reversed list to modify the types on the fly - this works according to VS when I set a breakpoint on "node.ContentType.AllowedContentTypes = allowedTypes;" the count has dropped by 1.
As you can see, I've attempted to use the saving event like this so it will still save the node even though the allowedChildTypes list has been modified?
It's not though as when I click the node in the content tree after the save event has completed and the page has refreshed the doctype I want to restrict is still showing in the allowed child listing.
As always, any and all help is greatly appreciated!
Hello,
I was thinking more about using the content_new event or something like that. In there get the parent id and if that path is too deep cancel the event.
A complete different approach would be to hijack the web api call that returns the allowed child nodes. In AngularJS everything is done with ajax. For the Hybrid Framework I hijack the web api call which returns the content so I can change the media picker start node. You can find an example here: https://github.com/jbreuer/Hybrid-Framework-for-Umbraco-v7-Best-Practises/blob/master/Umbraco.Extensions/Utilities/WebApiHandler.cs
Try to find the web api call that returns the allowed child nodes and see if you can do it in there.
Jeroen
Jeroen,
I was going the CS Saving route as the 3rd level node will always be created before a deeper level is and this approach removes the allowed child type before any editor can try to add anything down the road.
The code above works up to the point of save for the new node - here's the desired process:
Does this help clarify the issue?
Hello,
I don't think you can change the allowed child nodes per node. It's only per document type so your event probably won't work. You could try the second suggestion I wrote about hijacking the web api.
Jeroen
Jeroen,
I took a slightly different approach as I was slightly perplexed by the examples you linked to, it wasn't your code but my level of understanding!
With that said, and after all the research I had done on the content service api, I persevered with my attack route and was successful this afternoon with this code:
I've left the wrong code in in case anyone stumbles on this later!
i searched internet error message path is too deep and found this one, its really helpful, also i solved my issue "Long Path Tool". thanks guys for your awesome article, you can try with Long Path Tool it may help you
is working on a reply...