Management API create document with notification handler attached

Hi,
I have successfully created a new document via the management API but if I try and create a document that has a notification handler associated with it then I get a 404 error.

I use /umbraco/management/api/v1/document/validate initially and if that returns a 200 response then I call /umbraco/management/api/v1/document to create my generic page document in the backoffice and it works no problems.

But if I try and create a blogpost which hooks in to ContentSavingNotification and creates a new folder structure like

Root
- Blog
--Year
—-Month
——- MyBlogPage

Then the management API returns the 404 error.

I’ve checked the payload that is used when I create the node via the backoffice and can’t see any difference between the Generic Page creation and the BlogPost creation.

Anyone had any experience with this?

Some more details which might help :

My notification handler :

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Web.Common.PublishedModels;
using Umbraco.Extensions;
using OwainCodes.Core.Models;
using Umbraco.Cms.Core;

namespace OwainCodes.Core.NotificationHandler
{
    public class CreateDateFolderNotificationHandler : INotificationAsyncHandler<ContentSavingNotification>
    {
        private readonly IContentService _contentService;
        private readonly ICoreScopeProvider _scopeProvider;
        private readonly IPublishedContentTypeCache _publishedContentTypeCache;


        public CreateDateFolderNotificationHandler(IContentService contentService, ICoreScopeProvider scopeProvider, IPublishedContentTypeCache publishedContentTypeCache)
        {
            _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService));
            _scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider));
            _publishedContentTypeCache = publishedContentTypeCache ?? throw new ArgumentNullException(nameof(publishedContentTypeCache));
        }

        public async Task HandleAsync(ContentSavingNotification notification, CancellationToken cancellationToken)
        {
            using ICoreScope scope = _scopeProvider.CreateCoreScope();

            foreach (var node in notification.SavedEntities)
            {
                var parentNodeId = node.ParentId;

                if (node.ContentType.Alias == BlogPage.ModelTypeAlias)
                {
                    string parentModelTypeAlias = string.Empty;
                    string modelTypeAlias = string.Empty;
                    string datePropertyTypeAlias = string.Empty;

                    switch (node.ContentType.Alias)
                    {
                        case BlogPage.ModelTypeAlias:
                            modelTypeAlias = BlogPage.ModelTypeAlias;
                            parentModelTypeAlias = Blog.ModelTypeAlias;
                            datePropertyTypeAlias = BlogPage.GetModelPropertyType(_publishedContentTypeCache, selector: r => r.PublishDate).Alias;
                            break;
                        default:
                            throw new Exception("Error setting Model/Prop alias");
                    }

                    IContent monthFolder;

                    if (parentNodeId <= 0)
                    {
                        notification.CancelOperation(new EventMessage("Error", "Something went wrong with saving the resource", EventMessageType.Error));
                        continue;
                    }

                    var currentParent = GetCurrentParent(parentNodeId);
                    var date = node.GetValue<DateTime>(datePropertyTypeAlias);

                    if (date == DateTime.MinValue)
                    {
                        date = DateTime.Today;
                        node.SetValue(datePropertyTypeAlias, date);
                    }
                    var dateYear = date.ToString("yyyy");
                    var dateMonth = date.ToString("MMMM");

                    if (currentParent.ContentType.Alias == DateFolder.ModelTypeAlias)
                    {
                        bool moveNode = VerifyOrMoveToCorrectLocation(scope, currentParent, dateYear, dateMonth, out monthFolder);

                        if (moveNode)
                            node.SetParent(monthFolder);
                    }
                    else if (currentParent.ContentType.Alias == parentModelTypeAlias)
                    {
                        monthFolder = MoveToCorrectLocation(scope, currentParent, dateYear, dateMonth);

                        node.SetParent(monthFolder);
                    }
                }
            }
            scope.Complete();
        }

        private IContent MoveToCorrectLocation(ICoreScope scope, IContent currentParent, string dateYear, string dateMonth)
        {
            IContent monthFolder;
            var isNew = GetDateFolder(currentParent, dateYear, scope, out var folder);
            if (isNew)
            {
                monthFolder = CreateFolder(folder, dateMonth);
            }
            else
            {
                GetDateFolder(folder, dateMonth, scope, out monthFolder);
            }

            return monthFolder;
        }

        private bool VerifyOrMoveToCorrectLocation(ICoreScope scope, IContent currentParent, string dateYear, string dateMonth, out IContent monthFolder)
        {
            var moveNode = true;
            var currentMonthFolder = currentParent;
            var currentMonthFoldersParent = GetCurrentParent(currentParent.ParentId);

            if (currentMonthFolder.Name.InvariantEquals(dateMonth) && currentMonthFoldersParent.Name.InvariantEquals(dateYear))
            {
                monthFolder = null;
                moveNode = false;
            }
            //Wrong month right year
            else if (!currentMonthFolder.Name.InvariantEquals(dateMonth) && currentMonthFoldersParent.Name.InvariantEquals(dateYear))
            {
                GetDateFolder(currentMonthFoldersParent, dateMonth, scope, out monthFolder);
            }
            else
            {
                var parentOfParent = GetCurrentParent(currentMonthFoldersParent.ParentId);
                GetDateFolder(parentOfParent, dateYear, scope, out var yearFolder);
                GetDateFolder(yearFolder, dateMonth, scope, out monthFolder);
            }

            return moveNode;
        }

        private bool GetDateFolder(IContent parentNode, string nodeName, ICoreScope scope, out IContent folder)
        {
            var dateFolderTypeId = DateFolder.GetModelContentType(_publishedContentTypeCache).Id;

            var matchingChild = _contentService.GetPagedChildren(parentNode.Id, 0, int.MaxValue, out _)
                .Where(c => c.Name.InvariantEquals(nodeName) && c.Trashed == false && c.ContentTypeId == dateFolderTypeId);

            if (matchingChild == null || !matchingChild.Any())
            {
                folder = CreateFolder(parentNode, nodeName);
                return true;
            }

            folder = matchingChild.FirstOrDefault();
            return false;
        }

        private IContent CreateFolder(IContent parentNode, string name)
        {
            IContent dateFolder = _contentService.CreateAndSave(name, parentNode.Id, "DateFolder");

            _contentService.PublishBranch(dateFolder, PublishBranchFilter.Default, ["en-us"]);

            return dateFolder;
        }

        private IContent GetCurrentParent(int parentId)
        {
            return _contentService.GetById(parentId);
        }
    }
}


