Output dữ liệu trong MVC
I. Trả về HTML bằng cách trả lại một view Một kiểu phản hồi phồ biến nhất của một action là tạo ra HTML và gửi nó về trình duyệt của người dùng. Khi chúng ta sử dụng hệ thống action result, chúng ta cần khởi tạo một đối tượng lớp ViewResult mà chúng ta muốn trả lại để tạo ra mã HTML như đoạn mã ...
I. Trả về HTML bằng cách trả lại một view
Một kiểu phản hồi phồ biến nhất của một action là tạo ra HTML và gửi nó về trình duyệt của người dùng. Khi chúng ta sử dụng hệ thống action result, chúng ta cần khởi tạo một đối tượng lớp ViewResult mà chúng ta muốn trả lại để tạo ra mã HTML như đoạn mã ví dụ sau
using System.Web.Mvc; namespace ControllersAndActions.Controllers { public class ExampleController : Controller { public ViewResult Index() { return View("Homepage"); } } }
Trong đoạn mã bên trên, chúng ta sử dụng phương View helper để tạo thể hiện của lớp ViewResult, cái mà sẽ được trả về như là kết quả của action method
Chúng ta chỉ định view nào mà chúng ta muốn dùng bằng cách truyền tham số cho phương thức View. Ở đây, chúng ta dùng Homepage
Chúng ta có thể tạo ra một đối tượng ViewResult một cách tường minh như đoạn mã sau:
using System.Web.Mvc; namespace ControllersAndActions.Controllers { public class ExampleController : Controller { public ViewResult Index() { return new ViewResult {ViewName = "Homepage"}; } } }
Khi MVC framework gọi ExecuteResult của đối tượng ViewResult, nó bắt đầu tìm kiếm cái View do chúng ta chỉ định. Nếu chúng ta sử dụng area trong dự án của chúng ta, thì framework sẽ tìm những vị trí như sau:
- /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.aspx
- /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.ascx
- /Areas/<AreaName>/Views/Shared/<ViewName>.aspx
- /Areas/<AreaName>/Views/Shared/<ViewName>.ascx
- /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.cshtml
- /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.vbhtml
- /Areas/<AreaName>/Views/Shared/<ViewName>.cshtml
- /Areas/<AreaName>/Views/Shared/<ViewName>.vbhtml
Chúng ta có thể nhìn thấy danh sách những nơi mà framework sẽ tìm kiếm cái view, trong đó có cả loại view engine cũ là aspx view engine (cho loại file có đuôi là aspx và ascx) mặc cho chúng ta chỉ định là Razor engine khi chúng ta tạo project. Framework cũng tìm kiếm C# và Visual Basic .Net Razor template (file cshtml cho C# và file vbhtml cho Visual Basic .Net); cú pháp Razor thì giống nhau trong các file này còn cú pháp những đoạn code khác thì theo ngôn ngữ. Khi mà framework tìm thấy view cần tìm, nó sẽ sử dụng view đó để tạo ra kết quả cho action
Nếu chúng ta không sử dụng area hoặc chúng ta sử dụng area nhưng framework không tìm thấy thấy file cần tìm thì nó sẽ tìm tiếp những vị trí sau:
- /Views/<ControllerName>/<ViewName>.aspx
- /Views/<ControllerName>/<ViewName>.ascx
- /Views/Shared/<ViewName>.aspx
- /Views/Shared/<ViewName>.ascx
- /Views/<ControllerName>/<ViewName>.cshtml
- /Views/<ControllerName>/<ViewName>.vbhtml
- /Views/Shared/<ViewName>.cshtml
- /Views/Shared/<ViewName>.vbhtml
Một lần nữa, ngay khi MVC framework kiểm tra một vị trí và file cần thiết được tìm thấy thì nó sẽ dừng công việc tìm kếm và sử dụng view đó để tạo kết quả và trả về cho người dùng.
Trình tự thư mục mà MVC framework tìm kiếm view là một ví dụ nữa cho convention over configuration. Chúng ta không cần đăng ký các file view với framework mà chúng ta chỉ cần đặt những file view vào những vị trí mà framework biết trước và framework sẽ tìm chúng.
Chúng ta có thể không cần truyền tham số cho phương thức View hay không?
Câu trả lời là có thể. Nhìn vào đoạn mã sau
using System.Web.Mvc; namespace ControllersAndActions.Controllers { public class ExampleController : Controller { public ViewResult Index() { return View(); } } }
Khi chúng ta không truyền tham số cho phương thức View thì MVC framework sẽ mặc định cho là chúng ta muốn render view có cùng tên với tên của action
II. Truyền dữ liệu từ action đến view
1. Cung cấp một đối tượng view model
Chúng ta có thể một đối tượng từ action đến view bằng cách truyền nó như là một tham số đến phương thức View như đoạn mã sau:
public ViewResult Index() { DateTime date = DateTime.Now; return View(date); }
Chúng ta truyền đối tượng DateTime như là view model. Chúng ta có thể truy xuất vào đối tượng này bằng cách dùng từ khóa Model của Razor như đoạn mã sau
@{ ViewBag.Title = "Index"; } <h2>Index</h2> The day is: @(((DateTime)Model).DayOfWeek)
Cách mà chúng ta làm theo ở trên được gọi là untyped hay weakly type view. View không biết bất cứ điều gì về đối tượng view model và cư xử với nó như là một thể hiển của đối tượng object. Để lấy được giá trị của thuộc tính DayOfWeek, chúng ta cần chuyển đổi đối tượng thành thể hiện của lớp DateTime. Cách này thì chạy, nhưng nó là làm cho code trong view của chúng ta thành một đóng bùi nhùi. Chúng ta có thể làm cho view của chúng ta sáng sủa, sạch sẽ hơn bằng cách dùng loại strongly typed view như đoạn mã sau:
@model DateTime @{ ViewBag.Title = "Index"; } <h2>Index</h2> The day is: @Model.DayOfWeek
Chúng ta chỉ định kiểu của model view bằng cách sử dụng từ khóa model của Razor. Chúng ta sử dụng chữ m in thường khi chỉ định kiểu cho model và chữ M in hoa khi chúng ta đọc giá trị. Điều này không chỉ giúp chúng ta dọn dẹp sạch sẽ view của chúng ta mà còn hỗ trợ IntelliSense cho strongly typed view
2. Truyền dữ liệu với ViewBag
Với View Bag, cho phép chúng ta định nghĩa những thuộc tính bất kì trên dynamic object và truy xuất chúng trong view. Dynamic object được truy xuất qua thuộc tính Controller.ViewBag. Đoạn mã sau là ví dụ về View Bag
public ViewResult Index() { ViewBag.Message = "Hello"; ViewBag.Date = DateTime.Now; return View(); }
Trong đoạn mã trên, chúng ta định nghĩa những thuộc tính là Message và Date đơn giản bằng cách gán những giá trị cho chúng. Và những thuộc tính này của chúng ta chưa hề tồn tại trước đó và chúng ta không hề chuẩn bị bất cứ gì để tạo ra chúng. Để đọc dữ liệu trở lại trong View, chúng ta đơn giản lấy cùng thuộc tính mà chúng ta đã set trong action như đoạn mã sau:
@{ ViewBag.Title = "Index"; } <h2>Index</h2> The day is: @ViewBag.Date.DayOfWeek The message is: @ViewBag.Message
ViewBag có sự thuận tiện hơn view model ở chỗ nó dễ dàng truyền nhiều đối tượng đến view. Nếu chúng ta bị giới hạn sử dụng model của view, khi đó chúng ta cần tạo ra một kiểu mới có những member là string và DateTime để mà đạt được kết quả như dùng ViewBag ở trên
Khi chúng ta làm việc với các đối tượng dynamic, chúng ta nhập vào trình tự của phương thức và gọi đến thuộc tính trong view như sau:
The day is: @ViewBag.Date.DayOfWeek.Blah.Blah.Blah
Tuy nhiên, Visual Studio không hỗ trợ IntelliSense những đối tượng dynamic, bao gồm ViewBag, và những lỗi sẽ xảy ra cho đến khi view được sử dụng để render.
3. Truyền dữ liệu với View Data
Tính năng View Bag được giới thiệu với phiên bản MVC 3. Trước đó, sự thay thế chính của model là view data. Tính năng của View Data tương tự như tính năng của View Bag, nhưng nó thực thi việc sử dụng lớp ViewDataDictionary hơn là việc sử dụng đối tương dynamic. Lớp ViewDataDictionary như là collection của các cặp key/value và truy xuất qua thuộc tính ViewData của lớp Controller. Ví dụ:
public ViewResult Index() { ViewData["Message"] = "Hello"; ViewData["Date"] = DateTime.Now; return View(); }
Và chúng ta đọc ngược lại các giá trị trong view như sau:
@{ ViewBag.Title = "Index"; } <h2>Index</h2> The day is: @(((DateTime)ViewData["Date"]).DayOfWeek) The message is: @ViewData["Message"]
III. Thực thi việc điều hướng
Kết quả chung chung từ một phương thức action thì không những tạo ra bất kì output một cách trực tiếp, mà còn điều hướng trình duyệt của người dùng đến một url khác. Hầu hết, url được điều hướng là một phương thức action khác trong ứng dụng, phương thức này dùng để tạo ra output mà chúng ta muốn người dùng nhìn thấy
Hầu hết việc điều hướng trong các phương thức action đó là xử lý các HTTP POST request. POST request thường được xử dụng khi chúng ta muốn thay đổi trạng thái của ứng dụng. Nếu chúng ta chỉ trả về HTML sau khi xử lý POST request, chúng ta sẽ gặp nguy hiểm nếu người dùng bấm vào nút reload của trình duyệt và submit lại form lần thứ hai, gây ra một kết quả không như và không mong đợi
Để tránh vấn đề này, chúng ta theo pattern có tên gọi là Post/Redirect/Get. Theo pattern này, chúng ta nhận một POST request, xử lý nó xong, và khi đó chúng ta điều hướng trình duyệt để mà GET request từ trình duyệt cho một URL khác.
GET request sẽ không sửa đổi trạng thái của ứng dụng chúng ta do đó việc submit lại form của request này cũng không gây ra bất cứ vấn đề gì.
Khi chúng ta thực thi việc điều hướng, chúng ta gửi một trong hai HTTP code đến trình duyệt là:
– Gửi HTTP code 302, đó là temporary redirect. Đây là loại redirect được dùng thường xuyên và, cho đến MVC 3, là loại duy nhất cho MVC framework hỗ trợ. Khi sử dụng Post/Redirect/Get pattern, đây là code mà chúng ta muốn gửi
– Gửi HTTP code 301, đó là permanent redirect. Cái này nên dùng với sự cẩn trọng bởi vì nó chỉ dẫn cho người nhận của HTTP code không yêu cầu url gốc lần nữa và sử dụng url mới bao gồm cùng với redirect code
1. Redirect đến url theo chuỗi
Cách cơ bản nhất để điều hướng trình duyệt là gọi phương thức Redirect, nó sẽ trả về một thể hiện của lớp RedirectResult như đoạn mã sau
public RedirectResult Redirect() { return Redirect("/Example/Index"); }
URL chúng ta muốn điều hướng là một chuỗi và truyền như là tham số cho phương thức Redirect. Phương thức Redirect gửi một temporary redirect. Chúng ta có thể gửi permanent redirect bằng cách dùng phương thức RedirectPermanent như đoạn mã sau:
public RedirectResult Redirect() { return RedirectPermanent("/Example/Index"); }
2. Redirect đến url của hệ thống route
Nếu chúng ta điều hướng người dùng đến một phần khác của ứng dụng, chúng ta cần bảo đảm rằng url mà chúng ta gửi là một url hợp lệ trong schema url của chúng ta. Vấn đề phát sinh khi sử dụng literal url cho việc điều hướng là bất cứ thay đổi trong schema routing của chúng ta có nghĩa là chúng ta cần phải vô code cập nhật lại tất cả url của chúng ta
Như là sự thay thế, chúng ta có thể sử dụng hệ thống route để tạo những url hợp lệ với phương thức RedirectToRoute
, phương thức này sẽ tạo ra một thể hiện của RedirectToRouteResult, như đoạn mã sau:
public RedirectToRouteResult Redirect() { return RedirectToRoute(new { controller = "Example", action = "Index", ID = "MyID" }); }
Phương thức RedirectToRoute dùng cho temporary redirect. Chúng ta sử dụng RedirectToRoutePermanent
cho permanent redirect. Cả hai có tham số là kiểu anonymous, sau đó những thuộc tính của tham số này được đưa đến hệ thống route để tạo ra url.
3. Redirect đến action
Chúng ta có thể điều hướng đến action dễ dàng hơn bằng cách sử dụng phương thức RedirectToAction. Đây là phương thức wrapper của phương thức RedirectToRoute giúp cho chúng ta chỉ định những giá trị cho action và controller mà không cần tạo kiểu anonymous. Ví dụ như sau:
public RedirectToRouteResult Redirect() { return RedirectToAction("Index"); }
Với đoạn mã trên, framework cho rằng chúng ta đang điều hướng đến một action cùng nằm trong controller hiện tại. Còn nếu chúng ta muốn điều hướng đến một action nằm trong một controller khác thì sao? Chúng ta cần cung cấp thêm tham số cho phương thức RedirectToAction như đoạn mã sau
public RedirectToRouteResult Redirect() { return RedirectToAction("Index", "MyController"); }
The RedirectToAction thực thi temporary redirect. Sử dụng RedirectToActionPermanent cho permanent redirect.
4. Bảo vệ dữ liệu xuyên suốt điều hướng
Việc điều hướng gây ra cho trình duyệt submit hoàn toàn một request HTTP mới, điều này có nghĩa là chúng ta không thể truy xuất vào chi tiết của request gốc. Nếu chúng ta muốn truyền dữ liệu từ một request này sang request kế tiếp, chúng ta có thể sử dụng tính năng Temp Data
TempData thì gần giống như Session data, ngoại trừ việc nó sẽ bị đánh dấu cho xóa đi khi chúng được đọc, và chúng sẽ bị xóa hẳn khi request được xử lý. Đây là sự sắp xếp lý tưởng cho cho những dữ liệu có thời gian sống ngắn mà chúng ta muốn nó tồn tại xuyên suốt trong điều hướng. Ví dụ đơn giản sử dụng phương thức RedirectToAction:
public RedirectToRouteResult Redirect() { TempData["Message"] = "Hello"; TempData["Date"] = DateTime.Now; return RedirectToAction("Index"); }
Khi mà phương thức này xử lý một request, nó thiết lập các giá trị trong collection TempData, và khi đó nó redirect trình duyệt của người dùng đến phương thức action Index trong cùng một controller
Chúng ta có thể đọc những giá trị trong TempData trong phương thức action mà chúng ta đã hướng tới và truyền nó sang view như sau:
public ViewResult Index() { ViewBag.Message = TempData["Message"]; ViewBag.Date = TempData["Date"]; return View(); }
Tuy nhiên, chúng ta có thể đọc trực tiếp những giá trị trong TempData như sau:
@{ ViewBag.Title = "Index"; } <h2>Index</h2> The day is: @(((DateTime)TempData["Date"]).DayOfWeek) <p /> The message is: @TempData["Message"]
Đọc những giá trị trong view có nghĩa là chúng ta không cần sử dụng tính năng View Bag hay View Data trong phương thức action. Tuy nhiên, chúng ta phải chuyển những kết quả của TempData về kiểu thích hợp.
Chúng ta có thể lấy một giá trị từ TempData không cần đánh dấu sẽ gỡ bỏ bằng cách dùng phương thức Peek như sau:
DateTime time = (DateTime)TempData.Peek("Date");
Chúng ta cũng có thể bảo vệ một giá trị không bị gỡ bỏ như sau:
TempData.Keep("Date");
Phương thức không bảo vệ một giá trị mãi mãi. Nếu giá trị được đọc lần nữa, nó sẽ bị đánh dấu là xóa một lần nữa
Nếu chúng ta muốn cất giữ những giá trị mà chúng ta không muốn chúng bị xóa một cách tự động thì chúng ta nên dùng session.
IV. Trả về dữ liệu dạng text
Để trả về dữ liệu dạng, chúng ta dùng loại action result ContentResult như sau:
public ContentResult Index() { string message = "This is plain text"; return Content(message, "text/plain", Encoding.Default); }
Chúng ta tạo ContentResult thông qua phương thức Controller.Content, có 3 tham số
– Đầu tiên là dữ liệu text mà chúng ta muốn gửi
– Thứ 2 là giá trị của HTTP content-type header cho response. Chúng ta có thể tìm thấy cái này trong lớp System.Net.Mime.MediaTypeNames
– Tham số cuối cùng là chỉ định scheme encoding sẽ dùng để chuyển đổi text vào trình tự của byte
Chúng ta có thể bỏ 2 tham số cuối, khi đó framework sẽ cho là dữ liệu của chúng ta là HTML (hay content-type là text/html). Framework sẽ cố gắng chọn định dạng encoding mà trình duyệt hỗ trợ khi nó là tạo ra request chúng ta đang xử lý. Điều này cho phép chúng ta trả về chỉ text như sau:
return Content("This is plain text");
Trong thực tế, chúng ta có thể tiến xa hơn một chút. Nếu chúng ta trả về bất cứ đối tượng nào từ phương thức action mà không phải là ActionResult, MVC framework sẽ cố gắng đồng bộ hóa dữ liệu thành chuỗi và gửi nó đến trình duyệt như là HTML như đoạn mã sau:
public object Index() { return "This is plain text"; }
V. Trả về dữ liệu dạng xml
Chúng ta cũng sử dụng phương thức Content để trả về định dạng xml. Tham số thứ 2 của chúng ta sẽ là text/xml như sau
public ContentResult XMLData() { XElement xBookParticipants = new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham"))); return Content(xBookParticipants.ToString() , "text/xml"); }
Kết quả đoạn mã trên sẽ là:
<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>
VI. Trả về dữ liệu loại json
Trong những năm gần đây, sử dụng xml trong các ứng dụng web ngày càng giảm dần, trong khi đó là sự gia tăng của JavaScript Object Notation (JSON) format. JSON thì nhẹ, định dạng text mô tả cấu trúc dữ liệu phân cấp. Dữ liệu dạng JSON là mã JavaScript hợp lệ, điều này có nghĩa nó được hỗ trợ bởi tất cả các trình duyệt chính, khiến cho nó dễ dàng làm việc hơn XML. JSON thì thường dùng cho việc gửi dữ liệu từ server đến client phản hồi cho những truy vấn bằng AJAX
MVC framework có sẵn lớp JsonResult cho chúng ta, nó sẽ làm nhiệm vụ đồng bộ hóa những đối tượng .NET thành định dạng JSON. Chúng ta có thể tạo JsonResult sử dụng phương thức Controller.Json như đoạn mã sau
public JsonResult JsonData() { var data = new[] { new {Name="Nguyễn Khánh Hưng", Job = "Programmer"}, new {Name="Đoàn Thanh Thúy", Job = "Coder"} }; return Json(data, JsonRequestBehavior.AllowGet); }
Với đoạn mã trên thì kết quả sẽ như sau
[{"Name":"Nguyễn Khánh Hưng","Job":"Programmer"},{"Name":"Đoàn Thanh Thúy","Job":"Coder"}]
VII. Trả về dữ liệu loại file và binary
FileResult là một lớp abstract cho tất cả các action result liên quan tới việc gửi dữ liệu binary tới trình duyệt. MVC framework đi cùng với ba lớp con của lớp này cho chúng ta sử dụng
– FilePathResult dùng gửi một file trực tiếp từ hệ thống file của server
– FileContentResult dùng gửi nội dung của một mảng byte nằm trong bộ nhớ sends the contents of an in-memory byte array.
– FileStreamResult dùng gửi nội dung của đối tượng System.IO.Stream đã được mở
Chúng ta không cần lo lắng về những kiểu này bởi vì chúng được tạo cho chúng ta một cách tự động bởi những phương thức Controller.File