Programmability create BlockListItem content

I’ve got a document type called Settings which contains a BlockList property called AkeneoProductMap which includes an element type of AkeneoProductMapItem.

I’ve tried reading through the following but still struggling:

Do I use new BlockListItem or new AkeneoProductMapItem.

If I use AkeneoProductMapItem all the properties are read only.

I think the issue you are running into is that BlockListItem and AkeneoProductMapItem are intended to be outputs from Umbraco, not as inputs back into umbraco. Block list data is stored as json text which doesnt match these classes, so you need to serialize whatever c# class you have into the desired block list json structures.

Here is how a block list is stored in umbraco (your guids and values will vary):

{
	"layout": {
		"Umbraco.BlockList": [
			{
				"$type": "BlockListLayoutItem",
				"contentUdi": "umb://element/7c76ea5b1b04453eb5d487afde5486b7",
				"settingsUdi": null,
				"contentKey": "7c78ea5b-1b04-453e-b5d4-87afde5486b7",
				"settingsKey": null
			}
		]
	},
	"contentData": [
		{
			"contentTypeKey": "5bd0307c-a5f3-4ecc-86f6-a936c116e188",
			"udi": null,
			"key": "7c78ea5b-1b04-453e-b5d4-87afde5486b7",
			"values": [
				{
					"editorAlias": "Umbraco.TextBox",
					"culture": null,
					"segment": null,
					"alias": "scriptName",
					"value": "Sample"
				},
				{
					"editorAlias": "Umbraco.DropDown.Flexible",
					"culture": null,
					"segment": null,
					"alias": "location",
					"value": [
						"Head - Start"
					]
				},
				{
					"editorAlias": "Umbraco.TextArea",
					"culture": null,
					"segment": null,
					"alias": "script",
					"value": "my data here"
				},
				{
					"editorAlias": "Umbraco.TrueFalse",
					"culture": null,
					"segment": null,
					"alias": "enabled",
					"value": true
				}
			]
		}
	],
	"settingsData": [],
	"expose": [
		{
			"contentKey": "7c78ea5b-1b04-453e-b5d4-87afde5486b7",
			"culture": null,
			"segment": null
		}
	]
}

Typically I’ll look at a raw json sample block list field value (target value) and then build new c# classes that match it. I’ll populate a model and then serialize it, and compare its format to the sample target value I found earlier.

You then need to update the content, so in your c# code you can call the IContentService to get the desired node, set the desired property value, and save and publish the node.

Note that is is typically rare to need to programatically update umbraco nodes, but it depends on your use case.

Here is a sample c# controller that calls the contentservice to save json data, this may help you see how to save umbraco content:

using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;

namespace Core.SiteSearch.Controllers
{
	/// <summary>
	/// 
	/// </summary>
	[ApiController]
	[Route("[controller]/[action]")]
	public class MyImportController : ControllerBase
    {
        private readonly ILogger<MyImportController> _logger;
        private IUmbracoContextAccessor _umbracoContextAccessor { get; set; }
        private IContentService _contentService { get; set; }
        public MyImportController(ILogger<MyImportController> logger, IUmbracoContextAccessor umbracoContextAccessor,
            IContentService contentService)
        {
            _logger = logger;
            _umbracoContextAccessor = umbracoContextAccessor;
            _contentService = contentService;
        }


        // /MyImport/Import/
        [HttpGet]
        public List<string> Import(bool previewOnly = true)
        {
            List<string> result = new List<string>();
            var contentNodes = _contentService.GetByIds(new List<int> { 1, 2 });

            foreach (var node in contentNodes)
            {
                var currentValue = node.GetValue<string>("blockContent");

                //TODO: build new structure and serialize it here...
                var newValue = "";
                //ENDTODO

                if (previewOnly)
                {
                    result.Add($"I would be saving {node.Id} {node.Name}: {newValue}");
                }
                else
                {
                    node.SetValue("blockContent", newValue);
                    _contentService.Save(node);
                    _contentService.Publish(node, Array.Empty<string>());
                    result.Add($"Saved {node.Id} {node.Name}: {newValue}");
                }
            }
            return result;
        }
    }
}
1 Like

Hi Aaron,

Thanks for the reply - really appreciate you taking the time!

I’ll explain why I needed dynamically created content. The client’s after a B2C-facing website, mainly for product information like technical drawings, fitting guides, imagery, and product specs — but they’ve got no intention of selling B2C. They already have an established B2B channel.

They want to manage blog posts, tweak marketing content, and have product data sync automatically from Akeneo.

At the moment, they’ve got around 5,800 products in Umbraco, with 184 Akeneo attributes mapped to Umbraco properties. I’ve automated the creation of the properties on the document type.

For the sync (handled with Hangfire), I’ve built it to:

  • Create products in Umbraco if they exist in Akeneo but not in Umbraco
  • Move products to the correct category/range if that’s changed in Akeneo and when they move, nuke any previously stored attribute values tied to the old category/range
  • Sync the mapped attributes depending on category specific rules (PDFs, images, product data)

I originally planned to use a BlockList to manage the category to attribute mappings, but with this much data it just became painful to manage. I feel like BlockList/BlockGrid could be so much easier to work with. Umbraco even generates a POCO for us, but doesn’t really give us a clean way to make use of it programmatically.

So in the end, I’ve decided to let them upload a CSV for their mappings instead.

1 Like

I see, that makes sense. Glad you got it working.