Thứ Năm, 9 tháng 2, 2017

Cải thiện hiệu xuất ứng dụng web ASP.NET MVC với Output Caching (C#)

Trong bài viết này mình sẽ hướng dẫn bạn làm thế nào để cải thiện hiệu suất của ứng dụng web ASP.NET MVC bằng cách tận dụng kỹ thuật output caching. Bạn sẽ tìm hiểu làm thế nào để lưu các kết quả trả về từ một action để các nội dung tương tự không cần phải được tạo ra lại khi có một người dùng khác gọi đến action đó.


Hãy tưởng tượng ứng dụng ASP.NET MVC của bạn sẽ hiển thị 1 danh sách các bản ghi (record) trong cơ sở dữ liệu trong view Index. Thông thường, bất cứ khi nào có người dùng gọi tới action trả ra view Index thì máy chủ web phải kết nối đến cơ sở dữ liệu và thực hiện truy vấn cơ sở dữ liệu. Công việc này được lặp đi lặp lại mỗi khi có người dùng truy cập vào action này.

Mặt khác nếu bạn tận dụng lợi thế của output caching thì bạn có thể tránh việc thực hiện một truy vấn cơ sở dữ liệu mỗi khi bất kỳ người dùng truy cập đến. View có thể được lấy từ cache thay vì phát sinh từ action method. Caching cho phép bạn tránh thực hiện công việc dư thừa trên máy chủ.

Kích hoạt Output Caching


Kích hoạt output caching bằng cách bổ sung attribute [OutputCache] đến 1 action bất kỳ hoặc cả controller. Ví dụ, controller trong Listing 1 bên dưới chứa 1 action Index(). Kết quả trả ra của action Index() sẽ được lưu trữ trong vòng 10 giây.

Listing 1 – Controllers\HomeController.cs

using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
    [HandleError]
    public class HomeController : Controller
    {
        [OutputCache(Duration=10, VaryByParam="none")]
        public ActionResult Index()
        {
            return View();
        }
    }
}

Chú ý: Trong các phiên bản Beta của ASP.NET MVC, output caching không làm việc cho một URL như sau http://www.MySite.com. Thay vào đó bạn phải nhập một URL như thế này http://www.MySite.com/Home/Index

Trong listing 1, kết quả trả ra của action Index() sẽ được lưu trữ trong 10 giây. Nếu bạn thích bạn có thể chỉ thị thời gian của bộ nhớ cache lâu hơn. Ví dụ nếu bạn muốn lưu trữ kết quả trả ra của một action trong vòng 1 ngày thì bạn gán thuộc tính Duration = 86400 (60 giây * 60 phút * 24 giờ).

Listing 2 bên dưới là nội dung của view Index. View Index đơn giản chỉ hiển thị thời gian hiện tại (xem hình 1).

Listing 2 – Views\Home\Index.aspx


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Home.Index" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Index</title>
</head>
<body>
    <div>
    The current time is: <%= DateTime.Now.ToString("T") %>
    </div>
</body>
</html>

Hình 1

Bạn hãy thử refresh trang này nhiều lần, bạn sẽ thấy rằng thời gian hiển thị trên trang sẽ không thay đổi trong vòng 10 giây. Nghĩa là trong 10 giây đó bất kỳ người dùng nào truy cập vào URL /Home/Index đều nhìn thấy phiên bản cache của view, máy chủ web không cần phải thực hiện việc xử lý trong action method này nữa. Điều này có nghĩa rằng công việc mà máy chủ web phải thực hiện để hiển thị lên view Index giảm mạnh.

Listing 2 thực hiện một công việc hết sức đơn giản. View chỉ hiển thị thời gian hiện tại. Tuy nhiên bạn có thể sử dụng cache để lưu kết quả trả về từ cơ sở dữ liệu. Trong trường hợp đó, máy chủ web không cần phải thực hiện truy vấn lấy kết quả trả về từ cơ sở dữ liệu mỗi khi action method được gọi nếu thời gian lưu cache vẫn còn. Caching có thể làm giảm số lượng công việc mà máy chủ web và máy chủ cơ sở dữ liệu của bạn thực hiện.

