Sometimes you want to render a block differently depending on what position it has in a content area. Per Magne Skuseth presented a solution some years ago. Here’s a couple of alternate ways of solving the same issue.
Know the index of a block using a controller
Add a tag (e.g. “IndexInContentArea”) when rendering the content area.
@Html.PropertyFor(x => x.CurrentPage.TestContentArea, new { Tag = "IndexInContentArea" })
Create a controller for blocks that kicks in when using that tag
[TemplateDescriptor(Inherited = true, Tags = new[] { "IndexInContentArea" }, AvailableWithoutTag = false, TemplateTypeCategory = TemplateTypeCategories.MvcPartialController)] public class IndexInContentAreaController : PartialContentController<SiteBlockData>; { public override ActionResult Index(SiteBlockData currentContent) { var contentArea = ControllerContext.ParentActionViewContext.ViewData.Model as ContentArea; ViewData["IndexInContentArea"] = contentArea.FilteredItems.ToList().FindIndex(o => o.ContentGuid == ((IContent)currentContent).ContentGuid); return PartialView($"~/Views/Shared/Blocks/{currentContent.GetOriginalType().Name}.cshtml", currentContent); } }
In the controller you can find the index of the current block. Note that I’m using FilteredItems when getting the blocks in the content area. That makes sure that the blocks gets filtered by status, permission, and personalization.
Now, In the view you can get the block´s index from ViewData[“IndexInContentArea”].
But wait… what if you add the same block twice in the same content area?
If you need to support that scenario replace line 7 in the code block above with these three lines
var key = string.Join("", contentArea.FilteredItems.Select(o => o.ContentGuid)); //a unique key created by concatenating the guids of all blocks in contentArea TempData[key] = TempData[key] == null ? 0 : ((int)TempData[key]) + 1; //save the counter in TempData ViewData["IndexInContentArea"] = TempData[key];
Instead of finding the index of the block, I’m saving a counter in TempData that increases for every block.
Know the index of a block without using a controller
According to Episerver there is a slight performance loss when using controllers for blocks instead of just letting the view engine select the block view. I don’t know the performance impact, and I don’t know how it compares to having code in the view instead, but here’s how to get the index of a block without using a controller.
Create the TempData “counter key” when rendering:
@Html.PropertyFor(x => x.CurrentPage.TestContentArea, new { BlockCounterKey = string.Join(string.Empty, Model.CurrentPage.TestContentArea?.FilteredItems.Select(o => o.ContentGuid) ?? new Guid[]{}) })
In the block view, it is possible to access “BlockCounterKey” in ViewData. Now that we have a unique key we can use that to save the counter in TempData:
var counterKey = (string)ViewData["BlockCounterKey"] ?? string.Empty; TempData[counterKey] = TempData[counterKey] == null ? 0 : ((int)TempData[counterKey]) + 1;
To show the block´s index in the view:
My index is: @TempData[counterKey]
Voilà! 🙂
At time of writing I was using Episerver CMS 10.8 (and C# 6), but I think this can be done on any version above 7.5.
Creds to my talented colleague Philip for some inspiration for this solution.
I think, I’ll just finally commit that change to the lib 😉
https://github.com/valdisiljuconoks/EPiBootstrapArea/issues/4
Many thanks for pushing me 😉
Now, if you are using bootstrap content area – you are able to write:
Index: @Html.BlockIndex()
This is a nice solution. But it works only if they are in the same ContentArea. If you put the same block in a container control which has also a ContentArea and both, the block itself and the container-block in the same main ContentArea,it will generate the same index twice.
Thanks for pointing that out Tim!