When working with MVC, it is best practice to work with strongly-typed Models to separate your presentation logic from your business logic. I've talked previously about how to create models to represent document types, but that still leaves the problem of how do you create strongly-typed models for your websites global properties, like your header and footer.
One option is creating a base controller to create them, another option is creating a base page. The problem with this approach is you're combining your page models with your global models.
A better approach would be to have all the properties and data you need in your layout.cshtml, header and footer partial populated automatically outside of your page/controller. In today's guide, I'm going to go over a lot of codes so be warned! By the end of this guide, we will have created a global view model for our layout file which will be populated with all the data the layout file needs, so you don't have to worry about setting it up in your controllers ever again.
The first part of this process will be implemented using an IResultFilter. A 'result filter' is a custom class we can create, that contains logic that will be executed before and/or after a view is loaded. For example, we can use this to populate a layout ViewModel on any page request for all of our page controllers. To get an IResultFilter to get triggered before a view is rendered, we need to register it in the global.asax. That code will look like this:
If you installed Umbraco via Nuget, then by default your global.ascx might not trigger automatically; I will cover how to set-up your web.config in a future post. The next step is to create the IResult filter:
Let's talk through what this is doing. First, I'm getting the current page request object. Next, I'm checking that the current model implements ILayoutViewModel. I'll cover this part in more detail later, but for now just think of this as a base view model that we will be passing into the Layout.cshtml. If the page request does this, we populate the view model with the header, footer data etc.. and then pass it back to the MVC pipeline to render it in the view.
Base View Model
So for the first part let's define ILayoutViewModel.
What we're saying here is we will create an interface that takes in a RenderModel. RenderModel is an Umbraco model object that defines the basic properties all Umbraco document types/pages will define. We also define a CurrentPage property that will be used to store the current pages data.
Base View Model
After we set this up, you will always have to pass back a view model to your views, otherwise you'll get an object casting issue.
The code for the model is hopefully pretty self-explanatory, it implements the interface and it's properties.
The Layout View Model
The next step is to define a layout view model. This file will be custom to your solution.
In this example, I've created a basic layout view model, that returns a header view model, .
Defining Our Page Objects
Let's say we have a home page template, we would create a HomepageViewModel that inherits from our BaseViewModel and takes in a 'HomeModel'. Remember that 'HomeModel' needs to inherit from RenderModel.
I'm skipping all the page related to model code, so the basic page will look like:
Note, that I'm passing in 'UmbracoContext.Current.PublishedContentRequest.PublishedContent' to the base constructor. I'm using this approach so I can have a parameterless constructor.
The Controller Code
The next step is
In our controller, we create a new strongly typed model for our document type and pass that into the View Model. I then pass the View Model back to the view.
The next steps are defining the layout.cshtml and the home pages .html.
The most import thing to note is the @model. In here we're implementing ILayoutViewModel. This is the interface we define in the IResultFilter. The ILayoutViewModel is also the interface that our Base View Model implements from. Our Home page view looks like this:
We use the HomepageViewModel as the model, define the Layout. On our page, we can then implement any logic we want.
In today's guide, we've covered a lot of code... let's re-cap. By now I'm hoping you get why we want to create a Strongly-type view model for our global page data, like the header and footer. Coupling the page data with this data is a bad approach because not every page might need a header and footer. If you have an e-commerce site, for example, it is very likely that your checkout process won't need the standard header and footer.
If we have to implement the header and footer page on each new document type, we will also be adding a lot of duplicate codes, which is bad. Instead, we want to use an IResultFilter, to intercept the MVC request to render a page, populate the layout view model, add it back to the request so when the 'layout.cshtml' the file is called, we have a layout view model that contains all the data we care about. To do this, we first register the ReultFilter in the Global.asax, next we create the Result filter.
In the 'result filter,' we get the current page object and make sure it inherits from a special interface we will define to store the global data. This interface will be implemented by a base view model, which all our page controller will use.
Next, we defined the base view model, a home page view model that implements the base view model and a home page model. In the home page controller, we create the home page model, pass it into the home page view model, then return it. In 'Layout.cshtml' we use the interface we defined in the IResultFilder '@model SampleSite.Interfaces.ILayoutViewModel' Implementing this means we now have all the data we defined in the Layout View Model as well as access to the current pages model data. With this defined, we can now do whatever HTML we want.
Our business logic is now abstracted from the view, we can use Razor, and we don't have to worry about setting this data anymore. In each controller as long as you pass back a view model that inherits from ILayoutViewModel, the header and footer will be automatically populated for you.