How to get block list data from a field via IContent

I have just upgraded to v17 from v13 and some code that I had written for a custom search indexer (for Azure search) now does not work for documents with block lists because it cannot deserialize the string value to json.

This code gets data for any block list fields (in this example, just one field):

var rawComponents = new List<BlockValue>()
{
				GetBlockValue(content.GetValue<string>("components"))
}.Where(x => x != null).SelectMany(x => x.ContentData);

The GetBlockValue function is as follows, it deserializes it from the string value into the BlockValue object:

BlockValue GetBlockValue(string json)
		{
			return string.IsNullOrWhiteSpace(json) ? null : Newtonsoft.Json.JsonConvert.DeserializeObject<BlockValue>(json);
		}

This now throws the following error:

Could not create an instance of type Umbraco.Cms.Core.Models.Blocks.BlockValue. Type is an interface or abstract class and cannot be instantiated. Path 'contentData', line 1, position 15.

If I create my own class to sit on top of BlockValue, I get this error:

Could not create an instance of type Umbraco.Cms.Core.Models.Blocks.IBlockLayoutItem. Type is an interface or abstract class and cannot be instantiated. Path 'Layout['Umbraco.BlockList'][0].contentUdi', line 1, position 581.

Here’s the value from one of the blocklist fields:

{
  "contentData": [
    {
      "contentTypeKey": "74a0b202-b95c-43f6-82ea-5df1b515b2ed",
      "udi": null,
      "key": "6c5220d4-aa75-46a3-ae23-a0c75f9546b3",
      "values": [
        {
          "editorAlias": "Umbraco.RichText",
          "culture": null,
          "segment": null,
          "alias": "content",
          "value": "{\\u0022markup\\u0022:\\u0022\\\\u003Cp\\\\u003Erte\\\\u003C/p\\\\u003E\\u0022,\\u0022blocks\\u0022:{\\u0022contentData\\u0022:[],\\u0022settingsData\\u0022:[],\\u0022expose\\u0022:[],\\u0022Layout\\u0022:{}}}"
        }
      ]
    }
  ],
  "settingsData": [],
  "expose": [
    {
      "contentKey": "6c5220d4-aa75-46a3-ae23-a0c75f9546b3",
      "culture": null,
      "segment": null
    }
  ],
  "Layout": {
    "Umbraco.BlockList": [
      {
        "contentUdi": null,
        "settingsUdi": null,
        "contentKey": "6c5220d4-aa75-46a3-ae23-a0c75f9546b3",
        "settingsKey": null
      }
    ]
  }
}

I’m at a bit of a loss as I have been unable to find any documentation around this change, some help would be great!

Hello @jplatfordquba

I would recommended to use content.Value(“components”) instead of content.GetValue(“components”)

Example code:

var blocks = content.Value(“components”);

if (blocks == null)
return;

foreach (var block in blocks)
{
var blockContent = block.Content;

if (blockContent.HasProperty("content"))
{
    var rteValue = blockContent.Value<string>("content");
}

}

Hi Nikhil,

Sorry, I should have been clearer. This is getting the value from an IContent object rather than IPublishedContent, so .Value is not available.

1 Like

My workaround for this has been to create my own class to deserialize to:

public class CustomUmbracoBlockValue
{
    public List<BlockItemData> ContentData { get; set; } = [];

    public List<BlockItemData> SettingsData { get; set; } = [];
    public IList<BlockItemVariation> Expose { get; set; } = new List<BlockItemVariation>();
}

This does not include the layout property causing the issues shown in my original post. For the time being, this looks to have sorted it as content is now indexed as it was prior to the upgrade to version 17.

Could you use the core convertor?
Umbraco-CMS/src/Umbraco.Infrastructure/Serialization/JsonBlockValueConverter.cs at main · umbraco/Umbraco-CMS

If you inject in IJsonSerializer, then you can use that to deserialize the value like:

var blockListValue = _serializer.Deserialize<BlockListValue>(node.GetValue("yourBlockListValueAlias") ?? "{}");

Hi Soren,

Thanks, that works too and is better than having a custom class.

I, however, jumped the gun a bit and have noticed that the block data is not indexed correctly (although the original error now no longer appears).

Further to my code snippet(s) above, there is this loop to get the page content from block lists. It simply loops through the block list fields and gets the data:

foreach(var component in rawComponents)
{
				var contentType = _contentTypeService.Get(component.ContentTypeKey);
				switch (contentType.Alias)
				{
					case RichText.ModelTypeAlias:
						searchData.Add(component.PropertyValue("content"));
...
}

In v13, the .PropertyValue function returned the block value within the content field of the block (in this case a Rich Text block). Now it just returns an empty string.

One of the blocks it is trying to index is:

{Umbraco.Cms.Core.Models.Blocks.BlockPropertyValue}
    Alias: "content"
    Culture: null
    EditorAlias: null
    PropertyType: null
    Segment: null
    Value: "{\"markup\":\"\\u003Cp\\u003Erte\\u003C/p\\u003E\",\"blocks\":{\"contentData\":[],\"settingsData\":[],\"expose\":[],\"Layout\":{}}}"

How do I get out the content in the Value property like it would have done already in v13?

Ended up having to write some extensions:

public static string StringValue(this BlockItemData blockItemData, string propertyName)
{
	var propertyValue = blockItemData.Values.Where(v => v.Alias == propertyName).FirstOrDefault();

	var propValue = propertyValue?.Value?.ToString() ?? string.Empty;
	if (string.IsNullOrWhiteSpace(propValue))
		return string.Empty;

    return propValue;
}

This retrieves the BlockItemData property value as a string.

For block list fields:

public static BlockListValue? BlockListValue(this BlockItemData blockItemData, IJsonSerializer serializer, string propertyName)
{
    var propertyValue = blockItemData.Values.Where(v => v.Alias == propertyName).FirstOrDefault();

    var propValue = propertyValue?.Value?.ToString() ?? string.Empty;
    if (string.IsNullOrWhiteSpace(propValue))
        return null;

    return serializer.Deserialize<BlockListValue>(propValue);
}