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:

In today’s post I’m going to discuss layout pages with Razor, and discuss some of the improvements to them that we introduced with the recent ASP.NET MVC 3 Beta.

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:

image_thumb_1263F129[1]

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:

image_thumb_579C4852[1]

Here is what the Index.cshtml view file (implemented using Razor) looks like:

image_thumb_17A2B20D[1]

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):

image

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:

image

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.

And now we have a common layout template that we can use to maintain a consistent look and feel across any number of pages on our site.

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:

image

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.

And now when we request the /Products URL within our site, we’ll get the following HTML returned:

image

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):

image

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:

image

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:

image

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:

image

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:

image_thumb_579C4852[1]

Here is the Index.cshtml view that we are using the render the /Products response:

image

Here is the SiteLayout.cshtml layout file we are using to implement a consistent look and feel across our site:

image

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:

image

And here is the generated HTML from the /Products URL:

image

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.

Summary

As I’ve mentioned before, one of the themes we’ve focused on with the ASP.NET MVC 3 and Razor releases has been to make the code you write cleaner and more concise.  We think the new layout functionality coming with this release contributes nicely towards making view files even easier to read and write.  I’ll be covering other nice improvements like this that are new to the ASP.NET MVC 3 Beta in future posts.

Hope this helps,

Scott

P.S. In addition to blogging, I am also now using Twitter for quick updates and to share links. Follow me at: twitter.com/scottgu