Nơi nội dung được lưu trữ


Theo mặc định, khi bạn sử dụng attribute [OutputCache], nội dung được lưu trữ ở 3 nơi: máy chủ web, máy chủ proxy và trình duyệt web. Bạn có thể chỉ định nơi mà nội dung được lưu trữ bằng cách thay đổi giá trị của thuộc tính Location của attribute [OutputCache].

Bạn có thể gán giá trị cho thuộc tính Location bằng 1 trong các giá trị sau:
  • Any
  • Client
  • Downstream
  • Server
  • None
  • ServerAndClient

Mặc định, thuộc tính Location có giá trị Any. Tuy nhiên, có những tình huống mà bạn chỉ muốn lưu cache trên trình duyệt hoặc các máy chủ. Ví dụ, nếu bạn muốn lưu cache các thông tin được cá nhân hóa cho từng người sử dụng thì bạn không nên lưu cache trên máy chủ (Server) mà nên lưu chữ trên máy khách (Client).

Ví dụ, controller trong Listing 3 cho thấy 1 action có tên là GetName() trả về người dùng hiện hành. Nếu anh chàng Jack đăng nhập vào website và gọi đến action GetName() thì action sẽ trả về 1 chuỗi "Hi Jack". Nếu sau đó người khác là cô Jill đăng nhập vào website và gọi đến action GetName() thì cô ấy cũng vẫn nhìn thấy chuỗi "Hi Jack". Không chi Jill và tất cả các thành viên khác đăng nhập sau đó đều chỉ nhìn thấy chuỗi "Hi Jack" thay vì tên của họ. Điều này là không hợp lý.

Listing 3 – Controllers\BadUserController.cs

using System.Web.Mvc;
using System.Web.UI;
namespace MvcApplication1.Controllers
{
    public class BadUserController : Controller
    {
        [OutputCache(Duration = 3600, VaryByParam = "none")]
        public string GetName()
        {
            return "Hi " + User.Identity.Name;
        }
    }
}

Để khắc phục vấn đề này thì bạn cần bổ sung giá trị cho thuộc tính Location trong attribute [OutputCache] như trong Listing 4. Bằng cách này thì nội dung sẽ được lưu trữ ở trình duyệt thay vì ở máy chủ. Như vậy khi nhiều người gọi action GetName() thì mỗi người sẽ nhìn thấy câu chào với tên của riêng họ.

Listing 4 – Controllers\UserController.cs

using System.Web.Mvc;
using System.Web.UI;
namespace MvcApplication1.Controllers
{
    public class UserController : Controller
    {
        [OutputCache(Duration=3600, VaryByParam="none", Location=OutputCacheLocation.Client, NoStore=true)]
        public string GetName()
        {
            return "Hi " + User.Identity.Name;
        }
    }
}

Chú ý rằng [OutputCache] cũng có 1 thuộc tính là NoStore, thuộc tính này được sử dụng để báo cho các máy chủ Proxy và trình duyệt biết rằng không lưu trữ một bản sao của nội dung được lưu trữ.

Tạo các phiên bản Output Cache khác nhau


Trong một số trường hợp, bạn có thể muốn lưu trữ các phiên bản khác nhau trên cùng 1 action. Hãy tưởng tượng, bạn đang tạo một trang có dạng master/detail. Trang master hiển thị  danh sách tiêu đề của những bộ phim. Khi bạn click vào 1 tiêu đề bất kỳ, bạn sẽ có được thông tin chi tiết của một bộ phim được chọn. 

Trong trường hợp này nếu bạn lưu cache của trang detail ở bất kỳ nơi đâu thì cũng không hợp lý. Giả sử Jack chọn xem thông tin bộ phim bất kỳ nào đó đầu tiên, sau đó Jill và những người dùng khác khi xem thông tin các bộ phim khác thì đều nhận được thông tin chi tiết của bộ phim mà Jack vừa xem. Lý do cho điều này là vì action Details() có chứa 1 tham số là Id. Chúng ta cần lưu cache của action Details() theo từng Id.

