How do I build an MVC application that leverages Areas for modules but uses a single layout for all Areas (Part 2)?

Posted on February 15th, 2011

In Part 1 of this post we explored how to create a single shared layout across multiple Areas using the ASPX view engine in ASP.NET MVC 3. In the follow up, we will cover doing the same with the Razor view engine.

Part 2: The Razor view engine

The Razor view engine is different from the ASPX view engine in that there is no concept of a hard line MasterPage. Heck, there is no concept of a user control view (ascx) either. In fact, the Razor view engine keeps things extremely simple. Every view is a cshtml file. The contents of the file define its purpose.

To get started we will need to create a new MVC 3 project, this time selecting Razor as our view engine:

New Project

The project that is created will actually come with a _Layout.cshtml file in the Views/Shared directory that has content indicating that it is a site layout file:

Default Layout

The contents of this file by default are:




    @ViewBag.Title
    
    



    @RenderBody()


It is important to note that the naming convention on this file has no bearing on whether or not it is a site layout. We can rename this file to ViewMasterPage.cshtml if we wanted to and as long as we reference it as the "layout" file to use for a view page then everything will work fine. How do we reference the "layout" file to use? Lets find out. Time to create our HomeController with an Index action and corresponding view.

Home Controller

If we add a new Razor view using the Add -> View dialog window in Visual Studio and select to use our _Layout.cshtml file the contents of our view file will be as follows:

@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

Index

The code block at the top of the view is where we set our variables such as the new ViewBag object properties (similar to the concept of the ViewData collection in previous iterations of ASP.NET MVC). As you can see, there is a variable named Layout that is getting set here as well. If we want to set the "site layout" to use on a per view basis it can be accomplished with the code above.

From here we can follow the same pattern as that of the ASPX view engine and create our Area(s), then simply set the Layout variable in those views to the top level Views/Shared/_Layout.cshtml file.

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

Before we take a look at how to implement our unique main menu for each Area(s) that we did in Part 1, lets take a look at a new option available to us with the Razor view engine for making layout management a bit easier. The Razor view engine will look for files with the specific naming convention of _ViewStart.cshtml that can be used to handle pre-defining variables used by each view within its scope. By default, a new MVC 3 Razor view engine project includes one of these files in the top level Views directory.

ViewStart

The scope of this _ViewStart.cshtml file covers all views in the top level Views directory:

ViewStart Scope

The default content of this file is:

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

With this in place, our cshtml view files that we create within the scope of the _ViewStart.cshtml file can exclude the Layout variable setting if we wish to use the top level site layout file _Layout.cshtml.

@{
    ViewBag.Title = "Index";
}

Index

As we add our Area(s), all we need to do is create a _ViewStart.cshtml file in the Views directory of our Area and all the Area views will use the Layout value set within it.

Adding an Area specific main menu in the Razor view engine is a little bit different than in the ASPX view engine. For starters, we don't create a user control view file. We just create a new cshtml file that we will name Menu.cshtml and place that in our top level Views/Shared directory.

Menu

We need to give it some content:

  • @Html.ActionLink("Go Home", "Index", "Home")

We also need to add a call to RenderPartial in our _Layout.cshtml file. While we are in there we will make it look like our sample in Part 1 as well.




    @ViewBag.Title
    
    



    

Our Test Site

@{Html.RenderPartial("Menu");} @RenderBody()

And update our Index.cshtml file to contain the same content as Part 1:

@{
    ViewBag.Title = "Home Page";
}

Welcome to our home page!

If we F5 the site from Visual Studio we can see our HomeController Index action rendering our layout, menu and page content just like our ASPX view engine example:

Home Page

Time to add an Area. We will create a new Area named Articles and create a LatestController and corresponding view for an Index action. We will also create a view in our Shared directory named Menu.cshtml for the Area's specific main menu. Finally, we will create the _ViewStart.cshtml file in the Views directory of our new Area.

Area

The content for our Areas/Articles/Views/Shared/Menu.cshtml is as follows:

  • @Html.ActionLink("Go Home From Articles", "Index", new { area = "", controller = "Home" })

