Views (Hiển thị) trong ASP.NET Core MVC
Nguồn: Views in ASP.NET Core MVC
Bởi Steve Smith và Dave Brock
Tài liệu này giải thích views được sử dụng trong các ứng dụng ASP.NET Core MVC. Để biết thông tin về Razor Pages, xem Kiến trúc và khái niệm Razor Pages trong ASP.NET Core.
Trong mẫu Model-View-Controller (MVC), view (hiển thị) xử lý việc trình bày dữ liệu và tương tác người dùng của ứng dụng. Một view là một HTML template (mẫu HTML) với Razor markup nhúng vào. Razor markup là code tương tác với HTML markup để tạo ra một trang web được gửi đến client.
Trong ASP.NET Core MVC, views là các file .cshtml sử dụng ngôn ngữ lập trình C# trong Razor markup. Thông thường, các file view được nhóm vào các thư mục được đặt tên theo từng controller của ứng dụng. Các thư mục được lưu trữ trong thư mục Views ở gốc của ứng dụng.
Thư mục Home bên trong thư mục Views đại diện cho controller Home. Thư mục Home chứa các view cho các trang web About, Contact, và Index (trang chủ). Khi người dùng yêu cầu một trong ba trang web này, các controller actions trong controller Home xác định view nào trong ba view được sử dụng để xây dựng và trả về trang web cho người dùng.
Sử dụng layouts để cung cấp các phần trang web nhất quán và giảm sự lặp lại code. Layouts thường chứa header (tiêu đề), các phần tử điều hướng và menu, và footer (chân trang). Header và footer thường chứa markup soạn sẵn cho nhiều phần tử metadata và liên kết đến script và style assets. Layouts giúp bạn tránh markup soạn sẵn này trong các view của mình.
Partial views giảm sự trùng lặp code bằng cách quản lý các phần có thể tái sử dụng của views. Ví dụ, một partial view hữu ích cho tiểu sử tác giả trên một trang blog xuất hiện trong nhiều view.
View components tương tự như partial views ở chỗ chúng cho phép bạn giảm code lặp lại, nhưng chúng phù hợp với nội dung view yêu cầu code chạy trên server để render (hiển thị) trang web. View components hữu ích khi nội dung được render yêu cầu tương tác cơ sở dữ liệu, chẳng hạn như giỏ hàng của một trang web.
Lợi ích của việc sử dụng views
Views giúp thiết lập separation of concerns (phân tách mối quan tâm) trong một ứng dụng MVC bằng cách tách markup giao diện người dùng khỏi các phần khác của ứng dụng. Việc tuân theo thiết kế SoC làm cho ứng dụng của bạn theo dạng module (module hóa), mang lại một số lợi ích:
- Ứng dụng dễ bảo trì hơn vì nó được tổ chức tốt hơn. Views thường được nhóm theo tính năng của ứng dụng. Điều này giúp dễ dàng tìm các view liên quan khi làm việc với một tính năng.
- Các phần của ứng dụng được liên kết lỏng lẻo. Bạn có thể xây dựng và cập nhật views của ứng dụng riêng biệt với business logic và các thành phần truy cập dữ liệu.
- Dễ dàng kiểm tra các phần giao diện người dùng của ứng dụng vì các view là các đơn vị riêng biệt.
- Do tổ chức tốt hơn, ít có khả năng bạn vô tình lặp lại các phần của giao diện người dùng.
Tạo một view
Các view dành riêng cho một controller được tạo trong thư mục Views/[ControllerName]. Các view được chia sẻ giữa các controller được đặt trong thư mục Views/Shared. Để tạo một view, thêm một file mới và đặt cho nó cùng tên với action controller liên quan với phần mở rộng file .cshtml. Để tạo một view tương ứng với action About trong controller Home, tạo file About.cshtml trong thư mục Views/Home:
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<p>Use this area to provide additional information.</p>Razor markup bắt đầu bằng ký hiệu @. Chạy các câu lệnh C# bằng cách đặt code C# trong Razor code blocks (khối code Razor) được bao quanh bởi dấu ngoặc nhọn ({ ... }). Ví dụ, xem phép gán "About" cho ViewData["Title"] được hiển thị trước đó.
Controllers chỉ định views như thế nào
Views thường được trả về từ các actions như một ViewResult, là một loại ActionResult. Action method của bạn có thể tạo và trả về một ViewResult trực tiếp, nhưng điều đó không phổ biến. Vì hầu hết các controller kế thừa từ Controller, bạn đơn giản sử dụng method helper View để trả về ViewResult:
HomeController.cs:
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}Method helper View có một số overloads. Bạn có thể tùy chọn chỉ định:
- Một view rõ ràng để trả về:
``csharp return View("Orders"); ``
- Một model để truyền vào view:
``csharp return View(Orders); ``
- Cả view và model:
``csharp return View("Orders", Orders); ``
View discovery (Khám phá view)
Khi một action trả về một view, một quá trình gọi là view discovery (khám phá view) diễn ra. Quá trình này xác định file view nào được sử dụng dựa trên tên view.
Hành vi mặc định của method View (return View();) là trả về một view có cùng tên với action method mà nó được gọi từ đó. Ví dụ, tên About của ActionResult trong controller được sử dụng để tìm kiếm một file view tên là About.cshtml. Đầu tiên, runtime tìm trong thư mục Views/[ControllerName] cho view. Nếu không tìm thấy view phù hợp ở đó, nó tìm kiếm trong thư mục Shared cho view.
View discovery tìm kiếm một file view phù hợp theo thứ tự này:
Views/\[ControllerName]/\[ViewName].cshtmlViews/Shared/\[ViewName].cshtml
Đường dẫn file view có thể được cung cấp thay vì tên view. Nếu sử dụng đường dẫn tuyệt đối bắt đầu từ gốc ứng dụng (tùy chọn bắt đầu bằng "/" hoặc "~/"), phần mở rộng .cshtml phải được chỉ định:
return View("Views/Home/About.cshtml");Bạn cũng có thể sử dụng đường dẫn tương đối để chỉ định các view trong các thư mục khác nhau mà không cần phần mở rộng .cshtml. Trong HomeController, bạn có thể trả về view Index của các view Manage của bạn với một đường dẫn tương đối:
return View("../Manage/Index");Tương tự, bạn có thể chỉ ra thư mục dành riêng cho controller hiện tại với tiền tố "./":
return View("./About");Truyền dữ liệu vào views
Truyền dữ liệu vào views bằng một số cách tiếp cận:
- Dữ liệu kiểu mạnh (strongly typed): viewmodel
- Dữ liệu kiểu yếu (weakly typed):
ViewData(ViewDataAttribute)ViewBag
Dữ liệu Strongly-typed (viewmodel)
Cách tiếp cận mạnh mẽ nhất là chỉ định loại model trong view. Model này thường được gọi là viewmodel. Bạn truyền một instance (thể hiện) của kiểu viewmodel cho view từ action.
Sử dụng viewmodel để truyền dữ liệu vào view cho phép view tận dụng kiểm tra kiểu mạnh. Kiểu mạnh (hoặc strongly typed) có nghĩa là mọi biến và hằng số đều có kiểu được định nghĩa rõ ràng (ví dụ: string, int, hoặc DateTime). Tính hợp lệ của các kiểu được sử dụng trong view được kiểm tra tại thời điểm compile (biên dịch).
Visual Studio và Visual Studio Code liệt kê các thành viên class strongly typed sử dụng tính năng gọi là IntelliSense. Khi bạn muốn xem các thuộc tính của một viewmodel, gõ tên biến cho viewmodel theo sau là dấu chấm (.). Điều này giúp bạn viết code nhanh hơn với ít lỗi hơn.
Chỉ định một model bằng cách sử dụng chỉ thị @model. Sử dụng model với @Model:
@model WebApplication1.ViewModels.Address
<h2>Contact</h2>
<address>
@Model.Street<br>
@Model.City, @Model.State @Model.PostalCode<br>
<abbr title="Phone">P:</abbr> 425.555.0100
</address>Để cung cấp model cho view, controller truyền nó như một tham số:
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
var viewModel = new Address()
{
Name = "Microsoft",
Street = "One Microsoft Way",
City = "Redmond",
State = "WA",
PostalCode = "98052-6399"
};
return View(viewModel);
}Không có hạn chế về loại model bạn có thể cung cấp cho view. Chúng tôi khuyến nghị sử dụng viewmodel POCO (Plain Old CLR Object - Đối tượng CLR đơn giản) với ít hoặc không có hành vi (method) được định nghĩa. Thông thường, các class viewmodel được lưu trữ trong thư mục Models hoặc thư mục ViewModels riêng biệt ở gốc của ứng dụng.
Dữ liệu Weakly-typed (ViewData, thuộc tính [ViewData], và ViewBag)
ViewBag không có sẵn theo mặc định để sử dụng trong các class Razor Pages PageModel.
Ngoài các view kiểu mạnh, views còn có quyền truy cập vào một bộ sưu tập kiểu yếu (còn gọi là loosely typed) của dữ liệu. Không giống như kiểu mạnh, kiểu yếu (hoặc loose types) có nghĩa là bạn không khai báo rõ ràng kiểu dữ liệu bạn đang sử dụng. Bạn có thể sử dụng bộ sưu tập dữ liệu kiểu yếu để truyền lượng nhỏ dữ liệu vào và ra khỏi controllers và views.
| Truyền dữ liệu giữa ... | Ví dụ |
|---|---|
| Controller và một view | Điền vào dropdown list với dữ liệu. |
| View và một layout view | Thiết lập nội dung phần tử <title> trong layout view từ một file view. |
| Partial view và một view | Một widget hiển thị dữ liệu dựa trên trang web mà người dùng yêu cầu. |
Bộ sưu tập này có thể được tham chiếu thông qua thuộc tính ViewData hoặc ViewBag trên controllers và views. Thuộc tính ViewData là một dictionary (từ điển) của các đối tượng kiểu yếu. Thuộc tính ViewBag là một wrapper (bao bọc) xung quanh ViewData cung cấp các thuộc tính động cho bộ sưu tập ViewData cơ bản. Lưu ý: Tra cứu key không phân biệt chữ hoa/thường cho cả ViewData và ViewBag.
ViewData và ViewBag được giải quyết động tại runtime. Vì chúng không cung cấp kiểm tra kiểu tại thời điểm compile, cả hai thường dễ bị lỗi hơn khi sử dụng viewmodel.
ViewData
ViewData là một đối tượng ViewDataDictionary được truy cập thông qua các key string. Dữ liệu chuỗi có thể được lưu trữ và sử dụng trực tiếp mà không cần ép kiểu, nhưng bạn phải ép kiểu các giá trị đối tượng ViewData khác sang các kiểu cụ thể khi bạn trích xuất chúng.
public IActionResult SomeAction()
{
ViewData["Greeting"] = "Hello";
ViewData["Address"] = new Address()
{
Name = "Steve",
Street = "123 Main St",
City = "Hudson",
State = "OH",
PostalCode = "44236"
};
return View();
}Làm việc với dữ liệu trong view:
@{
// Since Address isn't a string, it requires a cast.
var address = ViewData["Address"] as Address;
}
@ViewData["Greeting"] World!
<address>
@address.Name<br>
@address.Street<br>
@address.City, @address.State @address.PostalCode
</address>Thuộc tính [ViewData]
Một cách tiếp cận khác sử dụng ViewDataDictionary là ViewDataAttribute. Các thuộc tính trên controllers hoặc Razor Page models được đánh dấu bằng thuộc tính [ViewData] có giá trị của chúng được lưu trữ và tải từ dictionary.
public class HomeController : Controller
{
[ViewData]
public string Title { get; set; }
public IActionResult About()
{
Title = "About Us";
ViewData["Message"] = "Your application description page.";
return View();
}
}Trong layout, tiêu đề được đọc từ ViewData dictionary:
<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...ViewBag
ViewBag không có sẵn theo mặc định để sử dụng trong các class Razor Pages PageModel.
ViewBag là một đối tượng Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.DynamicViewData cung cấp quyền truy cập động vào các đối tượng được lưu trữ trong ViewData. ViewBag có thể thuận tiện hơn để làm việc vì nó không yêu cầu ép kiểu.
public IActionResult SomeAction()
{
ViewBag.Greeting = "Hello";
ViewBag.Address = new Address()
{
Name = "Steve",
Street = "123 Main St",
City = "Hudson",
State = "OH",
PostalCode = "44236"
};
return View();
}@ViewBag.Greeting World!
<address>
@ViewBag.Address.Name<br>
@ViewBag.Address.Street<br>
@ViewBag.Address.City, @ViewBag.Address.State @ViewBag.Address.PostalCode
</address>Sử dụng ViewData và ViewBag đồng thời
Vì ViewData và ViewBag tham chiếu đến cùng một bộ sưu tập ViewData cơ bản, bạn có thể sử dụng cả ViewData và ViewBag và kết hợp giữa chúng khi đọc và ghi giá trị.
Tóm tắt sự khác biệt giữa ViewData và ViewBag
ViewData- Kế thừa từ
ViewDataDictionary, vì vậy nó có các thuộc tính dictionary có thể hữu ích, chẳng hạn nhưContainsKey,Add,Remove, vàClear. - Các key trong dictionary là chuỗi, vì vậy khoảng trắng được cho phép.
- Bất kỳ kiểu nào khác
stringphải được ép kiểu trong view để sử dụngViewData. ViewBag- Kế thừa từ
Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.DynamicViewData, vì vậy nó cho phép tạo các thuộc tính động bằng ký hiệu chấm. - Đơn giản hơn để kiểm tra giá trị null. Ví dụ:
@ViewBag.Person?.Name
Khi nào sử dụng ViewData hoặc ViewBag
Cả ViewData và ViewBag đều là các cách tiếp cận hợp lệ để truyền lượng nhỏ dữ liệu giữa controllers và views. Sự lựa chọn dựa trên sở thích. Cả hai cách đều được giải quyết động tại runtime và do đó dễ gây ra lỗi runtime. Một số nhóm phát triển tránh chúng.
Dynamic views (View động)
Các view không khai báo kiểu model bằng @model nhưng có một instance model được truyền cho chúng (ví dụ: return View(Address);) có thể tham chiếu các thuộc tính của instance một cách động:
<address>
@Model.Street<br>
@Model.City, @Model.State @Model.PostalCode<br>
<abbr title="Phone">P:</abbr> 425.555.0100
</address>Tính năng này cung cấp sự linh hoạt nhưng không cung cấp bảo vệ biên dịch hoặc IntelliSense.
Các tính năng view khác
Tag Helpers giúp dễ dàng thêm hành vi phía server vào các thẻ HTML hiện có. Sử dụng Tag Helpers tránh phải viết code tùy chỉnh hoặc helpers trong các view của bạn.
Tạo HTML markup tùy chỉnh có thể đạt được với nhiều HTML Helpers tích hợp sẵn. Logic giao diện người dùng phức tạp hơn có thể được xử lý bởi View Components. View components cung cấp cùng SoC mà controllers và views cung cấp. Chúng có thể loại bỏ nhu cầu về các actions và views xử lý dữ liệu được sử dụng bởi các phần tử giao diện người dùng phổ biến.
Giống như nhiều khía cạnh khác của ASP.NET Core, views hỗ trợ dependency injection (tiêm phụ thuộc), cho phép các service (dịch vụ) được tiêm vào views.
CSS isolation (Cô lập CSS)
Cô lập các style CSS cho các trang, view và component riêng lẻ để giảm hoặc tránh:
- Phụ thuộc vào các style toàn cục có thể khó bảo trì.
- Xung đột style trong nội dung lồng nhau.
Để thêm một scoped CSS file (file CSS có phạm vi) cho một trang hoặc view, đặt các style CSS trong một file .cshtml.css đi kèm khớp với tên của file .cshtml. Trong ví dụ sau, một file Index.cshtml.css cung cấp các style CSS chỉ được áp dụng cho trang hoặc view Index.cshtml.
Pages/Index.cshtml.css (Razor Pages) hoặc Views/Index.cshtml.css (MVC):
h1 {
color: red;
}CSS isolation xảy ra tại thời điểm build. Framework viết lại các CSS selectors để khớp với markup được render bởi các trang hoặc view của ứng dụng. Các style CSS được viết lại được bundle (gói) và tạo ra như một static asset, {APP ASSEMBLY}.styles.css. Placeholder {APP ASSEMBLY} là tên assembly của project.
Trong nội dung <head> của Pages/Shared/_Layout.cshtml (Razor Pages) hoặc Views/Shared/_Layout.cshtml (MVC), thêm hoặc xác nhận sự hiện diện của link đến các style CSS được bundle:
<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />Hỗ trợ CSS preprocessor
CSS preprocessors (bộ xử lý trước CSS) hữu ích để cải thiện phát triển CSS bằng cách sử dụng các tính năng như biến, lồng nhau, module, mixins, và kế thừa. Mặc dù CSS isolation không hỗ trợ CSS preprocessors như Sass hoặc Less, việc tích hợp CSS preprocessors liền mạch miễn là việc biên dịch preprocessor xảy ra trước khi framework viết lại CSS selectors trong quá trình build.
Cấu hình CSS isolation
CSS isolation cho phép cấu hình cho một số tình huống nâng cao, chẳng hạn như khi có sự phụ thuộc vào các công cụ hoặc quy trình làm việc hiện có.
Tùy chỉnh định dạng scope identifier
Theo mặc định, scope identifiers sử dụng định dạng b-{STRING}. Để tùy chỉnh định dạng scope identifier, cập nhật file project sang một mẫu mong muốn:
<ItemGroup>
<None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>Tắt automatic bundling
Để từ chối cách framework publish và tải các scoped files tại runtime, sử dụng thuộc tính DisableScopedCssBundling:
<PropertyGroup> <DisableScopedCssBundling>true</DisableScopedCssBundling> </PropertyGroup>