• Thứ Tư, 27/09/2006 11:19 (GMT+7)

    Sử dụng Ajax trong lập trình ASP.NET

    Ajax ngày càng được sử dụng rộng rãi, nó giúp xoá mờ khoảng cách giữa máy tính (desktop) và Internet. Trong số báo TGVT A tháng 12/2005 tác giả Phạm Công Định đã có bài giới thiệu tổng quát về kỹ thuật lập trình web sử dụng Ajax, tiếp tục chủ đề này, trong bài viết này tôi giới thiệu một công cụ phát triển ứng dụng kiểu Ajax với ASP.NET.

    Ajax.NET

    Ajax.NET là thư viện Ajax cho .NET được phát triển bởi tác giả người Đức tên là Michael Schwarz (info@schwarz-interactive.de), mục đích nhằm giúp lập trình viên .NET dễ dàng khai thác sức mạnh của Ajax trong các ứng dụng web của mình. Thư viện này được cung cấp miễn phí trên Internet, bạn có thể yêu cầu tác giả cho tải về cả mã nguồn nếu muốn. Thư viện Ajax.NET hiện cũng được phát triển dưới một dự án nguồn mở tên là BorgWorX Ajax.NET (http://www.borgworx.net/) với nhiều chức năng mở rộng. Ở đây tôi chỉ đề cập tới phiên bản Ajax.NET đầu, sau khi làm quen với thư viện này bạn có thể dễ dàng khám phá phiên bản mở rộng trong dự án nguồn mở trên, thậm chí tham gia dự án này.

     

    Ajax.NET giúp lập trình viên sử dụng Ajax mà không phải bận tâm đến mã lệnh tương tác trực tiếp với các đối tượng XMLHttpRequest, xử lý dữ liệu XML, DOM. Bằng cách sử dụng những khả năng của môi trường lập trình .NET như HttpHandler, reflection, attributes... nó tự động sinh ra những mã lệnh cần thiết giúp việc tương tác giữa client/server hoàn toàn "trong suốt" cũng như đơn giản hoá việc gọi hàm, xử lý những dữ liệu phức tạp như mảng, bảng, đối tượng trên phía client.

    Để hiểu rõ về cách hoạt động cũng như sử dụng Ajax.NET trong một ứng dụng ASP.NET, tôi trình bày từng bước thực hiện một ứng dụng minh hoạ đơn giản. Ứng dụng là một form đăng ký thành viên như chúng ta thường gặp trên các diễn đàn, hoặc đăng ký tài khoản e-mail... Nguyên tắc hoạt động của form là mỗi khi có thành viên mới đăng ký sẽ sử dụng Ajax để kiểm tra xem tên (username) đã có chưa và hiển thị danh sách các thành phố dựa trên quốc gia mà thành viên sinh sống. Các thay đổi sẽ thực hiện ở "hậu trường" mà không phải thực hiện nạp lại toàn bộ trang web hay lưu tạm một lượng lớn dữ liệu ở client như cách thông thường.

    Bạn có thể tham khảo ứng dụng minh hoạ của bài viết này tại http://www.mobicolors.com/Tiendq/AjaxDemo/, file zip chứa toàn bộ mã nguồn của ứng dụng cũng có thể tải về từ địa chỉ trên. Ứng dụng này được xây dựng với Visual Studio 2005 phiên bản CTP 09/2005, sử dụng ngôn ngữ C#, tuy nhiên các kỹ thuật trình bày ở đây sẽ không có gì khác biệt khi sử dụng với VB.NET hoặc Visual Studio .NET 2003.

    Cài đặt ban đầu

    Trước tiên cần tạo một website mới trong VS2005, sau đó bạn tạo một tham chiếu (reference) tới thư viện Ajax.NET (file AjaxPro.dll nếu sử dụng với VS.NET 2003, hoặc file AjaxPro.2.dll nếu là VS2005). Thư viện Ajax.NET Professional sử dụng trong bài viết này là phiên bản mới nhất 5.11.4.2, có thể tải về từ http://www.schwarz-interactive.de.

    Tiếp theo, cài đặt HttpHandler của Ajax.NET vào website vừa tạo bằng cách thêm các dòng sau đây vào file Web.config (nếu sử dụng VS2005 bạn phải tự thêm file này vào website).






    type="AjaxPro.AjaxHandlerFactory, AjaxPro.2"/>


    ...


    Các bước trên chỉ cần thực hiện một lần với một website, sau đó chúng ta chuyển sang phần khai báo và viết các hàm xử lý trên phía server.

    Xây dựng các hàm Ajax phía server

    Nhìn chung việc xây dựng các hàm để xử lý những yêu cầu từ phía client không có gì khác so với các các quy định của .NET, ngoại trừ một việc là những hàm này sẽ được "đánh dấu" bằng một thuộc tính tên là AjaxMethodAttribute và có một số hạn chế sẽ được trình bày ở phần sau. Dưới đây là khai báo của hàm CheckAvailablity sử dụng trong ứng dụng để kiểm tra xem một username đã được đăng ký chưa. Để tập trung vào chủ đề chính, tôi chỉ đơn giản lưu username vào một biến session, như vậy là trong cùng một session sẽ không đăng ký được hai thành viên có username giống nhau.

    [AjaxPro.AjaxMethod]

    public bool CheckAvailablity(string userName)
    {
      return (null == Session[userName]);
    }

    Hàm CheckAvailability hoàn toàn có thể được sử dụng lại trong các hàm khác ở phía server mà không phải thay đổi gì cả, ví dụ hàm này được dùng để kiểm tra username khi người sử dụng nhấn vào nút Sign Up, phát sinh sự kiện postback gửi dữ liệu về xử lý trên server:

    protected void btnSignUp_Click(object sender, EventArgs e)
    {
      if (CheckAvailablity(txtUserName.Text))
      {
        // register new user...
      }
      // ...
    }

    Cũng tương tự cách khai báo như trên, chúng ta có hàm GetStateList dùng để trả về danh sách các thành phố của quốc gia tương ứng mỗi khi người sử dụng thay đổi tên quốc gia ở phía client. Hàm này trả về một đối tượng kiểu DataTable, tuy nhiên với sự hỗ trợ của Ajax.NET sẽ không có khó khăn gì để xử lý và hiển thị dữ liệu này trên form ở phía client.

    [AjaxPro.AjaxMethod]

    public DataTable GetStateList(int countryIndex)
    {
      DataTable dt = new DataTable();
      dt.Columns.Add("StateCode", typeof(string));
      dt.Columns.Add("StateName", typeof(string));
      // populate state list
      return dt;
    }

    Cuối cùng, để các hàm trên có thể được gọi từ phía client thì chúng ta cần phải đăng ký với Ajax.NET. Sau khi đăng ký với Ajax.NET, thường thực hiện trong hàm xử lý sự kiện Page_Load của trang web, Ajax.NET sẽ tự động sinh ra các mã lệnh JavaScript cần thiết của các hàm này ở phía client. Khi các hàm ở client được gọi thực hiện, dữ liệu sẽ chuyển tới các hàm tương ứng trên server để xử lý và trả về kết quả sau đó.

    public partial class Tiendq_AjaxDemo : System.Web.UI.Page
    {
      protected void Page_Load(object sender, EventArgs e)
      {
        AjaxPro.Utility.RegisterTypeForAjax(typeof(Tiendq_AjaxDemo));
        if (false == IsPostBack)
        {
          // ...
        }
      }
      // ...
    }

    Sử dụng hàm Ajax ở client

    Sau khi xây dựng xong các hàm Ajax phía server, chúng ta có thể sử dụng các hàm này ở client (mã lệnh viết bằng JavaScript) với cú pháp như đã khai báo ở phía server (mã lệnh viết bằng C#), việc gọi hàm có thể thực hiện đồng bộ (synchronous) hoặc bất đồng bộ (asynchronous). Khi gọi hàm theo kiểu synchronous sẽ nhận được dữ liệu trả về tức thời (hoặc phải chờ tuỳ theo tốc độ của kết nối tới server), khi gọi hàm theo kiểu bất đồng bộ (asynchronous) thì dữ liệu sẽ được trả về qua một hàm callback, đoạn mã lệnh thực hiện gọi hàm trong trường hợp này sẽ tiếp tục thực hiện và không cần chờ dữ liệu trả về từ phía server. Hàm callback sẽ được gọi khi có dữ liệu từ server trả về sau đó.

    Gọi hàm theo kiểu đồng bộ:

    function OnCheckAvailablity(userName)
    {
      var res = Tiendq_AjaxDemo.CheckAvailablity(userName);
      alert(res.value);
    }

    Gọi hàm theo kiểu bất đồng bộ:

    function OnCheckAvailablity(userName)
    {
      Tiendq_AjaxDemo.CheckAvailablity(userName,
      OnCheckAvailablity_Callback);
    }

    function OnCheckAvailablity_Callback(res)
    {
      if (null != res.error)
      {
        alert("Error message goes here!");
        return;
      }

      if (res.value)
      {
        // process data
      }

      else
      {
       // raise error
      }
    }

    Trong cả hai trường hợp gọi hàm trên, dữ liệu trả về là một đối tượng có cấu trúc như sau:

      Tên trường     Mô tả  
      Value     Dữ liệu trả về khi gọi thực hiện hàm phía server, kiểu dữ liệu thực sự phụ thuộc vào khai báo hàm phía server (có thể là chuỗi ký tự, số nguyên, mảng,...).  
      Error     Thông báo lỗi nếu có.  
      Request     Dữ liệu trả về của đối tượng XMLHttpRequest, trường hợp muốn lấy thêm các thông tin trả về khác. Xem tài liệu về đối tượng XMLHttpRequest để biết chi tiết.  
      Context     Tham chiếu tới đối tượng phía client.  

    Sau đây sẽ là phần mã lệnh gọi thực hiện các hàm CheckAvailablity và GetStateList ở phía client mà chúng ta đã khai báo ở phần trên. Hàm CheckAvailablity sẽ được gọi thực hiện khi người sử dụng nhấn vào nút Check để kiểm tra username muốn đăng ký đã tồn tại chưa, hàm GetStateList được gọi mỗi khi người sử dụng chọn quốc gia khác nhau để cập nhật lại danh sách thành phố của quốc gia đó:

    function OnCheckAvailablity(userName)
    {
        if (userName.length > 0)
            Tiendq_AjaxDemo.CheckAvailablity(userName,
      OnCheckAvailablity_Callback);
        else
            alert("User name is required field.");
    }

    function OnCheckAvailablity_Callback(res)
    {
        if (null != res.error)
        {
            alert("Error message goes here!");
            return;
        }
       
        if (res.value)
        {
            document.all("lblCheckStatus").innerHTML = "" + document.forms[0].txtUserName.value + " is available."
            document.all("lblCheckStatus").className = "cssAvailable";
        }
        else
        {
            document.all("lblCheckStatus").innerHTML = "" + document.forms[0].txtUserName.value + " is not available."
            document.all("lblCheckStatus").className = "cssNotAvailable";
        }
    }

    function OnSelectCountry(countryIndex)
    {
        if (countryIndex > 0)
            Tiendq_AjaxDemo.GetStateList(countryIndex, GetStateList_Callback);
        else
        {
            ResetStateList();
            document.forms[0].cboStates.disabled = true;
        }
    }

    function GetStateList_Callback(res)
    {
        if (null != res.error)
        {
            alert("Error message goes here!");
            return;
        }
       
        var dt = res.value;

        if ((null != dt) && ("object" == typeof(dt)))
        {
            document.forms[0].cboStates.disabled = false;
            ResetStateList();

            var oItem = null;

            for (var i = 0; i < dt.Rows.length; ++ i)
            {
                oItem = document.createElement("OPTION");
                oItem.text = dt.Rows[i].StateName;
                oItem.value = dt.Rows[i].StateCode;
                document.forms[0].cboStates.add(oItem);
            }
        }
    }


    Nhờ có Ajax.NET, ở đoạn mã lệnh JavaScript trên kiểu dữ liệu phức tạp DataTable trả về từ hàm GetStateList có thể xử lý dễ dàng theo cách quen thuộc như dùng ngôn ngữ của .NET (C#, VB.NET). Hoàn toàn không cần thao tác với XML, hay DOM.

    Một số chú ý

    Ở ứng dụng này tôi mới chỉ minh hoạ trường hợp gọi hàm được khai báo trong cùng một lớp của trang web, trường hợp khai báo hàm trong một Web User Control hoặc trong một lớp khác thì cần phải đăng ký với Ajax.NET như sau.

    Khai báo đăng ký Ajax.NET cho Web User Control, trong đó WebUserControl1 là tên của lớp user control có các hàm sử dụng Ajax:

    AjaxPro.Utility.RegisterTypeForAjax(typeof(WebUserControl1), this.Page);

    Khai báo đăng ký với Ajax.NET cho lớp Class1 có các hàm sử dụng Ajax:

    AjaxPro.Utility.RegisterTypeForAjax(typeof(Class1));

    Tương tự, nếu kiểu dữ liệu trả về là một đối tượng tự xây dựng thì ta cũng phải đăng ký như Class1 ở trên.

    Như đã đề cập ở phần trên của bài viết, có một số hạn chế khi xây dựng các hàm Ajax để gọi từ phía client cần phải chú ý:

    • Để truy cập các biến Request, Response, Session, Cache... cần phải sử dụng thông qua biến HttpContext.Current.

    • Nếu muốn hàm Ajax sử dụng các biến lưu trong session thì phải chỉ rõ khi khai báo thuộc tính AjaxMethodAttribute. Ví dụ hàm CheckAvailablity ở trên cần phải được khai báo đúng như sau:

    AjaxPro.AjaxMethod(AjaxPro.HttpSessionStateRequirement.Read)]

    public bool CheckAvailablity(string userName)
    {
       return (null == Session[userName]);
    }

    Trong đó HttpSessionStateRequirement là một enum định nghĩa các kiểu truy nhập vào biến session mà hàm CheckAvailability sẽ sử dụng.

    • Khi hàm Ajax được gọi sẽ không phát sinh ra sự kiện postback ở phía server, do đó hàm xử lý sự kiện Page_Load cũng sẽ không được gọi, và hàm Ajax không thể truy cập các control có trên trang web (server control).

    Ứng dụng đơn giản trên chỉ là minh hoạ cho việc sử dụng thư viện Ajax.NET với ASP.NET, còn nhiều tính năng nữa của thư viện này chưa trình bày được trong phạm vi bài viết như caching, hỗ trợ chuyển đổi kiểu dữ liệu, context... Bạn có thể tự tìm hiểu chi tiết sau khi đã làm quen với thư viện này.

    Lời kết

    Bản thân Ajax là một khái niệm mới và thư viện Ajax.NET vẫn đang còn được phát triển, nên còn một số hạn chế cũng như những thiếu sót trong tài liệu. Tuy nhiên nếu yêu cầu của ứng dụng không quá phức tạp thì nó hoàn toàn có thể đáp ứng tốt, giúp lập trình viên giảm đáng kể công sức so với sử dụng trực tiếp đối tượng XMLHttpRequest. Thậm chí chúng ta có thể sửa đổi mã nguồn của thư viện này để đáp ứng yêu cầu riêng của mình nếu cần thiết. Nếu gặp vấn đề trong khi sử dụng thư viện này, bạn đọc có thể tham gia vào nhóm thảo luận Ajax.NET tại http://groups.google.com/group/Ajaxpro để tìm thông tin trợ giúp cũng như chia sẻ kinh nghiệm của mình.

    Đỗ Quyết Tiến
    NetHoa Software
    Do Quyet Tien [tiendq@gmail.com]

    ---------------------------------------------
    Tài liệu tham khảo:
    1. Ajax.NET Professional - A free library for the Microsoft .NET Framework (http://Ajaxpro.schwarz-interactive.de/)
    2. Michael Schwarzs Ajax.NET Professional blog
    (http://weblogs.asp.net/mschwarz/)

    ID: A0603_115