We need to update our Views/Shared/Menu.cshtml at the top level to add a link into our Articles Area:

  • @Html.ActionLink("Go Home", "Index", "Home")
  • @Html.ActionLink("Latest Articles", "Index", new { area = "Articles", controller = "Latest" })

We will give our Index.cshtml view for our LatestController Index action some content:

@{
    ViewBag.Title = "Latest Articles";
}

Check out the latest articles:

Finally, we want to make sure our _ViewStart.cshtml file that is scoped for our Area contains the correct reference to the top level layout file:

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

Now if we do an F5 we can see our updated menu on our home page:

Home Page 2

...and if we navigate to the Latest Articles we can see our content page with the menu from the Articles Area being used (just like our ASPX view engine example in Part 1):

Articles Page

By creating a Menu.cshtml View in the Areas/Articles/Views/Shared directory we can control the content that gets used by the call to Html.RenderPartial() from our Razor view engine Layout page. This is explained at the end of Part 1 if you happened to not read that prior to reading Part 2!

As you can see, the Razor view engine is much lighter than the ASPX view engine in terms of content needed in each view file. It is also a bit more simplistic in its approach to view files. But both view engines provide a vary similar way to handle a shared view across Areas.

Discussion

GundamKnight
GundamKnight
16 Feb, 2011 04:58 PM

Areas FTW!

Antuan
Antuan
18 Jun, 2011 11:58 PM

This is very very helpful. I have been searching for a solution for this particular situation for a while now and now I found it. I did see other solutions but they weren't so attractive. Thanks a lot!!!

19 Jun, 2011 09:21 PM

Antuan
You're welcome!

Dave
Dave
13 Jul, 2011 03:05 PM

Very well explained - keep it up!

13 Jul, 2011 05:02 PM

Dave
Thanks for the feedback!

Paul Marshall
Paul Marshall
22 Aug, 2011 10:42 PM

Well explained article

25 Aug, 2011 02:46 PM

Paul Marshall
Thanks!

14 Sep, 2011 11:57 PM

Dude!! The concept of taking out the menus into partial controls saved me so much time.

Bhash
Bhash
10 Oct, 2011 12:38 PM

Thanks for the solution!! It saved my time.

Olufemi Oyekan
Olufemi Oyekan
18 Oct, 2011 09:51 PM

Outstanding job, clear as crystal.

Mike Goynes
Mike Goynes
17 Mar, 2012 04:00 PM

I just used this (step 2 only) to create my first MVC3 project, and it makes perfect sense. Thanks for creating such a well written explanation.

Teresa
Teresa
11 Apr, 2012 04:58 PM

Thank for the solution described above, I've been attempt to add an Area section into an already existing MVC3 razor application.

I've added area and moved over the necessary Views and Controllers, but the views that are now in the Areas sections do not seem to compile any of the HTML Helper methods such as Html.BeginForm() or Html.Encode ect...

I've added

@inherits System.Web.Mvc.WebViewPage

at the top of each view in my areas and also linked in the

@using {namespace}.API.Extensions;

where the HTML helper exists but it still will not compile without errors and will not load any of these views.

Can you tell if I am missing something here?

Thanks in advance!

Teresa
Teresa
11 Apr, 2012 04:59 PM

Thank for the solution described above, I've been attempting to add an Area section into an already existing MVC3 razor application.

I've added area and moved over the necessary Views and Controllers, but the views that are now in the Areas sections do not seem to compile any of the HTML Helper methods such as Html.BeginForm() or Html.Encode ect...

I've added

@inherits System.Web.Mvc.WebViewPage

at the top of each view in my areas and also linked in the

@using {namespace}.API.Extensions;

where the HTML helper exists but it still will not compile without errors and will not load any of these views.

Can you tell if I am missing something here?

Thanks in advance!

furkan
furkan
24 May, 2012 08:51 PM

Actually i wanna ask a question about adding menus to layout.cshtml from database. I mean i got some menu's names in database and i'm trying to add those menu's names with foreach function but i got error it said that nullreference exception in my model. Could you help me please ?

No new comments are allowed on this post.