Adding Custom Data to Block Grid Model Without Changing @inherits Directive

I have created indexing for block grid items so they are automatically numbered based on various user choices.

I am currently passing the values from items.cshtml to other view using viewdata.
I would like a more robust method.

I have investigated consuming the block grid model into a new view and injecting a new model into my current views but neither seems a good option and over complicated.

AI is suggesting:

1. Extend the Block Grid Model

By creating a custom model that inherits from BlockGridItem, you can add your custom properties while keeping the existing inheritance structure intact. This approach doesn’t require changing the @inherits directive in your views.

public class CustomBlockGridItem : BlockGridItem
{
    public int Index { get; set; }
    // Add other custom properties here
}

2. Use Composition

Creating a new class that contains the BlockGridItem and your custom properties allows you to keep the existing inheritance structure. You can pass this composed model to your views without changing the @inherits directive.

public class CustomBlockGridItem
{
    public BlockGridItem BlockGridItem { get; set; }
    public int Index { get; set; }
    // Add other custom properties here
}

3. Use a Custom ViewModel

Creating a custom ViewModel that includes the BlockGridItem and your custom properties allows you to pass the ViewModel to your views without changing the @inherits directive.

public class CustomBlockGridViewModel
{
    public BlockGridItem BlockGridItem { get; set; }
    public int Index { get; set; }
    // Add other custom properties here
}

Thoughts please?

Hey Dean,

The thing to be aware of with each of these is the scope and lifespan of the object you are working with.

For example a page model created due to a request from visitor A could also be served to visitor B from a cache. If logic was added to inject user data into the model for visitor A that could leak over to the content visitor B sees.

Altering the models for pages with visitor specific data has created problems before, so I would advise caution and some thorough testing with blocks too.

Harry Gordon, wrote a really helpful article on this, which would be worth checking out!

Thanks @mttblss - I actually read Harry’s article at the time and found it very interesting.
In my case the data I am generating is intended for all Backoffice and front end users.
I am simply numbering block items either all in an area or by type in an area e.g. Figure 1. Image 1 etc…

1 Like

I see Dean, so adding to the model would be safe in this case…

Now there’s a danger I’ve misunderstood again mate, but here goes anyway:

If you’re just applying incremental numbers to items like figures, could you render them all the same in the back end and use CSS counters? The figure captions on this page are done that way

Here’s the CSS, it saves a whole raft of back end pre-processing:

html {
  counter-reset: fig;
}

figcaption {
  &:before {
    content: "Figure\00a0" counter(fig) ".";
    counter-increment: fig;
  }
}

…assuming of course that is what you are wanting to do!

Thanks Matt, CSS counters is where I started and yes they are really great.
But I then began to add more conditional logic in the c#.
It could be used to add classes or data-attributes to control CSS counters of course but this felt like duplication.
Also, I find myself needing to pass other block grid variables to views which I have usually done with viewData.
Over the weekend I got it working using a viewmodel and defaultcontroller but wasn’t entirely happy.
I’m have an idea to add an empty property to the model using the backoffofice (it could also be used by the user as a custom value) but I’d still be interested in learning the best way to add values to models.

1 Like

I’ve always tried to find another way of handling this type of problem if at all possible. As you said in the original post, each of the possible approaches feels over complicated.

I don’t know there is a “right” answer, Once any performance issues had been ruled out I would fall back to the question which will be easiest to maintain

  • If I come back to this in a year which approach would I pick up quickest?
  • If someone else picked up the code which would be most standard and obvious to understand?

Completely agree @mttblss.

So a few things I considered related to your points:

  1. The team are C# devs
  2. They are already using C# for a block counter so I used the same technique
  3. They are using a CSS framework so I’m trying not to add custom CSS

Also passing values to CSS can get complex e.g. Custom prefixes.

However, this is about to get a lot easier:
una.im | New capabilities for attr()

Thing is I have it working fine with viewData but it does not preview in Block Preview which I think is an issue so I’ll keep looking.

1 Like

Sorry Dean, I don’t think I’ve helped here mate. I hope you find a solution that fits the requirements!

CSS attr sounds interesting: I’m loving that increasingly more can be done with pure CSS, no JS and a clean DOM model :+1:t2:

I’m very grateful you responded @mttblss.
I’ll post back how I get on.

1 Like

Just chiming in that we use ViewComponents for a lot of blockgrid models - and as such have made a little helper class we can extend

public class BlockGridViewComponent<T> : ViewComponent
{
    internal readonly IUmbracoContextAccessor _umbracoContextAccessor;
    public IPublishedValueFallback Fallback { get; set; }
    public IUmbracoContext UmbracoContext { get; set; }

    public readonly string ViewPath;
    public BlockGridViewComponent(IUmbracoContextAccessor umbracoContextAccessor, IPublishedValueFallback publishedValueFallback)
    {
        _umbracoContextAccessor = umbracoContextAccessor;
        _umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext);

        UmbracoContext = umbracoContext!;
        Fallback = publishedValueFallback;
        ViewPath = $"~/Views/Partials/blockgrid/Components/{typeof(T).Name}.cshtml";

    }
}

We then just put in a viewcomponent like so, when we need to extend functionality somehow.

public class RelatedEventsGridModuleViewComponent : BlockGridViewComponent<RelatedEventsGridModule>
{
    private readonly IIndexManager _indexManager;
    private readonly ILogger _logger;
    public RelatedEventsGridModuleViewComponent(ILogger<RelatedEventsGridModuleViewComponent> logger, IUmbracoContextAccessor umbracoContextAccessor, IIndexManager indexManager, IPublishedValueFallback publishedValueFallback) : base(umbracoContextAccessor, publishedValueFallback)
    {
        _indexManager = indexManager;
        _logger = logger;
    }

    public IViewComponentResult Invoke(BlockGridItem<RelatedEventsGridModule> model)
    {
    ....
        return View(ViewPath, model);
   }

In our cshtml for the grid, we then “taste” the View to see if it has an component and then either invokes the ViewComponent or the View

var componentName = block.Content.ContentType.Alias.CapitalizeFirstLetter();
var isComponentAvailable = ViewComponentSelector.SelectComponent(componentName) != null;

    if (isComponentAvailable)
    @await Component.InvokeAsync(block.Content.ContentType.Alias.CapitalizeFirstLetter(), block)
    else await Html.RenderPartialAsync(partialViewName, block);

I know we are just looking to add an index here, but maybe it will be relevant for people who are looking to add -more- stuff to their model or logic to their blocks before they are rendered.

Also final note - my Knowit.Umbraco.InstantBlockPreview supports previewing ViewComponents :wink:

1 Like

Thank you @kasparboelkjeldsen this looks very interesting.

I know we are just looking to add an index here

It’s already become quite a configurable index and I have a lot of logic going on in the blocks that should probably be elsewhere so this is great.

Knowit.Umbraco.InstantBlockPreview

Good to know.