Search This Blog

Wednesday, July 26, 2017

Cải thiện Performance của site ASP.NET MVC sử dụng Async Partial View

Trong website ASP.NET MVC, thông thường chúng ta sử dụng ActionResult để trả về kết quả và hiển thị lên View. Đôi khi kết quả trả về phải xử lí qua nhiều bước. Điều này có thể ảnh hưởng đến Performance của website khi phải chờ một khoảng thời gian nhất định để hiển thị được kết quả mong muốn.
Trong bài viết này, Kiên sẽ giới thiệu một cách xử lí khá là đơn giản, có thể áp dụng ngay trước khi chúng ta nghĩ đến các giải pháp tối ưu Performance khác. Đó là áp dụng nguyên tắc CHIA ĐỂ TRỊ, tức là thay vì chúng ta xử lí tất cả cùng một lúc và hiển thị kết quả đồng thời (Synchronous), thì chúng ta chia nhỏ các xử lí ra thực hiện song song (Asynchronous) và hiển thị kết quả trả về của từng xử lí đó. Trong ASP.NET MVC, chúng ta có thể sử dụng PartialView để làm việc này.

Lấy ví dụ hãy hình dung chúng ta có trang Dashboard hiển thị hai danh sách User Course như hình bên dưới:

Hãy bắt đầu với cách xử lí trả về một kết quả duy nhất. Giả sử chúng ta có các class Model như bên dưới:
   public class User  
   {  
     public string Id { get; set; }  
     public string Name { get; set; }  
   }  
   public class Course  
   {  
     public string Id { get; set; }  
     public string Title { get; set; }  
   }  
   public class Dashboard  
   {  
     public List<User> Users { get; set; }  
     public List<Course> Courses { get; set; }  
     // something more  
   }  

Chúng ta cũng có một Action xử lí và trả kết quả lên trang Dashboard.
   public class HomeController : Controller  
   {  
     public ActionResult Index()  
     {  
       // get dashboard data model  
       var dashBoardModel = new Dashboard();  
       dashBoardModel.Users = GetUsers();  
       dashBoardModel.Courses = GetCourses();  

       // return dashboard page with data model  
       return View(dashBoardModel);  
     }  
   }  

Dữ liệu hiển thị trên trang Dashboard bao gồm danh sách User và Course được xử lí trực tiếp trong action Index.

Method GetCourses: trả về danh sách Courses, đồng thời thêm dừng một khoảng thời gian là 2s để minh họa.
     private List<Course> GetCourses()  
     {  
       System.Threading.Thread.Sleep(2000);  
       return new List<Course>()  
       {  
         new Course { Id = Guid.NewGuid().ToString(), Title = "Chemistry"},  
         new Course { Id = Guid.NewGuid().ToString(), Title = "Math"},  
       };  
     }  

Tương tự là method GetUsers cũng dừng một khoảng thời gian 2s.
     private List<User> GetUsers()  
     {  
       System.Threading.Thread.Sleep(2000);  
       return new List<User>()  
       {  
         new User { Id = Guid.NewGuid().ToString(), Name = "Chu Hong Kien" },  
         new User { Id = Guid.NewGuid().ToString(), Name = "Bill Gates" },  
       };  
     }  

Nội dung trên view Index:
 @model AsyncPartialView.Controllers.Dashboard  
 @{  
   ViewBag.Title = "Index";  
 }  
 <h2>User</h2>  
 <table class="table">  
   <tr>  
     <th>Id</th>  
     <th>Name</th>  
   </tr>  
 @foreach (var item in Model.Users) {  
   <tr>  
     <td>@item.Id</td>  
     <td>@item.Name</td>  
   </tr>  
 }  
 </table>  
 <h2>Course</h2>  
 <table class="table">  
   <tr>  
     <th>Id</th>  
     <th>Title</th>  
   </tr>  
   @foreach (var item in Model.Courses)  
   {  
     <tr>  
       <td>@item.Id</td>  
       <td>@item.Title</td>  
     </tr>  
   }  
 </table>  

