Tuesday, October 4, 2011
ASP.NET MVC 3: Layouts with Razor
Two weeks ago we shipped the ASP.NET MVC 3 Beta Release. It supports “go live” deployments, and includes a bunch of nice improvements/enhancements. You can see a summary of the new ASP.NET MVC 3 features in my beta announcement post. Also read my original ASP.NET MVC 3 Preview post to learn about other ASP.NET MVC 3 features that showed up with that initial preview release.
This is another in a series of “mini-posts” I’m doing that talk about a few of the new ASP.NET MVC 3 Beta features in more detail:
- New @model keyword in Razor (Oct 22nd)
- Layouts (this post)
Razor Basics
ASP.NET MVC 3 ships with a new view-engine option called “Razor” (in addition to continuing to support/enhance the existing .aspx view engine).You can learn more about Razor, why we are introducing it, and the syntax it supports from my Introducing Razor blog post. If you haven’t read that post yet, take a few minutes and read it now (since the rest of this post will assume you have read it). Once you’ve read the Introducing Razor post, also read my ASP.NET MVC 3 Preview post and look over the ASP.NET MVC 3 Razor sample I included in it.
What are Layouts?
You typically want to maintain a consistent look and feel across all of the pages within your web-site/application. ASP.NET 2.0 introduced the concept of “master pages” which helps enable this when using .aspx based pages or templates. Razor also supports this concept with a feature called “layouts” – which allow you to define a common site template, and then inherit its look and feel across all the views/pages on your site.Using Layouts with Razor
In my last blog post I walked through a simple example of how to implement a /Products URL that renders a list of product categories:Below is a simple ProductsController implementation that implements the /Products URL above. It retrieves a list of product categories from a database, and then passes them off to a view file to render an appropriate HTML response back to the browser:
Here is what the Index.cshtml view file (implemented using Razor) looks like:
The above view file does not yet use a layout page – which means that as we add additional URLs and pages to the site we’ll end up duplicating our core site layout in multiple places. Using a layout will allow us to avoid this duplication and make it much easier to manage our site design going forward. Let’s update our sample to use one now.
Refactoring to use a Layout
Razor makes it really easy to start from an existing page and refactor it to use a layout. Let’s do that with our simple sample above. Our first step will be to add a “SiteLayout.cshtml” file to our project under the \Views\Shared folder of our project (which is the default place where common view files/templates go):
SiteLayout.cshtml
We’ll use the SiteLayout.cshtml file to define the common content of our site. Below is an example of what it might look like:
A couple of things to note with the above file:
- It is no longer necessary (as of the ASP.NET MVC 3 Beta) to have an @inherits directive at the top of the file. You can optionally still have one if you want (for example: if you want a custom base class), but it isn’t required. This helps keep the file nice and clean. It also makes it easier to have designers and non-developers work on the file and not get confused about concepts they don’t understand.
- We are calling the @RenderBody() method within the layout file above to indicate where we want the views based on this layout to “fill in” their core content at that location in the HTML.
- We are outputting the “View.Title” property within the <title> element of our <head> section. I’ll discuss how this is used in a bit.
Index.cshtml
Let’s now update our Index.cshtml view to be based on the SiteLayout.cshtml file we just created. Below is an first-cut of what it might look like:
A couple of things to note about the above file:
- We did not need to wrap our main body content within a tag or element – by default Razor will automatically treat the content of Index.cshtml as the “body” section of the layout page. We can optionally define “named sections” if our layout has multiple replaceable regions. But Razor makes the 90% case (where you only have a body section) super clean and simple.
- We are programmatically setting the View.Title value within our Index.cshtml page above. The code within our Index.cshtml file will run before the SiteLayout.cshtml code runs – and so we can write view code that programmatically sets values we want to pass to our layout to render. This is particularly useful for things like setting the page’s title, as well as <meta> elements within the <head> for search engine optimization (SEO).
- At the moment, we are programmatically setting the Layout template to use within our Index.cshtml page. We can do this by setting the Layout property on the View (note: in the first preview this property was called “LayoutPage” – we changed it to just be “Layout” with the ASP.NET MVC 3 Beta). I’ll discuss some alternative ways (new with the ASP.NET MVC 3 Beta) that we can set this property shortly.
Notice above how the returned HTML content is a merge of the SiteLayout.cshtmk and Index.cshtml. The “Product Categories” title at the top is set correctly based on the view, and our dynamic list of categories is populated in the correct place.
DRYing things up with _ViewStart
Currently we are programmatically setting the layout file to use at the top of our Index.cshtml file. This is fine for cases where we have some view-specific logic where the layout file will vary depending on the specific view. But setting it this way can end up being redundant and duplicative for most web applications – where either all of the views use the same layout, or if they have different layouts (for example: for mobile devices or localized sites) the logic for which layout to pick is common across all of the views.The good news is that Razor (starting with the ASP.NET MVC 3 Beta release) includes a new feature that enables us to remove the need to explicitly set the Layout in each view – and instead allows us to define the layout logic once for all views in the site – making our view files even cleaner and more maintainable (and ensuring we keep to the DRY principle: Don’t Repeat Yourself):
Starting with the ASP.NET MVC 3 Beta release, you can now add a file called _ViewStart.cshtml (or _ViewStart.vbhtml for VB) underneath the \Views folder of your project:
The _ViewStart file can be used to define common view code that you want to execute at the start of each View’s rendering. For example, we could write code like below within our _ViewStart.cshtml file to programmatically set the Layout property for each View to be the SiteLayout.cshtml file by default:
Because this code executes at the start of each View, we no longer need to explicitly set the Layout in any of our individual view files (except if we wanted to override the default value above).
Important: Because the _ViewStart.cshtml allows us to write code, we can optionally make our Layout selection logic richer than just a basic property set. For example: we could vary the Layout template that we use depending on what type of device is accessing the site – and have a phone or tablet optimized layout for those devices, and a desktop optimized layout for PCs/Laptops. Or if we were building a CMS system or common shared app that is used across multiple customers we could select different layouts to use depending on the customer (or their role) when accessing the site.
This enables a lot of UI flexibility. It also allows you to more easily write view logic once, and avoid repeating it in multiple places.
Note: You can also specify layouts within a Controller or an Action Filter. So if you prefer to keep the layout selection logic there you can do that as well.
Finished Sample
Below is a screen-shot of the simple application we have been building:Here is the ProductsControllers that implements the /Products URL and retrieves the categories from a database and passes them to a view template to render:
Here is the Index.cshtml view that we are using the render the /Products response:
Here is the SiteLayout.cshtml layout file we are using to implement a consistent look and feel across our site:
Here is the _ViewStart.cshtml file that we are using to specify that all views in our site by default use the SiteLayout.cshtml file:
And here is the generated HTML from the /Products URL:
And because we now have a common layout file for our site, we can build out more functionality, controllers and views within our application - and have a site UI experience that is both consistent and very easy to maintain.
More Advanced Stuff
Two common questions people often ask are:1) Can I have nested layout files?
2) Can I have multiple, non-contiguous, replaceable regions within a layout file – so that I can “fill in” multiple different sections within my view files.
The answer to both of these questions is YES! I’ll blog some samples of how to do this in the future.