49 Comments

  • Looks great.

    For legacy sites with webforms and MVC mix, the current view engine allowed us to use the webforms master page as the master page for our MVC views. Is there any chance razor will support something similar and work with existing master pages from the MVC/Webforms engine?

  • Great! Is there any difference between this and MasterPage? I mean, are there any features that Layout has while MasterPage has not?

  • Thanks for another great post, Scott.

  • Forget it. I didn't see the previous blog explaining this... It's still confusing the bejesus out of me, mind.

  • Will we be able to return multiple views in one call in mvc3 final or will that feature be pushed out for mvc4?

    Thanks

  • Hi scott
    First of thanks a lot for keeping us posted on new stuff we love you :).
    I want to understand why are we bringing Layouts when we already have master pages?
    or when should we use master page and when to go for layouts?

  • You're using a doctype from HTML 5 in default view pages. How about replacing semantics of those page according to HTML5 spec as well. instead of , instead of etc.

  • Hi, with Razor, are you able to regions that can optionally be overwritten? As you are aware, in the aspx/masterpage model, you could define a placeholder that had content which was rendered if it wasn't overwritten. However, if you defined a that region in the aspx it would overwrite whatever was defined in the master. This has been very useful for me, and I was wondering if this functionality is possible in Razor?

  • Scott, Is it possible to test the view page in case we use _ViewStart.cshtml ?
    Or just put the layout selection logic in controller to more easily testing the selection layout logic.

  • Hi there, this question may sound a bit stupid but.. will it be possible, some day in the future, to use razor with WebForms?

    I don't want to use MVC because I hate having to create controllers for just about everything.. I prefer the logic to create "pages" when I need multiple pages in my website instead of actions!..

    However, I like the simplicity of razor to generate dynamic content. So I would like to combine it with webForms.

    I hope it makes sense.
    Thanks

    PS: When will Visual Studio support .cshtml and .vbhtml formatting and intellisense? Is it something planned for this year?

  • _Viewstart.cshtml screws up things for if you want to render partial views using Ajax.

  • Have you considered add directive of @layout like @model to further simplify the code? Having to open a code block seems needless.

  • I have upgraded a MVC 2 Website to MVC 3 Website, and have switched over to using Razor and updated my corresponding views as a result. I am running into a problem where my jQuery ready function is not executing. Everything is rendering fine, but none of my javascript code is running? What is going on?

  • Bringing this into VS should be a priority, but unfortunately it is not and obviously Microsoft is targeting at weekend developers with this when it should target and focus on professional developers and teams. And I do agree with Mike that this is going too fast and if launched with this pace the end product will again lack some features that will be exposed at Stack overflow or similar sites in the first few days after the release.
    Actually it would make sense if the ASP.NET MVC team would gather some feedback from the community as the whole and not just from the few exposed personalities because that would make the whole MVC picture much more complete and well thought. As it is now it's nothing more than just releasing new and new features one after the other.

  • Looks great masterpages with there runat="server" always looked out of place in a MVC view. However _ViewStart concerns me looks like a step back to code behind pages and seems to have the potential to break separation of concerns. I haven't made my mind up whether I like _ViewStart or not, think I need more persuading.

  • You have taken the razor to html/code spaghetti of past :-) Love the look of Razor. Just need to find some time to play with it myself.

  • so if I add a _ViewStart.cshtml file under Views/Products will it apply only to the views under Products?

  • This seems nice.
    What about areas and the location of the _ViewStart.cshtml file ?
    Will it pick up the _ViewStart.cshtml file from the View folder (/Areas/SomeArea/Views/...) of an Area automatically and if not defined look in /Views/ (in the root) ?

  • I don't think I've been this excited about web programming since ASP.NET 1.0! As soon as MVC3 Intellisense is released, I'm there... We're so proud of our "Gu-Master" and team :)

  • @Jesse,

    >>>>>>> For legacy sites with webforms and MVC mix, the current view engine allowed us to use the webforms master page as the master page for our MVC views. Is there any chance razor will support something similar and work with existing master pages from the MVC/Webforms engine?

    You can't share master pages between .aspx and razor views I'm afraid. However, you can have both in the same application. You can also use the Html.RenderAction() and Html.RenderPartial() to have Razor based views call .aspx based views and vice-versa.

    Hope this helps,

    Scott

  • @christoph,

    >>>>>> Scott, do you think there's a lot of changes coming until RTM or is it mostly polishing?! The roadmap at codeplex suggests that there are only minor issues left. In any case, great job!

    It is mostly polishing - with a few more features coming. Probably the biggest that people are looking for is intellisense with razor based views inside VS 2010. That will show up with the next public preview.

    Hope this helps,

    Scott

  • @allentranks,

    >>>>>>> Great! Is there any difference between this and MasterPage? I mean, are there any features that Layout has while MasterPage has not?

    This is conceptually the same as a master-page. The core concepts are the same, with some differences in how they were implemented. One nice feature that razor has (which I'll cover in my next post) is a really easy way to create re-usable helpers using a new @helper keyword. I'll cover that soon.

    Hope this helps,

    Scott

  • Scott, thanks for this post! I like the Razor view engine more with every article I read about it ...

  • Thanks Scott, this is great. Razor is so clean.

  • Keep in mind that when you enable Razor you must update both your /Views/Web.config and your /Areas/[Area]/Views/Web.config
    I was getting a lot of guff about @model not being in context

  • Now with Razor it will be easy to work.

  • The cleanest way would be to use .html and recognise that it is a razor page just by the content or a first line directive @language=vb | @language=cs

    I don't know if that is allowed by the W3C, but come on, it is Microsoft.

  • Will we be able to return multiple views in one call in mvc3 final or will that feature be pushed out for mvc4?

    Thanks

  • Off topic, Scott Gu. Is Silverlight for web really dead? Are you going to use it just for Windows Phone 7?

  • Looks good getting started with a new project using the Beta, really looking forward to the rtm.

  • _ViewStart: why not use Controller name as layout name by default, and have some default layout name ( e.q. Application in SparkViewEngine ) that can be overriden( but not required ) in _ViewStart.

    For me SparkViewEngine is more solid for now.

  • issue using Razor and MSPublish. MSPublish doesn't know to copy *.cshtml as output ... will this be fixed? Ideas on temp solution?

  • I am planning to use razor engine for a small website project. It would be fun to learn and code. My only concern is whether I will be able to host my website with normal windows hosting providers ?

  • @carlosmartinezt,

    >>> The cleanest way would be to use .html and recognise that it is a razor page just by the content or a first line directive @language=vb | @language=cs <<<

    Err, that would mean that the web server would have to inspect EVERY file to determine whether to send it through the view engine or not. HTML files are just "dumped" down the pipe, which is far more efficient. No, it is much better to change the extension so that the proper server extension can be called based on file name rather than content.

  • +1 for the concept of an @layout directive instead of the code block. Layouts / Master Pages are SO common that this deserves an @ tag of its own. More, I would like for the directive to use the default search path (i.e., current view folder -> shared view folder for current area -> shared view folder for default area) so that you don't have to explicitly specify the full path of the view.

    +1 also for disliking the _ViewStart page. Especially if it breaks partial views w/ AJAX. How about an @layout tag that can simply point to a cshtml page that itself determines the layout.

    MYPAGE.CSHTML
    -------------
    @model ...
    @layout "mylayout.cshtml"

    ...

    MYLAYOUT.CSHTML
    ---------------
    @{
    if(WHATEVER) {
    Layout = "normal.cshtml";
    } else {
    Layout = "mobile.cshtml";
    }
    }

    Not sure how you're supporting nested layouts and if this would break things, but it seems like this may be a much cleaner (and more flexible) way of doing things. A global start page just seems kludgy and prone to issues.

    If you don't like having the @layout directive potentially call a page that CHANGES the layout, then perhaps an explicit @viewStart directive that calls a specific page rather than the specially named one.

    Minimally, please provide some way of having a page that does NOT first call the _ViewStart page (like an @noViewStart directive or something).

  • Is Bob Muglia an idiot. I think the guy is a few screws loose !

    Silverlight had such a bright future until that moron cast a shadow of doubt on the technology. Fortunately for me, I was just starting to invest in Silverlight, and I had some large banking projects lined up with the technology.

    You can appreciate that recommending Silverlight to a client now would be like shooting myself in the foot. You mean that technology that Microsoft relegated to the soon to be failure WP7.


  • Do you know how I could find out the status of CAT.NET? It looks like a great enterprise tool for VS, but the beta was pulled & the CAT.NET team seems to have dropped off the map. Thanks much!

  • @MartinF...

    +1

    I would like to know the same - how can we use _ViewStart in Areas? When I try, I get this error:

    Unable to cast object of type 'ASP._ViewStart_vbhtml' to type 'System.Web.WebPages.StartPage'.

  • Ok, I have found a couple more bugs with the beta. One is related to views, not layouts in particular, but them too. When you add a new view to your project, the default build action for that view is none. The default should be content, or you get into some real problems when you do a publish of the project from within Visual Studio. The second bug is more complicated and has nothing to do with views or layouts. So say I have an action defined as follows:

    [HttpPost]
    public ActionResult Save(ProductViewModel model)
    {
    ...
    return Json(model);
    }

    The ProductViewModel class is defined as follows:

    public class ProductViewModel
    {
    public string Make { get; set; }
    public string Model { get; set; }
    ...
    }

    Then on the view we call the save action from JQuery sending a Json Object as follows:

    function Save(make, model)
    {
    var product = {
    Make: make,
    Model: model
    };

    var encoded = JSON.stringify(product);

    $.ajax({
    url: '/Controller/Save',
    type: "POST",
    data: encoded,
    dataType: "json",
    contentType: 'application/json; charset=utf-8',
    success: function(result) {
    alert(JSON.stringify(result));
    },
    error: function() {
    alert("Error occured.");
    }
    });
    }

    Then the following occurs. The Action Save receives a null view model. The key to this problem is that the variable name that is passed to the action is one of the properties of the view model. If you change the Action method to anything else (except make) it will work. So if we change it as follows, everything will work as expected:

    [HttpPost]
    public ActionResult Save(ProductViewModel product)
    {
    ...
    return Json(product);
    }

  • 'Html' is ambiguous, imported from the namespaces or types 'System.Web.WebPages, System.Web.Mvc'.
    I have been searching like crazy to find an answer to this error.
    I am in VS 2010 MVC 3 empty project. I have added my first Controller and View. When I load it I get thise error.
    Please advise on where I can find the answer.

  • In cshtml file:

    帳號:@Context.User.Identity.Name

    Razor engine show "error CS1002: it must be ;"
    But "帳號" text is Multilingual characters.
    How to write Multilingual Character in cshtml file?

  • @flash: I pasted that line in a Razor file and did not get an error. Are you running the latest build? If so, can you please start a thread on the forum so we can discuss more easily? Thanks!

  • @Levi,

    >>>>>>> Ok, I have found a couple more bugs with the beta. One is related to views, not layouts in particular, but them too. When you add a new view to your project, the default build action for that view is none. The default should be content, or you get into some real problems when you do a publish of the project from within Visual Studio.

    This first issue is because there wasn't Razor tooling in the beta. With the new RC build (and with the final release) the build action will by default be set to content. For views created with the beta you need to go back and change the content type explicitly to content.

    The second issue is expected behavior. The name of the parameter affects model binding, so when the parameter is named “model” and the binding data contains “model”, we will bind that value to the object. This is how we’re able to bind multiple model values to multiple parameters (i.e., you could create parameters “string make” and “string model” to extract the values as well as the complex model).

    You could also craft the JSON such that it matched the parameter name, if you really wanted to use “model” as the parameter name:

    {
    Model: {
    Make: make,
    Model: model
    }
    }

    And then the data would have the equivalent names of “Model.Make” and “Model.Model”. Of course, with such binding, you would need to name your parameter model now, as there’d be no other way to match the binding data coming in for the post.

    Hope this helps,

    Scott

  • Hi Scott.

    I'm wondering whats the best practice to create a logic for selecting phones views.

    I Know Nerdinner has a way for this, and Scott Hanselman blogged about a new way using a custom ViewEngine to do this.

    Now you blogged a third way for this, so When to use each one? is one better than the other ways?

    Thanks in Advance.

  • Does Razor work in medium trust / partial trust environments?

  • I am really excited about the layouts. I can see creating a sort of layout dictionary that defines the main and sub page layouts. I am correct that it should be possible to define regions within the index and assign different layouts to each region? And also the layouts themselves can contain nested layouts? This is kind of a decorator pattern for layouts. Nice....

  • Awesum blog. I liked to read most of the blog entries :)

  • It looks like if you specify a ViewStart in the main view/shared i.e root of the web app, then this is not applied in areas.

    I think some feature that applies common "ViewStart" logic, would be very useful - if I haven't missed a trick that is.

    Otherwise, gotta say I am loving all the recent MVC 3, Razor and EF features. Take my hat off to Microsoft.

  • When I render partial views via @Html.RenderAction(..) (Action was marked with ChildActionOnly) but the child view output was added the code from _ViewStart. I though _ViewStart code only call only one per page request, but it was called every render child action.

Comments have been disabled for this content.