When building websites, we usually layout our pages based on a bunch of different blocks. Some call them components, others call them segments, widgets etc. but they are basically just different names for the same thing: An section of a page with isolated content that we want to be able to iterate through and render specific markup for. At Creuna we call them Blocks, so I’ll be using this term for the rest of this post.
Nested Content
Umbraco has a built in tool that’s perfect for building blocks called Nested Content. It enables editors to create and nest blocks in several levels, without ever having to leave the current page they are editing. As I've mentioned in many previous blogposts, I love Nested Content. It’s probably one of my top 5 favorite features in Umbraco. I once said that I believe Nested Content has made my life at least 3% better, and I still believe that to be a somewhat accurate number. It’s super easy to work with and all the editors loves it since it looks so simple and straightforward.
Nested Content has made my life at least 3% better.
It’s also friendly to work with from a developers perspective, since Nested Content maps to IPublishedContent, which we’re already familiar with in Umbraco, so we don’t have to learn any new types or concepts to start working with Nested Content. I’m not going to be covering how to set up Nested Content from within Umbraco, since there is already great documentation available: Nested Content Documentation
Although this tutorial focuses on rendering Nested Content items, since it maps to IPublishedContent there's no stopping you from using this same approach for rendering node blocks (document types created as child nodes of a page or in component library with content pickers) if this is how you've configured your setup. Obviously you can build blocks in several other ways (The Grid for example) and there's a great tutorial here on Skrift.io that covers the various options available: Building with Components
Strongly typed view models
I prefer to render my blocks the same way I render my pages, through specific controllers for each specific type of content and mapped to strongly typed view models. It makes unit testing a lot easier, my views a lot smaller and with less logic, which makes my codebase a lot more enjoyable to maintain.
This strategy follows the MVC pattern which, among many other advantages, promotes decoupled and reusable components. By having our blocks routed through specific controllers, we can isolate its behavior and make sure that a specific type of content always renders in a certain way no matter the context. If we need to tweak the behavior of a certain type, or if we find a faulty behavior and need to troubleshoot our block, we always know where to start looking.
This post is based on a post I wrote on my LinkedIn around Christmas 2018, about how to use this same strategy with Route Hijacking to create strongly typed models and page rendering. It covers how to setup Dependency Injection in Umbraco, Unit Testing with NUnit and how to install UmbracoMapper for mapping your own view models. A lot of these concepts are also used in this post today and I’m not going to be covering them all again, so if you're not familiar with these concepts I suggest you read my other post: How to install Umbraco with Unit Testing, Dependency Injection, Custom Mappings and Route Hijacking.
By having our blocks routed through specific controllers, we can isolate its behavior and make sure that a specific type of content always renders in a certain way no matter the context.
Tutorial
In my Umbraco setup, I have a Home Document Type with a Nested Content property configured, called Blocks, that allows a Hero Document Type which has two properties, Heading and Preamble.
Note: If you prefer to have your blocks as child nodes, then instead of creating a Nested Content property, you allow the Hero Document Type to be a child of your Home Document Type.
1. Create a Block View Model
First we create a view model for our Hero, called HeroViewModel.cs with the same properties as we've configured in Umbraco. Later this view model will be mapped against our Nested Content data using UmbracoMapper.
public class HeroViewModel {
public string Heading { get; set; }
public string Preamble { get; set; }
}
2. Create a Base Controller
We’re going to route all our block items through specific SurfaceControllers for each Document Type, and all of these controllers will need some shared functionality which we'll be placing in a BaseBlockController.cs:
public abstract class BaseBlockController<TModel> : SurfaceController where TModel : class, new() {
private readonly IUmbracoMapper umbracoMapper;
public virtual string ViewPath(IPublishedContent content) => string.Format("/Views/Partials/Blocks/{0}.cshtml", content.DocumentTypeAlias);
protected BaseBlockController(UmbracoContext umbracoContext, IUmbracoMapper umbracoMapper) : base(umbracoContext) {
this.umbracoMapper = umbracoMapper;
}
public virtual ActionResult Index(IPublishedContent content) {
return PartialView(this.ViewPath(content), this.MapModel(content));
}
public virtual TModel MapModel(IPublishedContent content) {
var model = new TModel();
this.umbracoMapper.Map(content, model);
return model;
}
}
Notice that the above code sample requires that you’ve implemented Dependency Injection and registered UmbracoMapper (Tutorial in my previously mentioned post). You’ll also notice that we have a ViewPath declared, where all our block controllers will be looking when trying to find a matching partial view for each Nested Content item. (Obviously you can change the ViewPath to whatever works best for your setup.)
3. Create a Block Controller
Now we can create our actual implementation of our HeroController.cs, inheriting our previously created BaseBlockController.cs and using generics we can pass in our HeroViewModel that we want our IPublishedContent item to be mapped against. If all you want to do is to map each view model property to a matching property value, then this is all the code you'll need for each of your block controllers.
public class HeroController : BaseBlockController<HeroViewModel> {
public HeroController(UmbracoContext umbracoContext, IUmbracoMapper umbracoMapper) : base(umbracoContext, umbracoMapper) { }
}
All the base controller methods are virtual, so you can override each and every one of them to fit each individual need and special scenarios while preserving the base functionality. For instance, if one of your nested content types should be visible to authenticated users only, then the Index method would probably need to be overridden for that specific block controller. Or if you need to add any external data or “pre-bake” any properties before rendering, then the MapModel method would probably be the method to override. Here's an example of how these mentioned examples could look like. (Note that these are not real services I'm using, they are just there to prove a point.)
public class HeroController : BaseBlockController<HeroViewModel> {
private readonly IAuthenticationService authenticationService;
private readonly IExampleService exampleService;
public HeroController(UmbracoContext umbracoContext, IUmbracoMapper umbracoMapper, IAuthenticationService authenticationService, IExampleService exampleService) : base(umbracoContext, umbracoMapper) {
this.authenticationService = authenticationService;
this.exampleService = exampleService;
}
public override ActionResult Index(IPublishedContent content) {
if (this.authenticationService.UserIsLoggedIn() == false) return null;
return base.Index(content);
}
public override HeroViewModel MapModel(IPublishedContent content) {
var viewModel = base.MapModel(content);
viewModel.ExternalData = this.exampleService.GetExternalData();
return viewModel;
}
}
4. Create a HtmlHelper Extension
This helper will make sure that all the Nested Content items finds a suitable controller by iterating over an IEnumerable of IPublishedContent and try to route each item to a controller Index action based on its Document Type alias.
public static class HtmlHelperExtensions {
public static IHtmlString Render(this HtmlHelper helper, IEnumerable<IPublishedContent> items) {
if (items == null) return null;
var htmlString = items.Aggregate(string.Empty, (current, content) => current + helper.Render(content)?.ToHtmlString());
return MvcHtmlString.Create(htmlString);
}
public static IHtmlString Render(this HtmlHelper helper, IPublishedContent content) {
return content == null ? null : helper.Action("Index", content.DocumentTypeAlias, new { content });
}
}
5. Rendering Blocks
Now we can render our blocks in our page template using our new html helper extension. In this example I’ve already mapped my @Model.Blocks to an IEnumerable of IPublishedContent in my page controller, since I'm using Route Hijacking & UmbracoMapper for all my page rendering, but you can use any way you prefer to fetch your Nested Content data, since it maps to IEnumerable of IPublishedContent either way thanks to the built in value converter. More documentation on how to fetch Nested Content data.
@using Typed.Core.Features.Shared.Extensions
@inherits UmbracoViewPage<Typed.Core.Features.Home.HomeViewModel>
@{
Layout = "Master.cshtml";
}
@Html.Render(Model.Blocks)
Rendering Blocks when using ModelsBuilder
If you're using ModelsBuilder to build your page models, your model mapping will be taken care of for you automagically. ModelsBuilder will most likely generate a content model for you that looks something like this (I'm using using DLL mode in this example):
namespace Umbraco.Web.PublishedContentModels {
[PublishedContentModel("home")]
public class Home : PublishedContentModel {
public const string ModelTypeAlias = "home";
public Home(IPublishedContent content);
[ImplementPropertyType("blocks")]
public IEnumerable<IPublishedContent> Blocks { get; }
}
}
As you can see, ModelsBuilder will automatically map your Nested Content property to an IEnumerable of IPublishedContent, so all you have to do is to render your blocks using your previously created html helper extension. Your template would then probably look something like this.
@inherits UmbracoTemplatePage<Home>
@using Typed.Core.Features.Shared.Extensions
@using ContentModels = Umbraco.Web.PublishedContentModels;
@{
Layout = null;
}
@Html.Render(Model.Content.Blocks)
You can use this same helper for rendering even if you've created your blocks as child nodes or if you have a content picker that you select blocks from a library, since it accepts an IEnumerable of IPublishedContent. Query your Model.Content.Children items to fetch the blocks you would like to render or if you've gone with the content picker approach, use the built-in value converter Model.Content.GetPropertyValue<IEnumerable<IPublishedContent>>("yourContentPickerAlias") and then render your blocks using the same helper method as mentioned above.
6. Create a Block Partial View
The last thing we need to do is create a partial view for our Hero, in the same ViewPath as we've declared in our BaseBlockController.cs. So in this case we create /Views/Partials/Blocks/Hero.cshtml.
@inherits UmbracoViewPage<Typed.Core.Features.Shared.Blocks.Hero.HeroViewModel>
@{
Layout = null;
}
@Html.DisplayFor(x => x.Heading)
@Html.DisplayFor(x => x.Preamble)
I prefer my views to have limited or no functionality at all. The reasons are many, one of them is testability and another is the fact that if something breaks I wont get a compilation error if the faulty functionality is in my views, but instead I'll be greeted with a not so friendly YSOD.
Other than being simple, I prefer my views to be small. I’ll rather be looking at 10 smaller views with no more than 10 lines of code than having to scroll through a 100 line view when I’m about to alter the behavior of my site. By populating my view models in my controllers before serving it to my partial views, I can pre-bake my models in the controller so that when it finally reaches the partial view, the only thing left to do is render the data in suitable html tags.
Bonus step: Unit Testing
As I mentioned earlier, part of the reason I want to render my blocks and pretty much anything on my site through controllers is because it's easier to unit test. In these examples I'm using NUnit, Moq and little helper called ContextMocker to mock all my Umbraco dependencies. There’s a great article here on Skrift.io on how to setup Unit Testing in Umbraco that I’ve followed when implementing these tests, which is a must read for anyone interested in both Unit Testing and Umbraco: Unit Testing Umbraco with Umbraco Context Mock. Here's an example of how this could look:
[TestFixture]
public class HeroControllerTests {
private IUmbracoMapper mapper;
private Mock<IPublishedContent> content;
private UmbracoContext context;
private HeroController controller;
[SetUp]
protected virtual void SetUp() {
this.mapper = new UmbracoMapper();
this.context = new ContextMocker().UmbracoContextMock;
this.content = new Mock<IPublishedContent>();
this.controller = new HeroController(this.context, this.mapper);
}
[Test]
[TestCase(null, null)]
[TestCase("A heading", "A heading")]
public void Given_HeroHeadingCases_When_IndexAction_Then_ReturnViewModelWithHeading(string heading, string expected) {
this.content.SetPropertyValue("heading", heading);
var viewModel = (HeroViewModel)((PartialViewResult)this.controller.Index(this.content.Object)).Model;
Assert.AreEqual(viewModel.Heading, expected);
}
[Test]
[TestCase(null, null)]
[TestCase("A preamble", "A preamble")]
public void Given_HeroPreambleCases_When_IndexAction_Then_ReturnViewModelWithPreamble(string preamble, string expected) {
this.content.SetPropertyValue("preamble", preamble);
var viewModel = (HeroViewModel)((PartialViewResult)this.controller.Index(this.content.Object)).Model;
Assert.AreEqual(viewModel.Preamble, expected);
}
}
public static class PublishedContentExtensions {
public static void SetPropertyValue(this Mock<IPublishedContent> content, string propertyAlias, object value, bool recursive = false) {
var property = new Mock<IPublishedProperty>();
property.Setup(x => x.PropertyTypeAlias).Returns(propertyAlias);
property.Setup(x => x.Value).Returns(value);
content.Setup(x => x.GetProperty(propertyAlias, recursive)).Returns(property.Object);
}
}
If the routed IPublishedContent item has a certain property value, my unit tests expects my controller to map this value to a corresponding view model property.
These last two test are just examples of how to unit test our previously overridden behavior for authenticated users and populating our view model with external data.
[TestFixture]
public class HeroControllerTests {
private IUmbracoMapper mapper;
private Mock<IAuthenticationService> authenticationService;
private Mock<IExampleService> exampleService;
private Mock<IPublishedContent> content;
private UmbracoContext context;
private HeroController controller;
[SetUp]
protected virtual void SetUp() {
this.mapper = new UmbracoMapper();
this.context = new ContextMocker().UmbracoContextMock;
this.authenticationService = new Mock<IAuthenticationService>();
this.exampleService = new Mock<IExampleService>();
this.content = new Mock<IPublishedContent>();
this.controller = new HeroController(this.context, this.mapper, this.authenticationService.Object, this.exampleService.Object);
}
[Test]
[TestCase(null, null)]
[TestCase("Any external data", "Any external data")]
public void Given_UserIsLoggedIn_ExternalDataCases_When_IndexAction_Then_ReturnViewModelWithExternalData(string externalData, string expected) {
this.exampleService.Setup(x => x.GetExternalData()).Returns(externalData);
this.authenticationService.Setup(x => x.UserIsLoggedIn()).Returns(true);
var viewModel = (HeroViewModel)((PartialViewResult)this.controller.Index(this.content.Object)).Model;
Assert.AreEqual(viewModel.ExternalData, expected);
}
[Test]
public void Given_UserIsNotLoggedIn_When_IndexAction_Then_ReturnNull() {
this.authenticationService.Setup(x => x.UserIsLoggedIn()).Returns(false);
var viewModel = ((PartialViewResult)this.controller.Index(this.content.Object));
Assert.Null(viewModel);
}
}
By isolating the behavior of our Nested Content types to a controller, we make it a lot easier to write unit tests since we don't have to mock the context of where a block is being used. We can write unit tests with less mocking, and we won't need to write specific unit tests for each page type that will be using our Nested Content types, since we’ve taken it out of its context (unless your Nested Content items implement some behavior that varies depending on its page context, then you would need to write specific unit test for each context.)
It's a lot easier to write unit tests when we don't have to mock the context of where a block is being used.
That's a wrap!
Please note that this is a way (not the way) of working with Umbraco Blocks which I happen to like, because it rhymes well with what I’m used to from my non-Umbraco projects. We all come from different backgrounds and have different setups, so feel free to pick and choose the things you like and whatever works for your setup and ignore the things you don’t like or need.
Over the years of working with Umbraco I learned so much from reading code implemented by other community members on skrift.io and our.umbraco.org, so this time I thought I’d return the favor and share some of my implementation code. I really hope you liked it!
Thank you so much for you time, have a great day and take care of each other. ❤️