Chương 24 Model Binding Model binding là quá trình của việc tạo ra các đối tượng NET sử dụng dữ liệu được gửi bởi trình duyệt trong một yêu cầu HTTP.. The Invoker action, mà tôi giới t
Trang 1Chương 24
Model Binding
Model binding là quá trình của việc tạo ra các đối tượng NET sử dụng dữ liệu được
gửi bởi trình duyệt trong một yêu cầu HTTP Tôi đã dựa vào quy trình model binding mỗi lần tôi đã định nghĩa một phương thức action có một tham số Tham số của phương thức action được tạo ra thông qua model binding từ dữ liệu trong yêu cầu Trong chương này, tôi sẽ cho bạn thấy cách hệ thống model binding làm việc và minh họa các kỹ thuật cần thiết để tùy chỉnh nó để khai thác nhiều lợi ích hơn sử dụng Table 24-1: tóm tắt nội dung chương
Chuẩn bị Project minh họa
Tôi đã tạo ra một project Visual Studio mới gọi là MvcModels không sử dụng template và lựa chọn các tùy chọn để bao gồm các thư mục core MVC và references Tôi sẽ sử dụng cùng một lớp model mà bạn đã thấy trong các chương trước, do đó tôi tạo ra một tập tin lớp mới gọi là Person.cs trong thư mục Models và đảm bảo rằng các nội dung phù hợp với Bảng listing 24-1
Listing 24-1 Nội dung của tập tin the Person.cs
using System;
Trang 2namespace MvcModels.Models
{
public class Person
{
public int PersonId { get ; set ; }
public string FirstName { get ; set ; }
public string LastName { get ; set ; }
public DateTime BirthDate { get ; set ; }
public Address HomeAddress { get ; set ; }
public bool IsApproved { get ; set ; }
public Role Role { get ; set ; }
}
public class Address
{
public string Line1 { get ; set ; }
public string Line2 { get ; set ; }
public string City { get ; set ; }
public string PostalCode { get ; set ; }
public string Country { get ; set ; }
Listing 24-2 Nội dung tập tin HomeController.cs
private Person[] personData = {
new Person {PersonId = 1, FirstName = "Adam" , LastName =
Trang 3< label > Role: </ label > @Html.DisplayFor(m => m.Role) </ div >
Cuối cùng, tôi tạo ra thư mục Views /Shared và thêm một layout được gọi là _Layout.cshtml, nội dung của nó có thể được nhìn thấy trong Listing 24-4
Listing 24-4 Nội dung của tập tin _Layout.cshtml
<! DOCTYPE html >
< html >
< head >
< meta name ="viewport" content ="width=device-width" />
< title > @ViewBag.Title </ title >
Trang 4Hiểu về Model Binding
Model binding là một cầu nối tao nhã giữa các yêu cầu HTTP và các phương pháp C
# mà định nghĩa action Hầu hết các ứng dụng MVC Framework dựa vào mô hình liên kết với chừng mực nào đó, bao gồm cả các ứng dụng ví dụ đơn giản mà tôi tạo ra trong phần trước Để xem model binding làm việc, khởi động ứng dụng và điều hướng đến / Home / Index / 1 Kết quả được minh họa trong hình 24-1
Trang 5URL chứ giá trị của thuộc tính PersonId của đối tượng Person mà tôi muốn xem: /Home/Index/1
MVC Framework biên dịch một phần của URL này và sử dụng đối số khi nó gọi phương thức Index trong lớp controller home để phục vụ yêu cầu
Listing 24-5 Nội dung của tập tin RouteConfig.cs
Trang 6quá trình model binding , phần quan trọng là biến phân đoạn tùy chọn id Khi tôi chuyển đến / Home / Index / 1 URL, đoạn cuối cùng của URL, trong đó xác định các đối tượng người mà tôi muốn lấy thông tin, được gán cho biến id định tuyến
The Invoker action, mà tôi giới thiệu trong chương 17, sử dụng thông tin định tuyến
để tìm ra rằng các phương thức action Index được yêu cầu để phục vụ yêu cầu, nhưng
nó không thể gọi phương thức Index cho đến khi nó đã có giá trị cho các đối số của phương thức
The Invoker action mặc định, ControllerActionInvoker, (được giới thiệu trong chương 17), dựa vào model bindersh để tạo ra các đối tượng dữ liệu được yêu cầu để gọi các action Model binders được xác định bởi interface IModelBinder, được thể hiện trong Listing 24-6 Tôi sẽ trở lại với giao diện này sau trong chương này khi tôi chỉ cho bạn cách để tạo ra mmodel binders tùy chỉnh
Listing 24-6 Interface IModelBinder trong MVC Framework
Ví dụ trong phần này, các invoker hành động sẽ kiểm tra các phương thức Index và thấy rằng nó có một tham số int Sau đó nó sẽ xác định vị trí các binder chịu trách nhiệm về các giá trị int và gọi phương thức BindModel của nó
Các model binder chịu trách nhiệm cung cấp một giá trị int có thể được sử dụng để gọi phương thức Index Điều này thường có nghĩa là chuyển đổi một số phần tử của các dữ liệu yêu cầu (như form hoặc giá trị chuỗi truy vấn), nhưng MVC Framework không đặt bất kỳ giới hạn về thu thập dữ liệu
Tôi sẽ chỉ cho bạn một số ví dụ về tùy chỉnh binder trong chương này Tôi cũng giới thiệu bạn một số tính năng của lớp ModelBindingContext, được truyền cho phương thức IModelBinder.BindModel
Trang 7Sử dụng Model Binder mặc định
Mặc dù một ứng dụng có thể định nghĩa model binders tùy chỉnh, hầu hết chỉ dựa vào tích hợp lớp binder, DefaultModelBinder Đây là binder được sử dụng bởi các invoker action khi nó không thể tìm thấy một binder tùy chỉnh để ràng buộc các dạng Theo mặc định, mô hình bìa đó tìm kiếm bốn địa điểm, thể hiện trong Bảng 24-2, cho dữ liệu phù hợp với tên của các tham số bị ràng buộc
Table 24-2 Thứ tự mà lớp DefaultModelBinder tìm kiếm dữ liệu đối số
Các vị trí được tìm tuần tự Ví dụ, trong ví dụ đơn giản của tôi, các DefaultModelBinder tìm kiếm một giá trị cho tham số id như sau:
Tip: Khi dựa trên model binder mặc định, điều quan trọng là các tham số cho phương
thức action của bạn khớp với các thuộc tính dữ liệu bạn đang tìm kiếm Ứng dụng ví
dụ của tôi hoạt động vì tên của tham số phương thức action tương ứng với tên của một biến định tuyến Nếu tôi đặt tên cho tham số phương thức action là PersonId, model binder mặc định sẽ không thể định vị một giá trị dữ liệu phù hợp và yêu cầu của tôi đã thất bại
Trang 8Ràng buộc kiểu đơn giản
Khi xử lý với các loại tham số đơn giản, các DefaultModelBinder tìm cách để chuyển đổi các chuỗi giá trị đã được thu được từ các dữ liệu yêu cầu thành kiểu tham số sử dụng lớp System.ComponentModel.TypeDescriptor Nếu giá trị không thể được
chuyển đổi (chẳng hạn như, khi tôi cung cấp một giá trị kiểu chuỗi là apple cho một
tham số mà đòi hỏi một giá trị int), thì các DefaultModelBinder sẽ không thể liên kết với model Bạn có thể thấy vấn đề mà việc này gây ra bằng cách khởi động ứng dụng
ví dụ và điều hướng đến URL / Home / Index / apple Hình 24-2 minh họa các phản hồi từ máy chủ
Model binder mặc định binder hơi cứng đầu Nó thấy rằng một giá trị int là cần thiết
và nó sẽ cố gắng để chuyển đổi giá trị mà tôi cung cấp trong URL, apple, thành kiểu
int, mà là nguyên nhân của lỗi hiển thị trong hình Tôi có thể làm cho mọi việc dễ dàng hơn cho model binder bằng cách sử dụng một loại nullable, để cung cấp cho model binder một phương án dự phòng Bạn có thể thấy cách tôi đã áp dụng một loại nullable vào action Index trong Listing 24-7
Listing 24-7 Nội dung của tập tin HomeController.cs
public ActionResult Index(int? id) {
Person dataItem = personData.Where(p => p.PersonId == id).First();
return View(dataItem);
}
Trang 10Tip: Lưu ý rằng tôi đã giải quyết được vấn đề của các giá trị không phải số cho mô
hình chất kết dính, nhưng mà tôi vẫn có thể lấy được các giá trị int mà không có đối tượng Person nào hợp lệ được định nghĩa trong controller Home Chẳng hạn, model binder sẽ vui vẻ chuyển đổi phân đoạn cuối cùng của URL / Home / Index / -1 và / Home / Index / 500 thành giá trị int Điều này sẽ cho phép phương thức action gọi phương thức Index với một giá trị thực, nhưng vẫn sẽ dẫn đến một lỗi vì tôi không thực hiện bất kỳ kiểm tra bổ sung nào trong controller Tôi khuyên bạn nên chú ý đến phạm vi của các giá trị tham số phương thức action của bạn có thể nhận được và kiểm nghiệm thích hợp
CÚ PHÁP CHUYỂN ĐỔI VÙNG MIỀN NHẠY CẢM
Lớp DefaultModelBinder sử dụng cài đặt đặc tả vùng miền để thực hiện chuyển đổi kiểu từ những phần khác nhau của dữ liệu request Các giá trị được thu thập từ các URL (bao gồm định tuyến và dữ liệu chuỗi truy vấn) được chuyển đổi sử dụng chế độ chuyển đổi culture-insensitive nhưng những giá trị từ dữ liệu form thì được chuyển đổi sử dụng chế độ culture into account
Vấn đề phổ biến nhất mà điều này gây ra liên quan đến các giá trị DateTime Giá trị ngày Culture-insensitive được kỳ vọng sẽ là định dạng phổ biến: yyyy-mm-dd Giá trị ngày Form được kỳ vọng sẽ được định dạng theo chỉ định của máy chủ Điều này có nghĩa rằng một máy chủ cài đặt với văn hóa Anh sẽ kỳ vọng ngày ở định dạng dd-
Trang 11yyyy, trong khi một máy chủ thiết lập với văn hóa Mỹ sẽ kỳ vọng định dạng dd-yyyy, mặc dù trong cả hai trường hợp thì định dạng yyyy-mm- dd đều được chấp nhận
mm-Một giá trị ngày sẽ không được chuyển đổi nếu nó không phải là định dạng phù hợp Điều này có nghĩa rằng bạn phải chắc chắn rằng tất cả các ngày trong URL được thể hiện trong định dạng phổ thông Bạn cũng phải thận trọng khi xử lý các giá trị ngày
mà người dùng cung cấp Các binder mặc định giả định rằng người sử dụng sẽ thể hiện ngày tháng trong định dạng của các nền văn hóa máy chủ, một điều dường như luôn luôn không xảy ra trong một ứng dụng MVC có người dùng quốc tế
Ràng buộc kiểu phức tạp
Khi tham số phương thức action là một kiểu phức tạp (ví dụ, bất kỳ loại nào mà không thể được chuyển đổi bằng cách sử dụng lớp TypeConverter), thì các lớp DefaultModelBinder sử dụng ánh xạ để có được bộ thuộc tính public và sau đó lần lượt liên kết chúng với nhau Để minh chứng cho cách làm việc này, tôi đã bổ sung thêm hai phương pháp hành động mới vào controller Home
Listing 24-9 Nội dung của tập tin the HomeController.cs
private Person[] personData = {
new Person {PersonId = 1, FirstName = "Adam" , LastName =
Trang 12Phương thức CreatePerson mà không có bất kỳ tham số nào tạo ra một đối tượng
/Views/Home/CreatePerson.cshtml view mà tôi đã tạo ra để hỗ trợ cho phương thức action và bạn có thể thấy nội dung của nó trong Listing 24-10
Listing 24-10 Nội dung của tập tin CreatePerson.cshtml
< div > @Html.LabelFor(m => m.Role)@Html.EditorFor(m => m.Role) </ div >
< button type ="submit"> Submit </ button >
}
View này tạo một tập hợp đơn giản các label và editorcho các thuộc tính của một đối tượng Person và chứa một thẻ <form> de063 post back các dữ liệu đã được chỉnh sửa cho phương thức action CreatePerson (phiên bản được thêm vào thuộc tính mô tả: HttpPost) Phương thức action này sử dụng view /Views/Home/Index.cshtml để hiển thị các dữ liệu mà form chứa Bạn có thể thấy các phương thức action mới làm việc bằng cách khỏi động ứng dụng và điều hướng đến / Home / CreatePerson, như trong Hình 24-5
Trang 13Trong trường hợp này, tôi tạo ra một kiểu model binding khác khi tôi post back các form trở lại phương thức CreatePerson Model binder mặc định phát hiện ra rằng phương thức action đòi hỏi một đối tượng Person và xử lý các thuộc tính của nó một cách lần lượt Đối với mỗi tài sản kiểu đơn giản, binder cố gắng định vị một giá trị yêu cầu, cũng giống như việc nó đã làm trong ví dụ trước Vì vậy, ví dụ, khi gặp phải thuộc tính PersonId, binder sẽ tìm kiếm một giá trị dữ liệu PersonId, cái mà nó tìm thấy trong các dữ liệu của form trong request
Nếu một thuộc tính đòi hỏi một loại phức tạp, thì quá trình này được lặp lại cho kiểu mới Tập hợp các thuộc tính public được lấy và binder cố gắng tìm kiếm các giá trị cho tất cả chúng Sự khác biệt là các tên thuộc tính được lồng nhau Chẳng hạn, thuộc tính HomeAddress của lớp Person là các loại Địa chỉ, được thể hiện trong Listing 24-
11
Listing 24-11 Nội dung của tập tin Person.cs
public class Address
{
public string Line1 { get ; set ; }
public string Line2 { get ; set ; }
public string City { get ; set ; }
public string PostalCode { get ; set ; }
public string Country { get ; set ; }
}
Trang 14
Khi tìm kiếm một giá trị cho thuộc tính line1, model binder tìm kiếm một giá trị dành cho HomeAddress.Line1, như trong tên của thuộc tính trong đối tượng model kết hợp với tên của thuộc tính trong loại thuộc tính
Tạo HTML ràng buộc dễ dàng
Việc sử dụng các tiền tố có nghĩa là tôi phải thiết kế views mà đưa nó vào tài khoản,
dù phương thức helper làm cho điều này dễ thực hiện Trong Listing 24-12, bạn có thể nhìn thấy cách tôi đã cập nhật các tập tin view CreatePerson.cshtml để tôi giữ lại một các thuộc tính cho loại Địa chỉ
Listing 24-12 Nội dung của tập tin CreatePerson.cshtml
Trang 15Như một kết quả của tính năng này, tôi không phải làm bất kỳ hành động đặc biệt để đảm bảo rằng model binder có thể tạo ra đối tượng Địa chỉ cho thuộc tính HomeAddress Tôi có thể minh hoạc điều này bằng cách chỉnh sửa view /Views/Home/Index.cshtml để hiển thị các thuộc tính HomeAddress khi được gửi từ form, như trong Listing 24-13
Listing 24-13 Nội dung của tập tin Index.cshtml
Trang 16Chỉ định Prefixes tùy chỉnh
Trong nhiều trường hợp khi HTML bạn tạo ra liên quan tới một loại đối tượng, nhưng bạn muốn để ràng buộc nó cho một cái khác Điều này có nghĩa rằng các tiền tố chứa các view sẽ không tương ứng với cấu trúc model binder đang kỷ vọng và dữ liệu của bạn sẽ không được xử lý đúng cách Để minh họa trường hợp này, tôi đã tạo ra một tập tin lớp mới gọi là AddressSummary.cs trong thư mục Models Bạn có thể xem nội dung của tập tin này trong Liệt kê 24-14
Listing 24-14 Nội dung của tập tin AddressSummary.cs
namespace MvcModels.Models
{
public class AddressSummary
{
public string City { get ; set ; }
public string Country { get ; set ; }
Trang 17namespace MvcModels.Controllers
{
public class HomeController : Controller
{
// other methods and statements omitted for brevity
public ActionResult DisplaySummary(AddressSummary summary)
Listing 24-16 Nội dung của tập tin DisplaySummary.cshtml
< label > Country: </ label > @Html.DisplayFor(m => m.Country) </ div >
View này sẽ hiển thị các giá trị của hai thuộc tính được xác định bởi lớp Address Summary Để minh họa vấn đề với các tiền tố khi ràng buộc với các loại model khác nhau, tôi sẽ thay đổi lời gọi đến phương thức helper BeginForm trong tập tin /Views/Home/CreatePerson.cshtml sao cho form được gửi trở lại với phương thức action DisplaySummary mới, như Listing 24-17
Listing 24-17 Nội dung của tập tin CreatePerson.cshtml
Trang 18Vấn đề là do thuộc tính mô tả name trong form có prefix HomeAddress, model binder không tìm kiếm nó khi cố gắn ràng buộc vào kiểu AddressSummary Tôi có thể sửa chữa lỗi này bằng cách áp dụng thuộc tính mô tả Bind vào tham số của phương thức hành động, để báo cho binder cái prefix nào cần phải tìm
Listing 24-18 Nội dung của tập tin HomeController.cs
Trang 19Thuộc tính ràng buộc lựa chọn
Hãy tưởng tượng rằng thuộc tính Country của lớp AddressSummary là đặc biệt nhạy
cảm và tôi không muốn người dùng có thể đặt giá trị cho nó Việc đầu tiên tôi có thể làm là không cho người dùng nhìn thấy thuộc tính này hoặc thậm chí ngăn chặn các thuộc tính không có trong HTML được gửi cho trình duyệt, sử dụng các thuộc tính đã tôi đã giới thiệu trong Chương 22, hoặc chỉ đơn giản bằng cách không thêm editors cho các thuộc tính đó vào view
Tuy nhiên, một người dùng tiêu cực có thể đơn giản chỉnh sửa các dữ liệu form gửi
đến máy chủ khi submit dữ liệu form và chọn giá trị cho thuộc tính Country phù hợp
với họ Những gì tôi thực sự muốn làm là báo cho model binder để không ràng buộc
một giá trị cho thuộc tính Country từ yêu cầu, mà tôi có thể làm được bằng cách sử dụng thuộc tính mô tả Bind trên các tham số phương thức action Trong Listing 24-19,
bạn có thể nhìn thấy cách tôi đã sử dụng các thuộc tính để ngăn người dùng đặt một
giá trị cho thuộc tính Country trong các phương thức action DisplaySummary trong
controller Home
Listing 24-19 Nội dung của tập tin HomeController.cs
public ActionResult DisplaySummary(
[Bind(Prefix="HomeAddress", Exclude="Country")]AddressSummary summary)
{
return View(summary);
}