Generally any time we are designing a web application / website, we want to maintain same look & feel across website.
e.g.: common header, footer, menu bar etc.
For example If we want to design a static website in html with common look & feel (common header, footer, menu bar etc), we involve in duplicating the same code (for common content) across all the pages in website. This is leads to problem of maintainability of application. If we want to make any changes in common content, example in menu bar I want to change one item name (contact to contact us) , for this small change also you should change in all the 100 or n pages in your website. If want change complete header/footer/ menu which is big headache for designer/developer.
Same kind of problem comes while designing ASP.NET applications also, ASP.NET 2.0 came with concept of Master Pages to solve the problem of maintain common look & feel across website, which we already know.
Our discussion is about ASP.NET MVC, how to solve this problem while using razor view engine. As we know razor is a new View Engine introduced with ASP.NET MVC 3 as alternative to traditional .aspx view engine. By default ASP.NET MVC 3 come with 2 view engines .aspx & razor. Razor is recommend view engine for mvc 3, which has lot befits over traditional .aspx view engine. If want to more understand about please refer to my article on razor.
Now the question is how to maintain common look & feel across entire application in ASP.NET MVC 3 while using razor?
Razor comes with a solution, with the concept of Layouts. Layouts are similar to Master Pages in ASP.NET WEBFORMS, which allows you to define a common site template, and inherit its look and feel across all the views in your asp.net mvc applications.
To demonstrate concept of Layouts, I am using very simple example: I have Person Model in application, I am going to display list persons details.
namespace LayoutSample.Models { public class Person { public string Name { get; set; } public int Age { get; set; } } }
I have HomeController , Index action which returns List of Persons, I am creating objects in action method it self, I am not writing any business logic & data access logic to fetch details from database, to make it simple.
using LayoutSample.Models; using System.Collections.Generic; using System.Web.Mvc; namespace LayoutSample.Controllers { public class HomeController : Controller { public ActionResult Index() { var list = new List<Person> { new Person { Name = "Name1", Age=21 }, new Person { Name = "Name2", Age=22 }, new Person { Name = "Name3", Age=23 }, new Person { Name = "Name4", Age=24 }, new Person { Name = "Name5", Age=25 }, new Person { Name = "Name6", Age=26 }, new Person { Name = "Name7", Age=27 }, new Person { Name = "Name8", Age=28 }, new Person { Name = "Name9", Age=29 } }; return View(list); } } }
Here is my Index.cshtml view which displays list of persons:
@model IEnumerable<LayoutSample.Models.Person> @{ Layout = null; } <!DOCTYPE html> <html> <head> <title>Persons Details</title> </head> <body> <table> <tr> <th>Name</th> <th>Age</th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Name) </td> <td> @Html.DisplayFor(modelItem => item.Age) </td> </tr> } </table> </body> </html>
The final output is:
This view does not use any layout, but my application needs a common look & feel across all the views, Let’s design a layout page with common header & footer.
I am going to add _SiteLayout.cshtml in ~/Views/Shared/ folder usually which is the default place where common view files/templates.
_SiteLayout.cshtml:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/modernizr-2.5.3.js")" type="text/javascript"></script> </head> <body> <header style="color: #f00; text-align: center; height: 50px; line-height: 50px;"> <h2>Persons Details</h2> </header> <div> @RenderBody() </div> <footer style="text-align: center;"> http://65.1.162.244 © 2011 </footer> </body> </html>
- _SiteLayout.cshtml has header, footer, along with RenderBody() method.
- We are calling the @RenderBody() method within the layout file to indicate where we want the views based on this layout to fill their content at that location.
Lets modify Index.cshtml to use Layout in _SiteLayout.cshtml
@model IEnumerable<LayoutSample.Models.Person> <table> <tr> <th>Name</th> <th>Age</th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Name) </td> <td> @Html.DisplayFor(modelItem => item.Age) </td> </tr> } </table>
Observations from above code
-
We are using Layout keyword in view to set Layout page programmatically.
-
View code is not wrapped by any special tag, this code will fill in RenderBody() of Layout Page.
-
We are using @ViewBag.Title is in Layout Page & setting value in View Page for @ViewBag.Title
First Index.cshtml html will execute , then SiteLayout.cshtml executes, then output html of Index.cshtml will fill into body of the output html of SiteLayout.cshtml. Now same layout page SiteLayout.cshtml can be used in all views to maintain common look & feel across entire application.
But we should use Layout keyword in all the views in application, to set the layout page for them. Again which involves in duplicating the code across all the views in app, which breaks DRY(Don’t Repeat Yourself) principle. MVC solves this problem in very simple way.
We should create _ViewStart.cshtml (or) _ViewStart.vbhtml under the ~/Views folder of our project. Move Layout Keyword to _ViewStart file define the layout for all the views in application. _ViewStart file can be used to define common view code that we want to execute at the start of each View’s rendering, so we need not explicitly set Layout in each view, until & unless we want to override the default layout page & set other layout page for a particular view.
_ViewStart.cshtml
@{
Layout = "~/Views/Shared/_SiteLayout.cshtml";
}
Few more lines about _ViewStart file from Scott Gu article on Layouts.
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.
In above example we are using RenderBody() method in layout file which filled by the contents of Views. So only one region/section of layout page is replaceable. But how can we have multiple replaceable regions/sections with in a layout file?
Razor comes with concept of sections to solve this, using sections depending on the layout we can optionally fill content at runtime. Our above example has a layout page that has only single section in it, now I want add one more section which display a menu bar depending on view, for example one view display menu bar , one view does not, Let’s see how to write the code.
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/modernizr-2.5.3.js")" type="text/javascript"></script> </head> <body> <header style="color: #f00; text-align: center; height: 50px; line-height: 50px;"> <h2>Persons Details</h2> <nav> @RenderSection("menu", required: false) </nav> </header> <div> @RenderBody() </div> <footer style="text-align: center;"> http://65.1.162.244 © 2011 </footer> </body> </html>
How to define section in layout pages?
we should use RenderSection() helper method. If we look at the syntax @RenderSection(string sectionname,bool required) . The first parameter specifies the name of the section we want to render at that location in the layout template. The second parameter is optional, and allows us to define whether the section we are rendering is required or not. If a section is “required”, then Razor will throw an error at runtime if that section is not implemented within a view template.
How to implement section in View Template?
This could be done using @section section-name {………}. @section {…} can be define any where in the view template.
@model IEnumerable<LayoutSample.Models.Person> <table> <tr> <th>Name</th> <th>Age</th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Name) </td> <td> @Html.DisplayFor(modelItem => item.Age) </td> </tr> } </table> @section menu{ <p>menu bar will appear here</p> }
so when we run the application index.cshtml will render the menu bar. if execute any other views in the application if they define menu section, it will otherwise, it don’t render anything or don’t throw any error because we define menu sectional as optional in layout template.
Now my idea is to find whether the section is defined or not ,if it is not defined then, I want to display some default content. We can detect whether a section is defined or not in view template using IsSectionDefined() method.
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/modernizr-2.5.3.js")" type="text/javascript"></script> </head> <body> <header style="color: #f00; text-align: center; height: 50px; line-height: 50px;"> <h2>Persons Details</h2> <nav> @if (IsSectionDefined("menu")) { @RenderSection("menu", required: false) } else{ <p>Welcome User!</p> } </nav> </header> <div> @RenderBody() </div> <footer style="text-align: center;"> http://65.1.162.244 © 2011 </footer> </body> </html>
If View template defines a section menu, it will render code/content in menu section else it will render Welcome User! message. So we are done with our examples, this article uses very simple example to demonstrate Layouts in Razor. But using RenderBody() & RenderSection() you can design any kind of complex/nice layout templates.
1 Comment
swathi
your code is understanding easily. but it was not executing so please check it.