Để làm được điều này chúng ta cần sử dụng thuộc tính VaryByParam. Thuộc tính này cho phép bạn tạo ra các phiên bản được lưu trữ khác nhau dựa theo form parameter hoặc querystring parameter.

Ví dụ, controller trong Listing 5 chứa 2 action là Master() và Details(). Action Master() trả về 1 danh sách các tiêu đề phim và action Details() trả về thông tin chi tiết của bộ phim được chọn.

Listing 5 – Controllers\MoviesController.cs

using System.Linq;
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
    public class MoviesController : Controller
    {
        private MovieDataContext _dataContext;
        public MoviesController()
        {
            _dataContext = new MovieDataContext();
        }
        [OutputCache(Duration=int.MaxValue, VaryByParam="none")]
        public ActionResult Master()
        {
            ViewData.Model = (from m in _dataContext.Movies
                              select m).ToList();
            return View();
        }
        [OutputCache(Duration = int.MaxValue, VaryByParam = "id")]
        public ActionResult Details(int id)
        {
            ViewData.Model = _dataContext.Movies.SingleOrDefault(m => m.Id == id);
            return View();
        }

    }
}

Action Master() bao gồm 1 thuộc tính VaryByParam với giá trị "none". Khi action Master() được gọi, bộ nhớ cache sẽ trả về ngay nội dung của view Master đã được lưu trước đó. Bất kỳ form parameter hay querystring parameter đều được bỏ qua (xem hình 2).

Hình 2

Hình 3

Action Details() bao gồm thuộc tính VaryByParam với giá trị "Id". Khi các giá trị khác nhau của tham số Id được truyền cho action, các phiên bản khác nhau được lưu trữ trên cache được tạo ra.

Bạn có thể gán giá trị cho thuộc tính VaryByParam bằng 1 trong các giá trị sau:

  • * = Tạo một phiên bản được lưu khác nhau bất cứ khi nào một form parameter hoặc querystring parameter khác nhau.
  • none = Không bao giờ tạo các phiên bản được lưu trữ khác nhau
  • Dánh sách tên các tham số ngăn cách nhau bởi dấu "," = Tạo các phiên bản được lưu trữ khác nhau bất cứ khi nào các giá trị của form parameter hoặc querystring parameter trong danh sách thay đổi

Tạo Cache Profile


Để tiện cho việc sử dụng cache output, bạn có thể tạo 1 cache profile trong file web.config. Khi tạo xong bạn có thể sử dụng cache profile này trên các action method và controller một cách nhanh chóng và bạn cũng có thể sửa đổi giá trị hay vô hiệu hóa output cache ngay trong web.config mà không cần biên dịch lại ứng dụng. Bất kỳ thay đổi trong file web.config sẽ được phát hiện và áp dụng.

Ví dụ, với thẻ <caching> trong web.config ở Listing 7 định nghĩa một cache profile có tên Cache1Hour. Các thẻ <caching> phải xuất hiện trong thẻ <system.web> trong file web.config.

Listing 6 – Caching section for web.config


<caching>
<outputCacheSettings>
    <outputCacheProfiles>
        <add name="Cache1Hour" duration="3600" varyByParam="none"/>
    </outputCacheProfiles>
</outputCacheSettings>
</caching>

Controller trong Listing 7 cho thấy cách áp dụng cache profile Cache1Hour đến 1 action với attribute [OutputCache].

Listing 7 – Controllers\ProfileController.cs

using System;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
    public class ProfileController : Controller
    {
        [OutputCache(CacheProfile="Cache1Hour")]
        public string Index()
        {
            return DateTime.Now.ToString("T");
        }
    }
}

Tóm lược


Output caching cung cấp cho bạn một phương pháp rất dễ dàng để cải thiện đáng kể hiệu suất các ứng dụng ASP.NET MVC của bạn. Trong bài viết này, bạn đã học được cách sử dụng attribute [OutputCache] để lưu trữ kết quả trả ra của các action trong controller. Bạn cũng biết cách thay đổi các thuộc tính Duration, VaryByParam để cấu hình cách nội dung được lưu trữ. Cuối cùng, bạn đã học được cách để tạo ra 1 cache profile trong web.config để tiện việc sử dụng và bảo trì. 



EmoticonEmoticon