Umbraco 11 - Creating a Block Grid programmatically - Areas query
Hi All, I'm in the process of migrating some very large U7 sites into v11 (18k+ articles on this one) . I've taken the approach of starting from scratch, and exporting my old content as XML and importing it into a clean v11 install with some custom import scripts.
Generally it's going very well and i'm making excellent progress, which is lovely.
I've got to the stage where I'm mapping my old DTGE content into the new Block Grid data type. I've managed to get everything from here working nicely:
However in my v11 setup I'd like to use areas going forwards, and force all my blocks to be inside layout areas (simply 6-6 and 12. However the documentation above includes this:
JsonProperty("areas")]
// areas are omitted from this sample for abbreviation
public object[] Areas { get; } = { };
Clearly this is what I need to implement, rather than adding my data to the root grid editor I need to add them to areas, but Im struggling to find any examples of this being done. Any pointers?
The layout is as follows:
So I'm looking to get my blocks inside those areas on import:
Currently they are just getting added into the root of the editor and outside of the areas.
I've managed to figure most of this out by inspecting the JSON in the database. The main issue I have remaining is that each area requires a key and it looks like that key is specific to the block grids layout block area configuration. I can't however see how I would find that key programatically.
For now i've hard coded the area keys into the import tool, so it works and puts the blocks inside the areas now.
As you can see beloe the blocks are correctly added to the full width area.
Hi, we have been dealing with this as well. This is the way I get the data now. (we are still testing/refactoring, so the code below will be ugly/hardcoded)
// get the layout type
IContentType split = _contentTypeService.Get("split");
// get the blockgrid data type
BlockGridConfiguration blockGridDataTemp = (BlockGridConfiguration)_dataTypeService.GetDataType("Block Grid Test").Configuration;
// filter out the block.
var splitLayoutBlock = blockGridDataTemp.Blocks.FirstOrDefault(x => x.ContentElementTypeKey == split.Key);
With this approach you get an object in which you can get the keys for the areas.
// get the element types for spot blocks (content and settings)
// build the individual parts of the block grid data from the raw data
var layoutItems = new List<BlockGridLayoutItem>();
var areaItems = new List<BlockGridAreaItem>();
var gridContentData = new List<BlockGridElementData>();
var gridSettingsData = new List<BlockGridElementData>();
//add layout to the grid first
foreach (GridExportRow row in contentPage.GridExportSections!.GridExportSection!.GridExportRows!.GridExportRow!) {
foreach (var area in row.GridExportItems!.GridExportItem!.GridExportAreas!.GridExportArea!) {
// generate new UDIs for block content and settings
var contentUdi = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var settingsUdi = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
switch (area.Alias)
{
case "rte":
// create new content data
IContentType? blockRTEType = _contentTypeService.Get("blockGridRichtext");
// create a new layout item
layoutItems.Add(new BlockGridLayoutItem(contentUdi, int.Parse(area.Columns), 1, new(), settingsUdi));
gridContentData.Add(new BlockGridElementData(blockRTEType!.Key, contentUdi, new Dictionary<string, object>
{
{ "content", area!.Rte },
}));
break;
case "media":
// generate new UDIs for block content and settings
IContentType? blockImageType = _contentTypeService.Get("blockGridImage");
// create a new layout item
layoutItems.Add(new BlockGridLayoutItem(contentUdi,int.Parse(area.Columns), 1, new(), settingsUdi));
//upload the image if it hasn't already been uploaded so we don't have the same image uploaded multiple times.
if (area.Media !=null && area.Media.Image != "")
{
var existingMediaItem = mediaFolder!.Descendants<Image>().Where(x => x.PreviousID == area.Media.Id.ToString().Trim());
if (existingMediaItem != null && existingMediaItem.Any())
{
Udi UDIMedia = Udi.Create(Constants.UdiEntityType.Media, existingMediaItem.FirstOrDefault()!.Key);
gridContentData.Add(new BlockGridElementData(blockImageType!.Key, contentUdi, new Dictionary<string, object>{
{ "image", UDIMedia },
}));
}
else {
try
{
string mainImage = "https://www.oldsitedomain.com/" + area.Media.Image;
int uploadedImageID = UploadMediaAsync(mainImage, 12345, area.Media.Id);
var primaryImage = _mediaService.GetById(uploadedImageID);
if (primaryImage != null)
{
var mediaUdi = Udi.Create(Constants.UdiEntityType.Media, primaryImage.Key);
gridContentData.Add(new BlockGridElementData(blockImageType!.Key, contentUdi, new Dictionary<string, object>
{
{ "image", mediaUdi },
}));
}
}
catch
{
//logging removed
}
}
}
// create new content data
break;
case "quote":
// generate new UDIs for block content and settings
IContentType? blockQuoteType = _contentTypeService.Get("blockGridQuote");
// create a new layout item
layoutItems.Add(new BlockGridLayoutItem(contentUdi, int.Parse(area.Columns), 1, new(), settingsUdi));
//upload the image
if (area.Quote!.Value != "")
gridContentData.Add(new BlockGridElementData(blockQuoteType!.Key, contentUdi, new Dictionary<string, object>{
{ "quote", area.Quote.Value },
}));
}
// create new content data
break;
}
}
}
//setup areas
var areas = new List<BlockGridArea>();
foreach (var gridContent in layoutItems) {
var areaItemLoop = new BlockGridAreaItem();
var settingsUdi = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
areaItemLoop.ContentUdi = gridContent.ContentUdi;
areaItemLoop.SettingsUdi = settingsUdi;
areaItemLoop.Areas = new();
areaItemLoop.ColumnSpan = gridContent.ColumnSpan;
areaItemLoop.RowSpan = gridContent.RowSpan;
areaItems.Add(areaItemLoop);
}
layoutItems.Clear();
//area for two col (left)
areas.Add(new BlockGridArea(Guid.Parse("GUIDFORLAYOUTAREA1"), new()));
//area for two col (right)
areas.Add(new BlockGridArea(Guid.Parse("GUIDFORLAYOUTAREA2"), new()));
//area for fullwidth col
areas.Add(new BlockGridArea(Guid.Parse("GUIDFORLAYOUTAREA3"), areaItems));
//create layout area
var contentLayoutUdi = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
IContentType? contentLayoutType = _contentTypeService.Get("blockGridContentLayout");
gridContentData.Add(new BlockGridElementData(contentLayoutType!.Key, contentLayoutUdi, new Dictionary<string, object>
{
}));
layoutItems.Add(new BlockGridLayoutItem(contentLayoutUdi, 12, 1, areas));
//new BlockGridArea(new Guid(), areaItems)
// construct the block grid data from layout, content and settings
var blockGridData = new BlockGridData(
new BlockGridLayout(layoutItems.ToArray()),
gridContentData.ToArray(),
gridSettingsData.ToArray());
// serialize the block grid data as JSON and save it to the "blockGrid" property on the content item
var propertyValue = JsonConvert.SerializeObject(blockGridData);
content!.SetValue("content", propertyValue);
}
if (!skipItem) {
_contentService.SaveAndPublish(content!, "en-GB");
}
if (unpublishItem) {
_contentService.Unpublish(content!, "en-GB");
}
The above is very much cut down, and specifically the grid to block grid importing. Hopefully it might give you something to start off with. Our process now is: generate XML from the existing v7 site. On the new v11 site, get that xml, map it to models and create or update content items. The above section deals specfically with the block grid import. It's heavily stripped down. We have a bunch of additional DTGE mappings and other grid fields that are also migrated and inserted appropriately. We then have Hangfire running on the v11 site so we we can re run the imports as the code is amended. It's working pretty well at this point.
I'm so frustratingly close. But after attempting an import, I get empty areas like this:
The frustrating thing is, the JSON for the property looks identical (to me) to when I create actual content in the Umbraco backoffice.
Here's the "bad" property with the missing area content:
{
"layout": {
"Umbraco.BlockGrid": [
{
"contentUdi": "umb://element/6d4f25cdff2243eda4e7c9d9d9e0d9b5",
"areas": [
{
"key": "bd0dbcc5-7141-41d1-9f73-9a67f205aad1",
"items": [
{
"contentUdi": "umb://element/2d16a0fca4ad4d08ac73a6ccba84beb8",
"columnSpan": 7,
"rowSpan": 1,
"areas": []
}
]
},
{
"key": "23461d63-60bf-42f2-ad7a-6728b5a54947",
"items": [
{
"contentUdi": "umb://element/9349829067e241c4a22a67ab7c653708",
"columnSpan": 5,
"rowSpan": 1,
"areas": []
}
]
}
]
}
]
},
"contentData": [
{
"contentTypeKey": "f3ea7f29-4e2f-4c73-951e-7f8599ffb510",
"udi": "umb://element/6d4f25cdff2243eda4e7c9d9d9e0d9b5"
},
{
"contentTypeKey": "e888d034-69f3-4c6d-aea2-3a25b2ad7007",
"udi": "umb://element/2d16a0fca4ad4d08ac73a6ccba84beb8",
"overlayPosition": "[\"Bottom\"]",
"overlayColor": "[\"Black\"]",
"image": "[{\"key\":\"3c35c496-37a7-4b71-82ea-cf6be2e16b5a\",\"mediaKey\":\"c0c370c2-5f75-48a0-accf-9f662eca9f07\"}]",
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
},
{
"contentTypeKey": "e888d034-69f3-4c6d-aea2-3a25b2ad7007",
"udi": "umb://element/9349829067e241c4a22a67ab7c653708",
"overlayPosition": "[\"Bottom\"]",
"overlayColor": "[\"Black\"]",
"image": "[{\"key\":\"8aea601d-5586-42e9-83cc-f6c00da39e9c\",\"mediaKey\":\"14451928-8fb2-4851-8d92-ccddc19e6b6a\"}]",
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
}
],
"settingsData": []
}
And here is similar "good" content that works after creating in Umbraco.
{
"layout": {
"Umbraco.BlockGrid": [
{
"contentUdi": "umb://element/ed7ce8f6585649db90ef4e6a5ac15dd6",
"areas": [
{
"key": "26b8bfdf-2cb8-4db1-926d-65b937e2f5ff",
"items": [
{
"contentUdi": "umb://element/2e310cb094a84fc1ae239c9ab4e128f6",
"areas": [],
"columnSpan": 7,
"rowSpan": 1
}
]
},
{
"key": "d829fb4a-3e6c-4acd-ae67-ba24e571ce77",
"items": [
{
"contentUdi": "umb://element/2e1533f349da41c6a40e6fe506209391",
"areas": [],
"columnSpan": 5,
"rowSpan": 1
}
]
}
],
"columnSpan": 12,
"rowSpan": 1
}
]
},
"contentData": [
{
"contentTypeKey": "f3ea7f29-4e2f-4c73-951e-7f8599ffb510",
"udi": "umb://element/ed7ce8f6585649db90ef4e6a5ac15dd6"
},
{
"contentTypeKey": "e888d034-69f3-4c6d-aea2-3a25b2ad7007",
"udi": "umb://element/2e310cb094a84fc1ae239c9ab4e128f6",
"image": "[{\"key\":\"eaa23e21-213f-416e-ab21-d3942a4a6879\",\"mediaKey\":\"7eff7fb9-cbab-4b5a-9d8e-77b368d050b0\"}]",
"overlayPosition": "[\"Bottom\"]",
"overlayColor": "[\"Black\"]",
"text": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>"
},
{
"contentTypeKey": "e888d034-69f3-4c6d-aea2-3a25b2ad7007",
"udi": "umb://element/2e1533f349da41c6a40e6fe506209391",
"image": "[{\"key\":\"7d5ec37d-8a51-49f3-8b61-3072a9396df8\",\"mediaKey\":\"ec690b49-1dbb-450a-a3ca-3bc9aba00fe2\"}]",
"overlayPosition": "[\"Bottom\"]",
"overlayColor": "[\"Black\"]",
"text": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>"
}
],
"settingsData": []
}
I'm attempting to do the same thing and everything looks correct. For the "area" key however, I'm just generating a new Guid which sounds like it may be wrong.
And in typical fashion, as soon as I asked his question the answer popped into my head. For anyone else looking to do similar:
They key value has to match the guid of the area in your block grid data type i.e., the guid of the area shown in the example below:
I'm not sure if there's an easier way to get the guid but I created a unique name for this area e.g., migrated_area_0 and searched for this in the database table dbo.umbracoDataType. The JSON stored will have the key in it (606689e1-2a06-4a8c-9656-2fd2fe61d063 in the below example`).
the key for the area needs to be the one from your layout, i got it with inspect element in the backend:
(this is what i was hoping i could find programatically but in the end hard coding was fine)
Umbraco 11 - Creating a Block Grid programmatically - Areas query
Hi All, I'm in the process of migrating some very large U7 sites into v11 (18k+ articles on this one) . I've taken the approach of starting from scratch, and exporting my old content as XML and importing it into a clean v11 install with some custom import scripts.
Generally it's going very well and i'm making excellent progress, which is lovely.
I've got to the stage where I'm mapping my old DTGE content into the new Block Grid data type. I've managed to get everything from here working nicely:
https://docs.umbraco.com/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-grid-editor#creating-a-block-grid-programmatically
However in my v11 setup I'd like to use areas going forwards, and force all my blocks to be inside layout areas (simply 6-6 and 12. However the documentation above includes this:
JsonProperty("areas")] // areas are omitted from this sample for abbreviation public object[] Areas { get; } = { };
Clearly this is what I need to implement, rather than adding my data to the root grid editor I need to add them to areas, but Im struggling to find any examples of this being done. Any pointers?
The layout is as follows:
So I'm looking to get my blocks inside those areas on import:
Currently they are just getting added into the root of the editor and outside of the areas.
Does that make sense? Many thanks in advance.
I've managed to figure most of this out by inspecting the JSON in the database. The main issue I have remaining is that each area requires a key and it looks like that key is specific to the block grids layout block area configuration. I can't however see how I would find that key programatically.
For now i've hard coded the area keys into the import tool, so it works and puts the blocks inside the areas now.
As you can see beloe the blocks are correctly added to the full width area.
Hi, we have been dealing with this as well. This is the way I get the data now. (we are still testing/refactoring, so the code below will be ugly/hardcoded)
With this approach you get an object in which you can get the keys for the areas.
Hi Al -- I'm looking at doing something very similar; any chance you could share the code for your import tool?
The above is very much cut down, and specifically the grid to block grid importing. Hopefully it might give you something to start off with. Our process now is: generate XML from the existing v7 site. On the new v11 site, get that xml, map it to models and create or update content items. The above section deals specfically with the block grid import. It's heavily stripped down. We have a bunch of additional DTGE mappings and other grid fields that are also migrated and inserted appropriately. We then have Hangfire running on the v11 site so we we can re run the imports as the code is amended. It's working pretty well at this point.
I don't see some of these classes. Maybe they were renamed?
This was the article that I used as a starter:
https://codeshare.co.uk/blog/how-i-migrated-my-umbraco-v7-site-to-v8-part-2-content/ (it's to v8 but was a helpful starting point)
Excellent! Thank you so much.
I'm trying to do the exact same thing, did someone work out how to find the key?
This bit seems to be helping me:
I'm so frustratingly close. But after attempting an import, I get empty areas like this:
The frustrating thing is, the JSON for the property looks identical (to me) to when I create actual content in the Umbraco backoffice.
Here's the "bad" property with the missing area content:
And here is similar "good" content that works after creating in Umbraco.
I'm stumped why one works and the other doesn't.
I figured it out! Pays to read the spec...
The key for the areas should be the same as the key in the config. I was creating a new one. Whoops.
Hi
Trying to get the key as well I am not quite sure what you mean the same as in the config what config are you referring to
Thanks for any help
Any chance you ever figured this out?
I'm attempting to do the same thing and everything looks correct. For the "area" key however, I'm just generating a new Guid which sounds like it may be wrong.
Any help would be hugely appreciated!
And in typical fashion, as soon as I asked his question the answer popped into my head. For anyone else looking to do similar:
They
key
value has to match theguid
of the area in your block grid data type i.e., the guid of the area shown in the example below:I'm not sure if there's an easier way to get the guid but I created a unique name for this area e.g.,
migrated_area_0
and searched for this in the database tabledbo.umbracoDataType
. The JSON stored will have the key in it (606689e1-2a06-4a8c-9656-2fd2fe61d063
in the below example`).Hope that's clear, took me a while to figure this out!
I'm about to log off but if you share what you have / where you are stuck I might well be able to point you in the right direction.
the key for the area needs to be the one from your layout, i got it with inspect element in the backend: (this is what i was hoping i could find programatically but in the end hard coding was fine)
Thanks for this not sure now long it would have taken to figure out this was the exact thing I needed
is working on a reply...