Introduction to ASP.NET Core part 14: the view start and the layout files
February 17, 2017 1 Comment
Introduction
In the previous post we looked at a special kind of view file called _ViewImports.cshtml. It is special in a way that MVC knows about this file and will pick it up during execution. We don’t need to register the file anywhere, we just need to put it in a folder where the views are stored. The view imports file has a very specific role. We can put a number of C# using statements that will be executed in all view cshtml files where the view imports file is applied. We can have multiple _ViewImports file in the project. We can have one in each folder containing the views, like /Views/Books, /Views/Customers etc. and each view file in those folders will pick up the view imports file in that folder. We can also have an overall view imports file in the Views folder as well. That imports file will be applied to every single view within the Views folder and its subfolders.
In this post we’ll look at another special file called _ViewStart.cshtml. We’ll also talk a bit about the layout file since it is strongly related to the view start file.
The view start file
The view start file is similar to _ViewImports.cshtml. It is also a file that MVC knows about and picks up automatically without any registration. It too can be placed in each views subfolder and one in the Views folder itself. The views start file will be executed before the views. It can contain a C# code block which MVC will run before processing the cshtml files.
Let’s see how it works. Add the following file in the Views/Books folder of our demo application:
You’ll see that the template added a short code block which is a telling sign of the main purpose of _ViewStart.cshtml:
@{ Layout = "_Layout"; }
We’ll come to layouts in a bit. This is an example of a C# code block in a cshtml file: open it with @ and a curly brace, add the C# code and close the block with another curly brace. For the time being let’s just put something different in there:
@{ //Layout = "_Layout"; System.Diagnostics.Debug.WriteLine("This is views start executing in the views/books folder!!!"); }
Start the application and navigate to /books. You should see the following printed in the debug window:
This is views start executing in the views/books folder!!!
Put one more file of the same type directly in the Views folder with the following content:
@{ //Layout = "_Layout"; System.Diagnostics.Debug.WriteLine("This is views start executing in the root Views folder!!!"); }
Just to recap we have the following files in the Views folder at present:
Run the application and navigate to /books. You should see the following output in the Debug window:
This is views start executing in the root Views folder!!!
This is views start executing in the views/books folder!!!
The root _ViewStart.cshtml file was executed before the more specific one.
Layout
The main usage of the _ViewStart file is to declare the layout file for the views. The layout file contains common markup for the views such as the navigation bar, a footer, a side bar etc. If there’s no common layout file then we have to duplicate a lot of markup in each cshtml file.
A layout file is commonly called _Layout.cshtml but this is not a must. In contrast to the view start and view imports files the Layout file must be declared in code, it won’t be picked up by MVC automatically. It’s possible to have multiple layout files and apply them for each view file separately. The common layout file must be placed in a folder called Shared within the Views folder. You may remember the Shared folder from an earlier post. MVC will search the subfolders of the Views folders for a matching controller. MVC will also search the Shared controller which is accessible to other views as well.
So go ahead and add a folder called Shared and put the following item in it:
The name of the layout file can be anything, but it’s customary to call it _Layout.cshtml.
The default layout view has a number of interesting elements. The basic HTML skeleton with the HTML declaration, the head, body and div elements are easy to follow I hope. The ViewBag.Title and RenderBody will be new for newcomers. In short:
- ViewBag is a dynamic object which can have any property attached to it. We’ll see an example soon how to assign the title from the view using ViewBag. Keep in mind that ViewBag is sort of a dynamic container of properties where Title is a widely used one to store the title of the page.
- @RenderBody(): this is where the body of the view will be injected
Let’s improve the markup of our 3 views in the Books folder and break out the common HTML elements from them. First we’ll declare our layout file in the common view start file, i.e. the one in the Views folder:
@{ Layout = "~/Views/Shared/_Layout.cshtml"; System.Diagnostics.Debug.WriteLine("This is views start executing in the root Views folder!!!"); }
Let’s start with Index.cshtml:
@model BookIndexViewModel @{ ViewBag.Title = "Books on offer"; } <h1>These are our books on offer. Total: @Model.TotalBooksOnOffer</h1> <table> <thead> <tr> <th>ID</th> <th>Author</th> <th>Title</th> <th>Details</th> </tr> </thead> <tbody> @foreach (var bookViewModel in Model.BookViewModels) { <tr> <td>@bookViewModel.Id</td> <td>@bookViewModel.Author</td> <td>@bookViewModel.Title</td> <td>@Html.ActionLink("Details", "Details", "Books", new { id = bookViewModel.Id }, new { @class = "book-details-link" })</td> </tr> } </tbody> </table> @Html.ActionLink("Add new", "Create")
Note the following:
- We set the title of the page using the ViewBag dynamic object. This will be picked up by the layout file. Notice that as you type “ViewBag.” in the editor it won’t give you any IntelliSense. This is due to the dynamic nature of ViewBag. If you’re new to this then read the post referenced above for a short intro.
- We removed the HTML elements that are included in the layout file and only kept what’s specific to the index page
- The HTML markup of Index.cshtml will be injected to where it says RenderBody() in _Layout.cshtml
Run the page and check the HTML source of /books. You’ll see all the elements from the layout page and the index view.
It’s perfectly fine to have multiple different layout pages in the application. The C# code block in _ViewStart.cshtml can contain if-else statements to decide which layout to use. E.g. you may want to have a different layout based on the current season. Check which season it is in the code block and assign the correct layout: _LayoutSummer.cshtml, _LayoutWinter.cshtml etc.
Here’s the updated Create.cshtml file:
@model InsertBookViewModel @{ ViewBag.Title = "Insert a new book"; } <h1>Insert a new book</h1> @using (Html.BeginForm()) { @Html.ValidationSummary() <div> @Html.LabelFor(m => m.Author) @Html.TextBoxFor(m => m.Author) @Html.ValidationMessageFor(m => m.Author) </div> <div> @Html.LabelFor(m => m.Title) @Html.EditorFor(m => m.Title) @Html.ValidationMessageFor(m => m.Title) </div> <div> @Html.LabelFor(m => m.Price) @Html.TextBoxFor(m => m.Price) @Html.ValidationMessageFor(m => m.Price) </div> <div> @Html.LabelFor(m => m.NumberOfPages) @Html.TextBoxFor(m => m.NumberOfPages) @Html.ValidationMessageFor(m => m.NumberOfPages) </div> <div> @Html.LabelFor(m => m.Genre) @Html.DropDownListFor(m => m.Genre, Html.GetEnumSelectList(typeof(DotNetCoreBookstore.Domains.Genre))) </div> <div> <input type="submit" name="create-book" value="Save" /> </div> }
…and the updated Details view:
@model BookDetailsViewModel @{ ViewBag.Title = "Details of " + @Model.Title; } <h1>Details of @Model.Title</h1> <table> <thead> <tr> <th>Author</th> <th>Title</th> <th>Genre</th> <th>Pages</th> <th>Price</th> </tr> </thead> <tbody> <tr> <td>@Model.Author</td> <td>@Model.Title</td> <td>@Model.Genre</td> <td>@Model.NumberOfPages</td> <td>@Model.Price</td> </tr> </tbody> </table> @Html.ActionLink("Back to list", "Index")
What if we want to declare a different layout which is specific to a given view? Easy, we can override the layout in the C# code block where the ViewBag.Title is assigned:
@model BookDetailsViewModel @{ ViewBag.Title = "Details of " + @Model.Title; Layout = "some_other_layout.cshtml"; }
What if a view doesn’t need a layout? Just assign Layout to null:
@model BookDetailsViewModel @{ ViewBag.Title = "Details of " + @Model.Title; Layout = null; }
Sections
The layout file has another interesting feature called RenderSection. It is similar to RenderBody in that it will render a set of HTML from a view.
Here’s the extended _Layout.cshtml file:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> </head> <body> <div> @RenderBody() </div> <div> @RenderSection("FunnyMessage", true) </div> </body> </html>
Run the application now and go to the /books page as usual. You should get an error message with the following content:
InvalidOperationException: The layout page ‘/Views/Shared/_Layout.cshtml’ cannot find the section ‘FunnyMessage’ in the content page ‘/Views/Books/Index.cshtml’.
This is because of the “true” boolean value in the RenderSection method. It means that a view that uses this layout file must implement a section called FunnyMessage. The section can be implemented in a named code block of a cshtml file. Here’s an example for the Index page:
@model BookIndexViewModel @{ ViewBag.Title = "Books on offer"; } <h1>These are our books on offer. Total: @Model.TotalBooksOnOffer</h1> <table> <thead> <tr> <th>ID</th> <th>Author</th> <th>Title</th> <th>Details</th> </tr> </thead> <tbody> @foreach (var bookViewModel in Model.BookViewModels) { <tr> <td>@bookViewModel.Id</td> <td>@bookViewModel.Author</td> <td>@bookViewModel.Title</td> <td>@Html.ActionLink("Details", "Details", "Books", new { id = bookViewModel.Id }, new { @class = "book-details-link" })</td> </tr> } </tbody> </table> @Html.ActionLink("Add new", "Create") @section FunnyMessage { <p>This is a very funny message</p> }
If you test the /books page now you’ll see the message printed underneath the list of books. Setting the second parameter of RenderSection to false will make the implementation of the section optional:
<div> @RenderSection("FunnyMessage", false); </div>
We’ll continue in the next post where we’ll start looking at a new feature in MVC .NET called tag helpers.
View the list of MVC and Web API related posts here.
Hi Andras,
I need to know that, can i override just footer of a default layout for a particular view, .
please help.
thanks