Ok! Chạy thử và đây là kết quả xử lí của trang Dashboard.
Thời gian phản hồi để tải hết nội dung trang (HTML) là ~ 4s. Bởi vì chúng ta có hai xừ lí, mỗi xử lí 2s, và vì chúng xử lí đồng bộ (Synchronous) nên tổng thời gian hoàn thành để hiển thị được nội dung là 4s. Khá là bad nếu bắt người dùng ngồi chờ và không làm gì trong khoảng thời gian này. :(.

Next. Giải pháp của chúng ta là:
- Bước 1: Tách hai xử lí GetCourses & GetUsers vào trong hai Action trả về hai Partial View riêng biệt. Action Index bây giờ không có bất kì xử lí nào. Để tiện việc so sánh nên Kiên return view IndexAsync.
     public ActionResult Index()  
     {  
       // get dashboard data model  
       var dashBoardModel = new Dashboard();  
       //dashBoardModel.Users = GetUsers();  
       //dashBoardModel.Courses = GetCourses();  
       // return dashboard page with data model  
       return View("IndexAsync", dashBoardModel);  
     }  
     public ActionResult _GetCourses()  
     {  
       var courses = GetCourses();  
       return PartialView("_GetCourses", courses);  
     }  
     public ActionResult _GetUsers()  
     {  
       var courses = GetUsers();  
       return PartialView("_GetUsers", courses);  
     }  

- Bước 2: Tạo PartialView cho từng Action.
PartialView _GetUsers.cshtml:
 @model IEnumerable<AsyncPartialView.Controllers.User>  
 <table class="table">  
   <tr>  
     <th>Id</th>  
     <th>Name</th>  
   </tr>  
   @foreach (var item in Model)  
   {  
     <tr>  
       <td>@item.Id</td>  
       <td>@item.Name</td>  
     </tr>  
   }  
 </table>  

PartialView _GetCourses.cshtml:
 @model IEnumerable<AsyncPartialView.Controllers.Course>  
 <table class="table">  
   <tr>  
     <th>Id</th>  
     <th>Title</th>  
   </tr>  
   @foreach (var item in Model)  
   {  
     <tr>  
       <td>@item.Id</td>  
       <td>@item.Title</td>  
     </tr>  
   }  
 </table>  

- Bước 3: Cập nhật View IndexAsync. Chúng ta tạo 2 thẻ div, có cùng class partial-content (tiện cho việc xử lí ở bước sau), data-url sẽ là URL đến hai Action (trả về 2 Partial View) ở bước 1. Có thể thêm "Loading..." hay hình gif gì đó để tăng trải ngiệm người dùng.
 @model AsyncPartialView.Controllers.Dashboard  
 @{  
   ViewBag.Title = "Index";  
 }  
 <h2>User</h2>  
 <div class="partial-content" data-url="@Url.Action("_GetUsers", "Home")">  
   Loading...  
 </div>  
 <h2>Course</h2>  
 <div class="partial-content" data-url="@Url.Action("_GetCourses", "Home")">  
   Loading...  
 </div>  

- Bước 4: Sử dụng JQuery để call & load từng action.
 <script type="text/javascript">  
   $(document).ready(function () {  
     $(".partial-content").each(function (index, item) {  
       var url = $(item).data("url");  
       if (url && url.length > 0) {  
         $(item).load(url);  
       }  
     })  
   })  
 </script>  

- Bước 5: Kết quả
Khi mới mở trang, chúng ta sẽ thấy nội dung xử lí vẫn chưa được hiển thị mà chỉ xuất hiện trạng thái Loading...(cool)

Và sau đó là kết quả hiển thị như bình thường

Nhưng thời gian phản hồi đã giảm đi phân nửa (1/2), chỉ còn ~ 2s.

Thực chất tổng thời gian xử lí là xấp xỉ như nhau (~4s), nhưng vì chúng xử lí bất đồng bộ (Asynchronous) và song song, nên ngay khi anh nào xử lí xong, chúng sẽ hiển thị kết quả ngay mà không cần phải chờ đợi nhau nữa.

OK! Đây là một trong các phương pháp cải thiện Performance cho website ASP.NET MVC của chúng ta trong trường hợp trang của chúng ta có nhiều xử lí cùng một lúc, đồng thời cũng làm tăng trải nghiệm người dùng (UX).

Source code minh họa Ở ĐÂY.
-----
Share to be shared
-----

No comments:

Post a Comment