Layout (Bố cục) trong ASP.NET Core
Nguồn: Layout in ASP.NET Core
Bởi Steve Smith và Dave Brock
Các trang và view thường chia sẻ các thành phần trực quan và lập trình chung. Bài viết này hướng dẫn cách:
- Sử dụng layout (bố cục) chung.
- Chia sẻ các directive (chỉ thị).
- Chạy code chung trước khi render (hiển thị) trang hoặc view.
Tài liệu này thảo luận về layout cho hai cách tiếp cận khác nhau trong ASP.NET Core MVC: Razor Pages và controllers với views. Với bài viết này, sự khác biệt là tối thiểu:
- Razor Pages nằm trong thư mục Pages.
- Controllers với views sử dụng thư mục Views cho các view.
Layout là gì?
Hầu hết các ứng dụng web đều có một layout chung cung cấp cho người dùng trải nghiệm nhất quán khi họ điều hướng từ trang này sang trang khác. Layout thường bao gồm các thành phần giao diện người dùng phổ biến như header (tiêu đề) ứng dụng, các thành phần điều hướng hoặc menu, và footer (chân trang).
Các cấu trúc HTML phổ biến như script và stylesheet cũng thường được sử dụng bởi nhiều trang trong ứng dụng. Tất cả các thành phần chung này có thể được định nghĩa trong một file layout, mà bất kỳ view nào trong ứng dụng đều có thể tham chiếu đến. Layout giúp giảm code trùng lặp trong các view.
Theo quy ước, layout mặc định cho một ứng dụng ASP.NET Core được đặt tên là _Layout.cshtml. Các file layout cho các dự án ASP.NET Core mới được tạo bằng template là:
- Razor Pages:
Pages/Shared/_Layout.cshtml
!Thư mục Pages trong Solution Explorer
- Controller với views:
Views/Shared/_Layout.cshtml
!Thư mục Views trong Solution Explorer
Layout định nghĩa một template (khuôn mẫu) cấp cao nhất cho các view trong ứng dụng. Ứng dụng không bắt buộc phải có layout. Ứng dụng có thể định nghĩa nhiều hơn một layout, với các view khác nhau chỉ định các layout khác nhau.
Đoạn code dưới đây hiển thị file layout cho một dự án được tạo bằng template với controller và views:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplication1</title>
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-page="/Index" class="navbar-brand">WebApplication1</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-page="/Index">Home</a></li>
<li><a asp-page="/About">About</a></li>
<li><a asp-page="/Contact">Contact</a></li>
</ul>
</div>
</div>
</nav>
<partial name="_CookieConsentPartial" />
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© 2018 - WebApplication1</p>
</footer>
</div>
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>
@RenderSection("Scripts", required: false)
</body>
</html>Chỉ định một Layout
Razor views có thuộc tính Layout. Các view riêng lẻ chỉ định một layout bằng cách thiết lập thuộc tính này:
@{
Layout = "_Layout";
}Layout được chỉ định có thể sử dụng đường dẫn đầy đủ (ví dụ: /Pages/Shared/_Layout.cshtml hoặc /Views/Shared/_Layout.cshtml) hoặc tên một phần (ví dụ: _Layout). Khi tên một phần được cung cấp, Razor view engine sẽ tìm kiếm file layout bằng quy trình khám phá tiêu chuẩn của nó. Thư mục nơi method handler (hoặc controller) tồn tại được tìm kiếm trước, sau đó là thư mục Shared. Quy trình khám phá này giống với quy trình được sử dụng để khám phá partial views.
Theo mặc định, mỗi layout phải gọi RenderBody. Bất cứ nơi nào lệnh gọi RenderBody được đặt, nội dung của view sẽ được render.
Sections (Phần)
Một layout có thể tùy chọn tham chiếu đến một hoặc nhiều section (phần), bằng cách gọi RenderSection. Sections cung cấp một cách để tổ chức vị trí của một số thành phần trang. Mỗi lệnh gọi RenderSection có thể chỉ định liệu section đó là bắt buộc hay tùy chọn:
<script type="text/javascript" src="~/scripts/global.js"></script>
@RenderSection("Scripts", required: false)Nếu một section bắt buộc không được tìm thấy, một ngoại lệ sẽ được ném ra. Các view riêng lẻ chỉ định nội dung cần render trong một section bằng cú pháp Razor @section. Nếu một trang hoặc view định nghĩa một section, nó phải được render (hoặc sẽ xảy ra lỗi).
Ví dụ định nghĩa @section trong Razor Pages view:
@section Scripts {
<script type="text/javascript" src="~/scripts/main.js"></script>
}Trong code trên, scripts/main.js được thêm vào section scripts trên một trang hoặc view. Các trang hoặc view khác trong cùng ứng dụng có thể không yêu cầu script này và sẽ không định nghĩa section scripts.
Markup sau đây sử dụng Partial Tag Helper để render _ValidationScriptsPartial.cshtml:
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}Scaffolding Identity đã tạo ra markup trước đó.
Các section được định nghĩa trong một trang hoặc view chỉ có sẵn trong trang layout trực tiếp của nó. Chúng không thể được tham chiếu từ các partial, view components (thành phần view), hoặc các phần khác của hệ thống view.
Bỏ qua sections
Theo mặc định, trang layout phải render phần body và tất cả các section trong một trang nội dung. Razor view engine thực thi điều này bằng cách theo dõi xem body và mỗi section có được render hay không.
Để hướng dẫn view engine bỏ qua body hoặc sections, hãy gọi các method IgnoreBody và IgnoreSection.
Body và mỗi section trong một Razor page phải được render hoặc bỏ qua.
Import các Directive (Chỉ thị) Dùng Chung
Views và pages có thể sử dụng Razor directives để import namespace và sử dụng dependency injection. Các directive được chia sẻ bởi nhiều view có thể được chỉ định trong một file _ViewImports.cshtml chung. File _ViewImports hỗ trợ các directive sau:
@addTagHelper@removeTagHelper@tagHelperPrefix@using@model@inherits@inject@namespace
File không hỗ trợ các tính năng Razor khác, như functions và định nghĩa section.
Một file _ViewImports.cshtml mẫu:
@using WebApplication1 @using WebApplication1.Models @using WebApplication1.Models.AccountViewModels @using WebApplication1.Models.ManageViewModels @using Microsoft.AspNetCore.Identity @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
File _ViewImports.cshtml cho một ứng dụng ASP.NET Core MVC thường được đặt trong thư mục Pages (hoặc Views). File _ViewImports.cshtml có thể được đặt trong bất kỳ thư mục nào, trong trường hợp đó nó sẽ chỉ được áp dụng cho các trang hoặc view trong thư mục đó và các thư mục con của nó. Các file _ViewImports được xử lý bắt đầu từ cấp root và sau đó cho mỗi thư mục dẫn đến vị trí của trang hoặc view. Các cài đặt _ViewImports được chỉ định ở cấp root có thể bị ghi đè ở cấp thư mục.
Ví dụ, giả sử:
- File
_ViewImports.cshtmlcấp root bao gồm@model MyModel1và@addTagHelper *, MyTagHelper1. - File
_ViewImports.cshtmltrong một thư mục con bao gồm@model MyModel2và@addTagHelper *, MyTagHelper2.
Các trang và view trong thư mục con sẽ có quyền truy cập vào cả Tag Helpers và model MyModel2.
Nếu nhiều file _ViewImports.cshtml được tìm thấy trong hệ thống phân cấp file, hành vi kết hợp của các directive là:
@addTagHelper,@removeTagHelper: Tất cả đều chạy, theo thứ tự.@tagHelperPrefix: Cái gần view nhất ghi đè bất kỳ cái nào khác.@model: Cái gần view nhất ghi đè bất kỳ cái nào khác.@inherits: Cái gần view nhất ghi đè bất kỳ cái nào khác.@using: Tất cả đều được bao gồm; các bản trùng lặp bị bỏ qua.@inject: Với mỗi thuộc tính, cái gần view nhất ghi đè bất kỳ cái nào khác có cùng tên thuộc tính.
Chạy Code Trước Mỗi View
Code cần chạy trước mỗi view hoặc page nên được đặt trong file _ViewStart.cshtml. Theo quy ước, file _ViewStart.cshtml nằm trong thư mục Pages (hoặc Views). Các câu lệnh được liệt kê trong _ViewStart.cshtml được chạy trước mỗi full view (không phải layouts, và không phải partial views). Giống như ViewImports.cshtml, _ViewStart.cshtml có tính phân cấp. Nếu một file _ViewStart.cshtml được định nghĩa trong thư mục view hoặc pages, nó sẽ chạy sau file được định nghĩa trong root của thư mục Pages (hoặc Views) (nếu có).
Một file _ViewStart.cshtml mẫu:
@{
Layout = "_Layout";
}File trên chỉ định rằng tất cả các view sẽ sử dụng layout _Layout.cshtml.
_ViewStart.cshtml và _ViewImports.cshtml thường không được đặt trong thư mục /Pages/Shared (hoặc /Views/Shared). Các phiên bản cấp ứng dụng của các file này nên được đặt trực tiếp trong thư mục /Pages (hoặc /Views).