In this tutorial, you will learn about some view model patterns that you can use with MVC and Episerver CMS. Recently an Episerver newbie, asked me what was the benefit of using a view model in Episerver, especially when we have the page-type object. In this tutorial, I will attempt to answer that question, I will talk about a few patterns and share some code examples so you can implement the patterns yourself. Sound good, read on 🔥🔥🔥
Let us start with a quick MVC overview recap. In MVC you have a
Viewwhich contains your HTML (CSS, js, etc..) in a .
cshtml file. A
Model which in Episervers case could be a page type or block type and a
Controller. The controller is the area to get the data required by the view. The controller is used to define the rules of how the page should work. These rules could include the type of model that will be passed to the view, the rules on who can access it, how the view should be cached, etc...
What is the point of the ViewModel?
In Episerver MVC, we also have the page-type and block-type objects that get passed into our controller. These objects could be thought of and could be used as the
M, e.g. the
Model. Should they?
One definition of what the ViewModel should be used for is a filter between the properties defined in the pages and blocks properties and what is exposed to the front-end view. For Episerver that means never exposing the Page Type of Block Type definition directly but adding properties for the things that will be displayed. In my opinion, this just seems like a lot of boilerplate code to write, especially if you don't have anything security-wise to warrant all the effort. So if we're not going to use this definition of what a view model is... what are we?
One definition of what the view model should be used for in Episerver is a filter between the properties defined in the CMS pages and blocks and the data that needs to be exposed in the front-end view. In this pattern, you never expose the page or block type object directly in the view. Instead, you use a view model as an intermediary. Mapping only the properties from the Episerver object into the view model that will be displayed. In my opinion, this pattern tends to result in lots of boilerplate code needing to be created and maintained. One use case for doing this would be security. Not exposing properties that are not used. In 20 years of doing this, I've never seen a security issue where extra data has been exploited in the view model by a hacker. If someone could access your server and update your views, you have a bigger problem! Another use-case could be performance, however, we are talking about a few extra properties which may add 0.0001ms extra load time onto a request. If the page is cached then this is another non-reason in my opinion. A more valid use case is less test code to write. This is valid, however, if you passed the Episerver object directly into a view, you wouldn't need to test the mapping code in the first place. So if we're not going to use this definition to justify the existence of a view model... what justification are we going to use?
The Base View Model
One reason to use a view model is to reduce code. If you use a base view model for pages and blocks, you can get extra data in the view without having to write extra code. An example of what that base view model class would look like is shown below:
The code required to implement a view model using this base class could look like this:
The code to return the view model from a controller would then like this:
Following this pattern, you have access to the underlying page and block properties within your view. You can have access to some useful shared data. It is also possible to add extra data that it not exposed within the Episerver object into your view. This is really handy. Remember, all logic should be processed in the controller to follow the MVC paradigm. If you pass the Episrver object directly. You will likely need to start adding logic into the view as you have no other way to pass front-end related data into the views. So apart from forwarding on the page or block property, what extra data should go in there?
1. Friendly Url/Link Resolving: In a lot of pages, we need to link to other internal pages within the website, however, when we render a pages
LinkUrl property it renders out the Episerver internal Url which is useless for your SEO. The controller is the perfect place to do this logic and the view model is the perfect place to add this extra data:
I haven't included the code for the link resolver in this article, however, if you want to learn more have a look at my Dependencies article.
2. CSS Logic: On most pages, you will require some ternary logic to determine which class or element to display on a page. A lot of developers tend to add this logic into the view. This approach is not ideal, especially if you use Episerver Display options. When using display options and adding logic into your view, it is likely you will need to duplicate the same logic in each variation. This is why adding the logic in the view controller or view model is a better approach. Define it once, use multiple times 💥
This snippet will render a "none" class if the user has enabled it, otherwise, nothing will be displayed and the heading will display. If you have multiple views per control this will reduce duplicate code in each view. Doing this also allows you to unit test that the right class is displayed in the right situation.
This snippet will render a
none class if the content editor has disabled the element in the CMS, otherwise, the heading will display. If you have multiple views per controller adding the logic will reduce duplicate code in each view. Doing this also allows you to unit test this logic 😊
3. Concatenating Elements
Let us say you want to render three different properties,
Surname as a single line in your view. Within the view model, you can create a
FullName method to save you having the concatenate the code in a view:
4. Image Alt Tags: To have good SEO on your site, you need to ensure all your images have
Alt tags. Depending on how you do the CMS content modelling, you may want to use the view model to ensure there's an alt tag for every image exposed. If you do not force content editors to add
Alt tag data and they omit the data, what do you do in the view? Using a view model, you can expose an additional
Alt tag property in your view model that uses the page title as a fallback if the
Alt tag is empty. This way in your view you know you will always have some data. My personal preference is to use a local block for these situations, however, this pattern can be very handy in some situations 💥
5. Rendering Custom HTML Elements You may also want to render your own custom
MvcHtmlString tags in your views. Instead of hardcoding these items in your views, it is better to expose them via the view model. For example, the following code will render out a different heading tag based on a selection factory:
6. Filtering Content From AContent Area: When you have a block (like a carousel), you will likely have a number of child blocks (carousel items) defined by the parent. Instead of using
PropertyFor() to render the data. In a controller, you can iterate through the content areas
Items collection manually to give yourself a more fine-grained level of the controller over the final output. If you do this, you may additionally want to wrap the data in its own view model to provide a better abstraction:
I hope you now get the point of having a view model in Episerver CMS. It's the place to keep code that isn't part of a page or block type definition that is required by the view. Following this approach means you should keep your Episerver views free of code, maintaining a good layer between presentation and business logic. Happy Coding 🤘