Registered the handler within Progam.cs

builder.CreateUmbracoBuilder()
	.AddBackOffice()
	.AddWebsite()
	.AddComposers()
	.AddNotificationAsyncHandler<ContentSavingNotification, CreateDateFolderNotificationHandler>()
	.Build();

Then in the backoffice, I have a Blog Node which allows children of Blog. I also have a doctype called DateFolder which is added via the Notification Handler so that I get the hierarchy for the blogs e.g. Year/Month

I’ve tested this within the backoffice and everything gets created as expected, I’ve also ran my external api calls on non-blog pages e.g. a generic page which doesn’t use the DateFolder structure, just a simple creation on the root or under a home node and it gets created without any issues.

I use the API endpoints :

ValidateEndpoint - /umbraco/management/api/v1/document/validate
await CallUmbracoApi(validateEndpoint, token, 'POST', body);

CreateEndpoint - umbraco/management/api/v1/document
const createResponse = await CallUmbracoApi(createEndpoint, token, 'POST', body);

The body is :

const body = {
			"documentType": {
				"id": nodeId
			},
			"template": null, 
			"values": [
				{
					"alias": this.settings.titleAlias,
					"culture": null,
					"segment": null,
					"value": pageTitle || ""
				},
				{
					"alias": this.settings.blogContentAlias,
					"culture": null,
					"segment": null,
					"value": pageContent?.content || ""
				}
			],
			"variants": [
				{
					"name": pageTitle,
					"culture": null,
					"segment": null
				}
			],
			"id": await GenerateGuid(),
			"parent": (this.settings.blogParentNodeId && this.settings.blogParentNodeId.trim() !== '' && this.settings.blogParentNodeId !== 'null') 
				? { "id": this.settings.blogParentNodeId } 
				: null
		};

And this body is used for Validate AND Document

The validate endpoint returns a 200 repsonse, the Document endpoint returns a 404.

Hope that helps.

Haven’t looked at the code, but is this the classic problem of doing it all in one scope and then you think you’ve “created” a node but it doesn’t do anything until the scope completes?
Then when you ask for the created node, of course it does not exist yet..

I dont think so.
From what I can tell, it’s the response from the Create endpoint that returns the 404 when trying to create the blog and I would expect the same error when I am creating a generic page if this was a scope issue, maybe?
With no changes to the code, other than the doctype I’m creating, everything works under one scenario but not the other.

Unless I’m misunderstanding something of course :slight_smile:

Well. After a lot of headscratching I tried doing this on a different test site and with no code changes - it just works! No idea what is different on my other site but there you have it. Nothing is broken just something odd on my instance,

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.