Strongly typed models in the Umbraco Grid

When Umbraco introduced the grid layout in Umbraco 7.2, and we (my colleagues and I at Skybrud.dk) started using the grid layout in some of our solutions, we quickly ran into some issues:

DynamicsSince all of our developers working directly with Umbraco have a Visual Studio solution, we strive to always use a strongly typed approach (typed models) rather than using dynamics. However, the default Umbraco examples (eg. the Fanoe Starter kit) on how to render the grid all uses dynamics.

Searching the gridThe value of a property with the Umbraco grid layout is saved as raw JSON (both in the database and in Examine). While this JSON is technically searchable, it's typically far from ideal to search through raw JSON using Examine.

As a result of these issues, my colleague tasked me with finding a solution for these, and thus Skybrud.Umbraco.GridData was born.

When you install a new instance of Umbraco, it comes with support for a number of grid editors by default. Skybrud.Umbraco.GridData provides strongly typed models for these, and this article will show how you can use these in your own projects. The article will also show how you can use Skybrud.Umbraco.GridData to provide strongly typed models for your own grid editors.

The Fanoe starter kit will be used for comparison. Also, the article is based on the newly released v2.0 of the Skybrud.Umbraco.GridData package. Most of the examples also apply to v1.5, but there are some differences.

To help with understanding the examples used throughout this article, I have also created an Umbraco sample solution - which amongst other things features a custom grid editor as well as a custom Examine indexer.

You can find the sample solution in this GitHub repository. The username and password for Umbraco are skrift and skrift1234 respectively. Just open the solution, build and then and press F5 ;)

Let's get to the basics

In default Umbraco, if you try to get the value of a grid property, the type will be an instance of JObject (representing the root JSON object of the grid value), which is great for working dynamics, but doesn't really give you a strongly typed model.

By installing our GridData package, the property value will instead be an instance of the GridDataModel class (don't worry - you can still use dynamics if you wish). So rather than:

This extension method will make sure that you always get an instance of GridDataModel, and handle all the necessary null checking for you. Even if the property doesn't exist or the property isn't based on a grid layout, you will still get an instance of GridDataModel.

Getting the value of a control

With reference to the ~/Views/Partials/Grid/Elements/Base.cshtml partial view from the Fanoe starter kit, you have a reference to the control as dynamic (it's really an instance of JObject, but still referred to as a dynamic). To get the value of the control, you can simply write something like:

@inherits UmbracoViewPage<dynamic>
@{
dynamic value = Model.value;
}

This is quite simple, but you also don't have any intellisense. With the GridData package and a strongly typed approach, the model of your partial view would instead be an instance of GridControl. With this, you can get the value like:

Here IGridControlValue is an interface used to represent the value of a grid control in a generic way. If we know the type of the control (eg. the alias of the parent grid editor), we can also get the value in another way:

However you should be aware that if you have a grid editor with an alias not supported by default, Model.Value will simply return null. This is because the GridData package doesn't know how to parse the value for a control using that editor. You can read more about handling this a bit later in this article.

Getting the configuration of an editor

In a similar way, you would refer to the configuration of a grid editor using dynamics like:

If you're using Visual Studio, you'll now have full intellisense for the various properties and methods:

Also notice that even though we now have a strongly typed model, the GridDataModel class also exposes a section property (notice the lowercase s) so you can continue to use dynamics when and where you like.

The GridDataModel class and related classes support checking whether the entire model, section, row, area or control is valid.

Eg. the view for rendering the grid could look like below. If the grid model isn't valid (the entire grid is considered valid if it has at least one valid control), we stop any further rendering.

Rendering the grid

In default Umbraco, if you have to render the Grid, you're probably doing something like:

@CurrentPage.GetGridHtml("content", "fanoe")

In this example, content refers to the alias of the property holding the grid value, and fanoe is the template/framework used for the further rendering the grid, using the partial view located at ~/Views/Partials/Grid/Fanoe.cshtml.

With the GridData package, you can instead render the grid like:

@Html.GetTypedGridHtml(Model.Content, "content", "fanoe")

Internally this method will get the grid value as an instance of GridDataModel, which then will be provided as the model for the partial view located at ~/Views/Partials/TypedGrid/Fanoe.cshtml (notice that the folder name is now TypedGrid rather than just Grid).

Rendering a grid control

Grid controls are a little more complex to render. If we were to render a text-based grid control, we could pass a GridControl instance as model to a partial view, which could then look like:

However if we for each partial view for a grid control were to get the value and the configuration, this would quickly seem to be a bit redundant. So for this purpose, the GridData package also features a GridControlWrapper class - which as the name suggests - is a wrapper for an instance of GridControl.

To get a wrapper instance for a text-based grid control, we could do something like (the first type parameter is the concrete type of the control value, while the second type parameter is the concrete type of the editor configuration):

The first type parameter is the concrete type of the control value - in this case the GridControlTextValue class. The second type parameter is the concrete type of the editor configuration - in this case the GridEditorTextConfig. With the control wrapper used as the model for the partial view, it could instead look like:

Since the GridData package - through the added converters - knows about the type of the concrete type of the control value and the concrete type of the editor config, you can also just do something like:

Both methods here does exactly the same, so just pick the one that you prefer ;)

Extending the grid

Like I mentioned earlier, this package will only provide strongly typed models for the grid controls that comes with Umbraco by default (as well as the grid editors from the Fanoe starter kit). For these grid controls, you can get the value as:

IGridControlValuevalue = control.Value;

However if you have some custom grid controls, the control.Value property will simply return null because the GridData package doesn't know how to parse the JSON of the control values into strongly typed models. In a similar way, the control.Editor.Config property will also be null (it may also just be null because the editor doesn't have a configuration).

