In this chapter, you will learn about different views that can be used for layout, to include namespaces, and to render partial content in a view.
The _Layout.cshtml Razor view gives the application more structure and makes it easier to display data that should be visible on every page, such as a navigation bar and a footer. You avoid duplication using this view. The underscore at the beginning of the name is not required, but it is a convention that is commonly used among developers. It signifies that the view shouldn’t be rendered as a view result with the View method from a controller action.
The normal views, like the Index view, are rendered inside the _Layout view. This means that they don’t have any knowledge about the navigation and the footer; they only need to render what the action tells them to render.
If you look inside the views you have created, they have some code in common, such as the <html>, <head>, and <body> elements. Because the markup is the same for all the views, it could be moved to the _Layout view.
Shared views, like _Layout, are placed in a folder called Shared inside the Views folder. These views are available anywhere in the application. The layout view doesn’t have to be named _Layout; you can even have multiple layout views in the application if you like.
The _Layout view is a Razor view, which means that you can use C# inside the view, like you can in any other view. It should also have a method called @RenderBody, which is responsible for rendering the different content views the user navigates to, such as the Index and the Details views.
There is an object called @ViewBag in the _Layout view. It is a dynamic object that you can use to send data from the server to the view.
Another method that can be used in the _Layout view is the @RenderSection. This method can be used to render specific sections of HTML from the content view in the _Layout view. There is an asynchronous version of this method that you can use if you want that type of behavior.
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/
jquery-validate/1.19.0/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/
jquery-validation-unobtrusive/3.2.11/
jquery.validate.unobtrusive.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/
bootstrap.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/
twitter-bootstrap/4.1.3/css/bootstrap.min.css" />
<footer>@RenderSection("footer", false)</footer>
The complete markup for the _Layout view:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.0/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" />
<title>@ViewBag.Title</title>
</head>
<body>
<div>
@RenderBody()
</div>
<footer>
@RenderSection("footer", false)
</footer>
</body>
</html>
Now that the _Layout view has been added, you need to remove the markup shared among the content views.
Open the Index view and remove the <head> and <body> elements, and do the same for the other views in the Home folder. You can use the Ctrl+E, D keyboard command to format the HTML.
Since you removed the <title> element from the view, you can add it to the ViewBag object as a property called Title. Assign the name of the view to the property. Since the ViewBag is placed inside a C# block, it doesn’t need the @-sign.
You can also use the Layout property in the C# block to tell the MVC framework which layout view to use with the view. The layout view must be specified with an explicit path, beginning with the tilde (~) sign.
The usual C# rules apply inside C# blocks, such as ending code lines with a semicolon.
@model IEnumerable<AspNetCore22Intro.ViewModels.VideoViewModel>
<table>
@foreach (var video in Model)
{
<tr>
<td>@Html.ActionLink(video.Id.ToString(), "Details",
new { id = video.Id })</td>
<td>@video.Title</td>
<td>@video.Genre</td>
</tr>
}
</table>
@{
ViewBag.Title = "Home";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@section footer{ <div>This is the Index footer</div> }
The complete code in the Index view, after removing the elements:
@model IEnumerable<AspNetCore22Intro.ViewModels.VideoViewModel>
@{
ViewBag.Title = "Home";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<table>
@foreach (var video in Model)
{
<tr>
<td>@Html.ActionLink(video.Id.ToString(), "Details",
new { id = video.Id })</td>
<td>@video.Title</td>
<td>@video.Genre</td>
</tr>
}
</table>
@section{
<div>This is the Index footer</div>
}
The complete code in the Details view, after removing the elements:
@model AspNetCore22Intro.ViewModels.VideoViewModel
@{
ViewBag.Title = "Details";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div>Id: @Model.Id</div>
<div>Title: @Model.Title</div>
<div>Genre: @Model.Genre</div>
@Html.ActionLink("Home", "Index")
The complete code in the Create view, after removing the elements:
@using AspNetCore22Intro.Models
@model AspNetCore22Intro.Entities.Video
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Create Video</h1>
<form asp-action="Create" method="post">
<div asp-validation-summary="All"></div>
<table>
<tr>
<td><label asp-for="Title"></label></td>
<td><input asp-for="Title" /></td>
<td><span asp-validation-for="Title"></span></td>
</tr>
<tr>
<td><label asp-for="Genre"></label></td>
<td><select asp-for="Genre"
asp-items="Html.GetEnumSelectList<Genres>()"></select>
</td>
<td><span asp-validation-for="Genre"></span></td>
</tr>
</table>
<input type="submit" value="Create" />
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
The Razor view engine has a convention that looks for a file called _ViewStart.cshtml. This file is executed before any other views, but it has no HTML output. One purpose it has is to remove duplicate code from code blocks in the views, like the Layout declaration. Instead of declaring the location of the _Layout view in each view, it can be placed inside the _ViewStart view. It is possible to override the settings in the _ViewStart view by adding the Layout declaration in individual views.
If you place this view directly in the Views folder, it will be available to all views. Placing it in another folder inside the Views folder makes it available to the views in that folder.
You can assign null to the Layout property in a specific view to stop any layout view from being used with the view.
Let’s create the _ViewStart view in the Views folder, and add the Layout declaration in it.
The Razor view engine has a convention that looks for a file called _ViewImports.cshtml. This file is executed before any other views, but it has no HTML output. You can use this file to add using statements that will be used by all the views; this removes code duplication and cleans up the views.
So, if you know that many views will use the same namespaces, then add them to the _ViewImports.cshtml file. Add the file to the Views folder.
@model Video
@model IEnumerable<VideoViewModel>
@model VideoViewModel
The complete code in the _ViewImports file:
@using AspNetCore22Intro.Models
@using AspNetCore22Intro.Entities
@using AspNetCore22Intro.ViewModels
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Tag Helpers are new to ASP.NET Core, and can in many instances replace the old HTML helpers. The Tag Helpers blend in with the HTML as they appear to be HTML attributes or HTML elements.
You have already used Tag Helpers in the Create form. There you added the asp-for and asp-validation-for among others. They blend in much better than the alternatives: LabelFor, TextBoxFor, EditorFor, and other HTML helpers that are used in previous versions of ASP.NET. You can still use Razor HTML Helpers in ASP.NET Core, and they have one benefit over Tag Helpers; they are tightly coupled to the model. This means that you get IntelliSense and can rename properties more easily. In a Tag Helper, the property is added as a string value.
To use the Tag Helpers, you need to add a @addTagHelper directive to the _ViewImports view, or in specific views where you want to use them. The first parameter, the asterisk, specifies that all Tag Helpers in that namespace should be available. You can change this to a specific Tag Helper if you don’t want to import all helpers.
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Let’s add a link calling the Create action from the Index view using Tag Helpers, so that you don’t have to type in the URL to the Create view in the browser. Let’s also replace the ActionLink HTML helper for the Id property, with a Tag Helper that opens the Details view and has the description Details.
<a asp-action="Create">Create</a>
<td>
<a asp-action="Details" asp-route-id="@video.Id">Details</a>
</td>
The complete markup for the Index view:
@model IEnumerable<VideoViewModel>
@{
ViewBag.Title = "Home";
}
<table>
@foreach (var video in Model)
{
<tr>
<td><a asp-action="Details"
asp-route-id="@video.Id">Details</a></td>
<td>@video.Title</td>
<td>@video.Genre</td>
</tr>
}
</table>
<a asp-action="Create">Create</a>
@section footer{
<div>This is the Index footer</div>
}
There are two more views needed to complete the CRUD operations, the Edit and Delete views. Let’s add the Edit view by copying the Create view and modify the form. Then let’s refactor the IVideoData interface, and the classes implementing it. Instead of saving data directly when a video is added or edited, this refactoring will make it possible to add or edit multiple videos before saving the changes to the database.
ViewBag.Title = $"Edit {Model.Title}";
<h1>@ViewBag.Title</h1>
<form asp-action="Edit" method="post">
<input type="submit" value="Edit" />
<tr>
<td>@video.Title</td>
<td>@video.Genre</td>
<td><a asp-action="Details"
asp-route-id="@video.Id">Details</a></td>
<td><a asp-action="Edit"
asp-route-id="@video.Id">Edit</a></td>
</tr>
[HttpGet]
public IActionResult Edit(int id)
{
...
}
return View(video);
[HttpPost]
public IActionResult Edit(int id, VideoCreateEditViewModel model)
{
...
}
var video = _videos.Get(id);
if (video == null || !ModelState.IsValid)
return View(model);
video.Title = model.Title;
video.Genre = model.Genre;
_videos.Commit();
return RedirectToAction("Details", new { id = video.Id });
The complete code for the HTTP GET Edit action:
[HttpGet]
public IActionResult Edit(int id)
{
var video = _videos.Get(id);
if (video == null) return RedirectToAction("Index");
return View(video);
}
The complete code for the HTTP POST Edit action:
[HttpPost]
public IActionResult Edit(int id, VideoCreateEditViewModel model)
{
var video = _videos.Get(id);
if (video == null || !ModelState.IsValid) return View(model);
video.Title = model.Title;
video.Genre = model.Genre;
_videos.Commit();
return RedirectToAction("Details", new { id = video.Id });
}
The idea is that you should be able to do multiple changes and add new videos before committing the changes to the database. To achieve this, you must move the SaveChanges method call to a separate method called Commit. Whenever changes should be persisted to the database, the Commit method must be called.
int Commit();
public int Commit()
{
return 0;
}
public int Commit()
{
return _db.SaveChanges();
}
public void Add(Video video)
{
_db.Add(video);
}
The complete code in the IVideoData interface:
public interface IVideoData
{
IEnumerable<Video> GetAll();
Video Get(int id);
void Add(Video newVideo);
int Commit();
}
The complete code in the SqlVideoData class:
public class SqlVideoData : IVideoData
{
private readonly VideoDbContext _db;
public SqlVideoData(VideoDbContext db)
{
_db = db;
}
public void Add(Video newVideo)
{
_db.Add(newVideo);
}
public int Commit()
{
return _db.SaveChanges();
}
public Video Get(int id)
{
return _db.Find<Video>(id);
}
public IEnumerable<Video> GetAll()
{
return _db.Videos;
}
}
A partial view has two main purposes. The first is to render a portion of a view; the other is to enable the reuse of markup to clean up the code in a view.
To render a partial view, you can use either the synchronous @Html.Partial method or the asynchronous @Html.PartialAsync method, or you can use the <partial> Tag Helper. Both methods take two parameters, where the first is the name of the partial view and the second is an optional model object.
Note that partial views always use data from the parent view model.
The following example would render a partial view called _VideoPartial that receives a video object from the parent view’s model. The first code line is synchronous while the second is asynchronous; you choose which one you want to use.
@Html.Partial("_VideoPartial", video);
@await Html.PartialAsync("_VideoPartial", video);
<partial name="_VideoPartial" model="video"/>
Let’s create a partial view called _VideoPartial to clean up the Index view. It will display the videos as sections, and get rid of that ugly table in the process.
@model VideoViewModel
<h3>@Model.Title</h3>
<div>@Model.Genre</div>
<div>
<a asp-action="Details" asp-route-id="@Model.Id">Details</a>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a>
</div>
@foreach (var video in Model)
{
<partial name="_VideoPartial" model="video"/>
}
<div>
<a asp-action="Create">Create</a>
</div>
The complete code for the partial view:
@model VideoViewModel
<section>
<h3>@Model.Title</h3>
<div>@Model.Genre</div>
<div>
<a asp-action="Details" asp-route-id="@Model.Id">Details</a>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a>
</div>
</section>
The complete code for the Index view:
@model IEnumerable<VideoViewModel>
@{ ViewBag.Title = "Home"; }
@foreach (var video in Model)
{
<partial name="_VideoPartial" model="video"/>
}
<div>
<a asp-action="Create">Create</a>
</div>
A View Component is almost a complete MVC abstraction. It is a partial view that has its own model, which it gets from a method called Invoke in a controller-like class. A View Component’s model is independent from the current view’s model. You should not use a regular partial view, with a model, from the _Layout view, since it has no model and it is difficult to get one into it. Use a View Component to render partial content in the _Layout view.
In previous versions of MVC, you use @Html.ActionHelper to execute a child action. In this version of MVC it has been replaced with the View Component.
You can look at a View Component as having a controller that you never route to.
View Component views are always placed in a folder called Components inside the Views folder. If you place the folder in the Views/Shared folder, the view can be used from any view. Each View Component has a subfolder in the Components folder with the same name as the View Component.
Let’s implement a View Component that uses the IMessageService service to display the configuration message in every view.
public class Message : ViewComponent { }
private readonly IMessageService _message;
public Message(IMessageService message)
{
_message = message;
}
public IViewComponentResult Invoke()
{
}
var model = _message.GetMessage();
return View("Default", model);
@model string
<section>
<small>@Model</small>
</section>
<footer>
@RenderSection("footer", false)
@await Component.InvokeAsync("Message")
</footer>
The complete code for the Message View Component:
public class Message : ViewComponent
{
private IMessageService _message;
public Message(IMessageService message)
{
_message = message;
}
public IViewComponentResult Invoke()
{
var model = _message.GetMessage();
return View("Default", model);
}
}
The complete markup for the Default view:
@model string
<section>
<small>@Model</small>
</section>
The complete code for the _Layout view:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<div>
@RenderBody()
</div>
<footer>
@RenderSection("footer", false)
@await Component.InvokeAsync("Message")
</footer>
</body>
</html>
In this chapter, you worked with layout views and partial views. You also used new features, such as Tag Helpers, View Components, and the _ViewStart and _ViewImport views.
Using these features allows you to reuse code and decompose a large view into smaller, more maintainable, pieces. They give you the ability to write maintainable and reusable code.
Join our mailing list to receive the latest news and updates from our team.
Don't worry, your information will not be shared.
50% Complete
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.