So to make these properties return strongly typed models, we need to use the IGridConverter interface. The grid package let's you add your own converter (or more than one if you'd like). As an example, the grid package comes with two converters by default - UmbracoGridConverter and FanoeGridConverter - which is why we already have strongly typed models for the default grid controls. You can have a look at these two converters for inspiration ;)

Anyways, the IGridConverter interface describes three methods, which I will try to describe with an example below.

My example is a grid editor for entering the details about one or more contact persons, where editors (the human kind logging into your backoffice) can specify the name, job title and email address of each contact person.

The examples below for the three methods comes from the same SkriftGridConverter class, which you can find in the sample solution.

ConvertControlValue

The ConvertControlValue method is used for converting the value of the grid controls.

The first parameter is an instance of the GridControl class - you can use this instance to check whether your converter actually should convert anything (whether it is a control that your converter supports).

The second parameter is an instance of JToken. The JToken class comes with the Newtonsoft.Json framework, and is a base class used to representing an arbitrary JSON value. If the control value is a JSON object, the parameter will really be an instance of JObject (which inherits from JToken), and the parameter will be an instance of JArray if the control value is a JSON array.

The third parameter is actually an output parameter. This means that if your converter supports a given control, you can pass the strongly typed model for the value back through this parameter.

The return type of the method is a boolean, which is used to indicate whether your converter supported the control. If it did, the grid package will skip checking with any further converters.

So for our example grid editor, the implementation of this method could look like:

In this example, the SkriftGridEditorContactPersonsConfig class is used to represent the JSON of the grid editor configuration. Some grid editors doesn't have a configuration, so in that case you can simply skip the switch statement.

GetControlWrapper

In order to support the strongly typed models of the partial views for each grid editor, the grid package features a GridControlWrapper class.

If you see the example below, a new wrapper will be initialized for our example grid editor. The generics make sure that if the wrapper instance is passed as model to a partial view, we can use Model.Value for the strongly typed model of the control value, and Model.Config for the strongly typed model of the editor configuration.

Implementing the model for a control value

In general, the values of grid controls should implement the IGridControlValue interface. This interface describes two properties and a single method:

ControlSince an instance of IGridControlValue represents the value of a specific grid control, the Control property is used for keeping a reference back to that control (which is an instance of the GridControl class).

IsValidThis property should return a boolean value whether the control and its value should be considered valid. Eg. if a control value represents a list of some kind, IsValid could return false if the list is empty since it then wouldn't make sense to render the control in the frontend.

GetSearchableTextTo make Examine indexing and searching easier, each instance of IGridControlValue should be able to provide a textual representation of the value. For our example grid editor, this could be the name and job title of each contact person.

While both the IsValid property and the GetSearchableText method can be useful, there are also use cases where you don't really need them. So rather than implementing the IGridControlValue interface, you can also inherit from the GridControlValueBase class. This is an abstract class that implements the interface for you, so you don't have to implement the interface, but instead let's you overwrite the parts that you need to change.

Indexing the grid in Examine

When having a property using the Umbraco grid layout, the property value is saved as raw JSON to the database, and also indexed in Examine as raw JSON. This JSON is technically searchable, but might also contain phrases that you don't want to be searchable - eg. the JSON property names.

Since we now have a strongly typed model for the entire grid value, we can also use this for Examine indexing. If you saw the IGridControlValue interface earlier, you probably also saw the GetSearchableText method.

With this method, each control can return the exact text that should be indexed for that control. Eg. GetSearchableTextmethod in the GridControlHtmlValue class strips all HTML elements, but still returns the inner text of said HTML elements. Similarly the GridDataModel class also has a GetSearchableText method, which is used to sum up the entire searchable text that should be indexed in Examine.

So an indexer for the grid could look like the example below (the SkriftGridExamineIndexer class can also be found in the sample solution). Also, remember to register the indexer with Umbraco during startup.

While this approach is great at minimizing your code, it's also very generic. There might be situations where you might need to customize the indexing a bit further.

If this is the case, you can iterate over the controls manually, and then concatenate the searchable string your self. You can see this GIST for further information (I tend to not see replies in my Gists, so feel free to create an issue or contact me on Twitter instead if you have any questions).

Using the GridData package in your own packages

If you have created your own grid editor and released it as a package, you can also provide strongly typed models for your package using the GridData package.

At Skybrud.dk we have a few packages that does this - you can find them through the links below:

Documentation

Good documentation is always a good starting point for understanding something - but good documentation also takes time to write.

So for the time being, the Skybrud.Umbraco.GridData documentation mostly contains the same examples as used in this article, but if you get stuck in the future, have a look at the documentation, since it hopefully has improved then.

Otherwise if you have any questions, feel free to post them in the comments below this article or create a new thread on Our Umbraco ;)

About the Author

Anders Bjerner is a System Developer at Skybrud.dk (an Umbraco Gold Partner located in Vejle, Denmark) with a background in Computer Science, and has been working with Umbraco since 2011. His work at Skybrud.dk typically consists of implementing Umbraco solutions for various government and private clients as well as developing custom packages (where a number of them can be found on Our Umbraco and NuGet). When not working and playing around with Umbraco, he can be found on his bike exploring the hills around his hometown (some of the steepest/highest in an otherwise flat Denmark).