481 Chương 12: Dịch vụ Web XML và Remoting return names; } private DataSet GetCustomerDataSet() { // Kiểm tra item đã có trong cache chưa. DataSet ds = HttpContext.Current.Cache["Products"] as DataSet; if (ds == null) { string SQL = "SELECT * FROM Products"; // Tạo các đối tượng ADO.NET. SqlConnection con = new SqlConnection(connectionString); SqlCommand com = new SqlCommand(SQL, con); SqlDataAdapter adapter = new SqlDataAdapter(com); ds = new DataSet(); // Thực thi câu truy vấn. try { con.Open(); adapter.Fill(ds, "Products"); // Lưu item vào cache (trong 60 giây). HttpContext.Current.Cache.Insert("Products", ds, null, DateTime.Now.AddSeconds(60), TimeSpan.Zero); } catch (Exception err) { System.Diagnostics.Debug.WriteLine(err.ToString()); } finally { con.Close(); } } return ds; } } 482 Chương 12: Dịch vụ Web XML và Remoting 4. 4. T o ph ng th c web h tr giao d ch ạ ươ ứ ỗ ợ ị T o ph ng th c web h tr giao d ch ạ ươ ứ ỗ ợ ị Bạn muốn thực thi tất cả các hành động của một phương thức web trong ngữ cảnh của một phiên giao dịch COM+ sao cho chúng chỉ có hai khả năng: hoặc là thành công hoặc là thất bại. Kích hoạt một phiên giao dịch tự động bằng cách chọn một giá trị thuộc kiểu liệt kê System.EnterpriseServices.TransactionOption và áp dụng nó cho thuộc tính TransactionOption của đặc tính WebMethod . Trong ASP.NET, dịch vụ Web XML hỗ trợ các phiên giao dịch tự động (có thể được kích hoạt trên mỗi phương thức). Khi được kích hoạt, bất kỳ nguồn dữ liệu nào có hỗ trợ giao dịch COM+ sẽ tự động được đưa vào phiên giao dịch hiện hành khi nó được sử dụng trong mã lệnh của bạn. Phiên giao dịch sẽ tự động được commit khi phương thức web hoàn tất. Phiên giao dịch này được roll-back khi có bất kỳ ngoại lệ chưa-được-thụ-lý nào xảy ra hoặc bạn gọi phương thức SetAbort của lớp System.EnterpriseServices.ContextUtil . Để kích hoạt việc hỗ trợ giao dịch cho một phương thức web, bạn cần thiết lập thuộc tính TransactionOption của đặc tính WebMethod là RequiresNew . Ví dụ, phương thức web (hỗ trợ giao dịch) dưới đây sẽ xóa các bản ghi trong một cơ sở dữ liệu và rồi hủy bỏ phiên giao dịch này. Để sử dụng đoạn mã này, bạn phải thêm một tham chiếu đến System.EnterpriseServices.dll. using System; using System.Data.SqlClient; using System.Web.Services; using System.EnterpriseServices; public class TransactionTest { private static string connectionString = "Data Source=localhost;" + "Initial Catalog=Northwind;user ID=sa"; [WebMethod(TransactionOption=TransactionOption.RequiresNew)] public void FailedTransaction() { // Tạo kết nối. SqlConnection con = new SqlConnection(connectionString); // Tạo câu truy vấn SQL. SqlCommand cmd = new SqlCommand("DELETE * FROM Customers", con); 483 Chương 12: Dịch vụ Web XML và Remoting // Thực thi câu truy vấn. Tác vụ này sẽ tự động được // đăng ký như một phần trong phiên giao dịch. con.Open(); cmd.ExecuteNonQuery(); con.Close(); // Gọi một phương thức nào khác. DoSomething(); // Nếu không có lỗi xảy ra, các thay đổi trong cơ sở dữ liệu // sẽ được commit ở đây khi phương thức này kết thúc. } private void DoSomething() { // Hủy bỏ phiên giao dịch. ContextUtil.SetAbort(); } Bạn có thể sử dụng tiện ích Component Service (trong phần Administrative Tools của Control Panel) để theo dõi phiên giao dịch trên. Trong tiện ích này, bạn hãy chọn mục Distributed Transaction Coordinator của máy tính hiện hành, và xem Transaction Statistics. Hình 12.2 cho thấy một phiên giao dịch không thành công khi chạy đoạn mã trên. 484 Chương 12: Dịch vụ Web XML và Remoting Hình 12.2 Theo dõi một phiên giao dịch không thành công [ Vì bản chất phi trạng thái (stateless) của giao thức HTTP, nên một phương thức web chỉ có thể góp phần làm gốc của một phiên giao dịch; nghĩa là bạn không thể đưa nhiều hơn một phương thức web vào cùng một phiên giao dịch. Mặc dù thuộc tính TransactionOption chấp nhận tất cả các giá trị TransactionOption chuẩn, nhưng các giá trị này không mang ý nghĩa như mong đợi. Ví dụ, Disabled , NotSupported , và Supported đều có cùng tác dụng là vô hiệu việc hỗ trợ giao dịch. Tương tự, cả Required và RequiresNew đều kích hoạt việc hỗ trợ giao dịch và khởi chạy một phiên giao dịch mới. Bạn nên sử dụng RequiresNew trong các phương thức web vì tên của nó tương xứng với hành vi thật sự (!). Các giao dịch COM+ làm việc một cách trong suốt với hầu hết các nguồn dữ liệu vì chúng cung cấp các bộ quản lý tài nguyên tương thích. Nhưng luôn nhớ rằng, nếu bạn tương tác với một tài nguyên không hỗ trợ giao dịch, mã lệnh sẽ không được roll-back. Một số hoạt động không phải là giao dịch: ghi file, đặt thông tin vào trạng thái phiên làm việc, và truy xuất một thiết bị phần cứng (như máy in). Mặt khác, các thao tác dữ liệu với hầu hết các hệ cơ sở dữ liệu Enterprise (bao gồm Microsoft SQL Server và Oracle ) đều là tương thích COM+ . 485 Chương 12: Dịch vụ Web XML và Remoting 5. 5. Thi t l p thông tin xác th c cho d ch v Web XMLế ậ ự ị ụ Thi t l p thông tin xác th c cho d ch v Web XMLế ậ ự ị ụ Bạn muốn gửi các thông tin đăng nhập từ client của dịch vụ Web XML đến IIS authentication . Sử dụng thuộc tính Credentials của lớp proxy. Bạn có thể tạo một đối tượng NetworkCredential mới chứa username và password; hoặc sử dụng CredentialCache để lấy các thông tin xác thực của người dùng hiện hành. Cũng giống như trang web, dịch vụ Web XML có thể được sử dụng cùng với IIS authentication. Những gì bạn cần làm là đặt dịch vụ Web XML vào một thư mục ảo hạn chế việc truy xuất nặc danh. Tuy nhiên, nếu người dùng có thể cung cấp các thông tin ánh xạ đến một tài khoản người dùng hợp lệ, người dùng này sẽ được xác thực và bạn có thể lấy các thông tin xác thực này thông qua đối tượng WebService.User . Khác với trang web, dịch vụ Web XML không có sẵn phương thức nào để thu lấy thông tin xác thực từ client vì dịch vụ Web XML được thực thi bởi các ứng dụng khác, chứ không phải bởi người dùng. Do đó, ứng dụng đang tương tác với dịch vụ Web XML sẽ chịu trách nhiệm nhập bất kỳ thông tin xác thực cần thiết nào. Ví dụ sau mô phỏng một dịch vụ Web XML có thực hiện xác thực người dùng. GetIISUser trả về người dùng đã được IIS xác thực. Nếu truy xuất nặc danh được phép thì kết quả sẽ là một chuỗi rỗng. Nếu truy xuất nặc danh bị từ chối thì kết quả sẽ là một chuỗi có dạng [DomainName]\[UserName] hay [ComputerName]\[UserName]. public class AuthenticationTest : System.Web.Services.WebService { // Lấy thông tin về người dùng đã được IIS xác thực. [WebMethod()] public string GetIISUser() { return User.Identity.Name; } } Bước cuối cùng là tạo một client có thể cung cấp các thông tin xác thực. Các thông tin này được nhập thông qua thuộc tính Credentials của đối tượng proxy (thiết lập thuộc tính này tương tự như thiết lập thuộc tính WebRequest.Credentials khi lấy về một trang web—tham khảo mục 11.3). Đoạn mã dưới đây trình bày cách truy xuất một dịch vụ Web XML sử dụng Basic authentication (xác thực cơ bản): // Tạo proxy. localhost.AuthenticationTest proxy = new localhost.AuthenticationTest(); // Tạo thông tin xác thực. proxy.Credentials = new System.Net.NetworkCredential( "myUserName", "myPassword"); 486 Chương 12: Dịch vụ Web XML và Remoting Console.WriteLine(proxy.GetIISUser()); Đoạn mã dưới đây trình bày cách truy xuất một dịch vụ Web XML sử dụng Integrated Windows authentication (xác thực được tích hợp với Windows): // Tạo proxy. localhost.AuthenticationTest proxy = new localhost.AuthenticationTest(); // Gán thông tin xác thực của người dùng hiện hành cho lớp proxy. proxy.Credentials = System.Net.CredentialCache.DefaultCredentials; Console.WriteLine(proxy.GetIISUser()); 6. 6. G i b t đ ng b m t ph ng th c webọ ấ ồ ộ ộ ươ ứ G i b t đ ng b m t ph ng th c webọ ấ ồ ộ ộ ươ ứ Bạn cần gọi một phương thức web trong một tiểu trình khác để chương trình của bạn có thể thực hiện các tác vụ khác trong khi chờ đáp ứng. Sử dụng các phương thức bất đồng bộ có sẵn trong lớp proxy. Các phương thức này có tên là BeginXXX và EndXXX với XXX là tên của phương thức đồng bộ gốc. Lớp proxy (được tạo tự động) có các tính năng cơ bản mà bạn cần để gọi bất kỳ phương thức web nào một cách bất đồng bộ. Ví dụ, phương thức web dưới đây có chức năng tạm dừng một khoảng thời gian ngẫu nhiên từ 10 đến 19 giây: using System; using System.Web.Services; public class Wait : System.Web.Services.WebService { [WebMethod] public int Wait() { DateTime start = DateTime.Now; Random rand = new Random(); TimeSpan delay = new TimeSpan(0, 0, rand.Next(10, 20)); while (DateTime.Now < start.Add(delay)) {} return delay.Seconds; } } 487 Chương 12: Dịch vụ Web XML và Remoting Với đoạn mã trên, lớp proxy tương ứng sẽ gồm ba phương thức: Wait , BeginWait và EndWait . Phương thức Wait gọi phương thức web một cách đồng bộ. Phương thức BeginWait khởi chạy phương thức web trong một tiểu trình riêng và trả về ngay lập tức. Phương thức BeginXXX luôn nhận nhiều hơn phương thức gốc hai đối số và trả về một đối tượng IAsyncState . Hai đối số này được sử dụng để nhập thông tin trạng thái và một callback. Đối tượng IAsyncState cho phép bạn xác định khi nào lời gọi kết thúc. Ví dụ, bạn có thể định kỳ kiểm tra thuộc tính IAsyncState.IsComplete để xác định lời gọi phương thức đã hoàn tất chưa. Khi nó đã hoàn tất, bạn nhập đối tượng IAsyncState vào phương thức EndWait để nhận giá trị trả về từ phương thức web. Nếu bạn gọi EndWait trước khi phương thức web hoàn tất, mã lệnh của bạn sẽ đợi cho đến khi nó hoàn tất. Có hai mẫu bất đồng bộ phổ biến dùng cho dịch vụ Web XML. Cách thứ nhất là gọi vài phương thức bất đồng bộ một lượt, rồi đợi chúng hoàn tất. Cách này cho phép bạn giảm thời gian đợi tổng cộng, và nó làm việc tốt nhất với đối tượng System.Threading.WaitHandle . Ví dụ dưới đây gọi phương thức Wait ba lần: using System; using System.Threading; public class WaitClient { [MTAThread] private static void Main() { localhost.WaitService proxy = new localhost.WaitService(); DateTime startDate = DateTime.Now; // Gọi ba phương thức một cách bất đồng bộ. IAsyncResult handle1 = proxy.BeginWait(null, null); IAsyncResult handle2 = proxy.BeginWait(null, null); IAsyncResult handle3 = proxy.BeginWait(null, null); WaitHandle[] waitHandle = {handle1.AsyncWaitHandle, handle2.AsyncWaitHandle, handle3.AsyncWaitHandle}; // Đợi cho cả ba phương thức hoàn tất. WaitHandle.WaitAll(waitHandle); int totalDelay = proxy.EndWait(handle1) + proxy.EndWait(handle2) + proxy.EndWait(handle3); 488 Chương 12: Dịch vụ Web XML và Remoting TimeSpan elapsedTime = DateTime.Now - startDate; Console.WriteLine("Completed after " + elapsedTime.ToString()); Console.WriteLine("Total delay time: " + totalDelay.ToString()); } } Trong trường hợp này, thời gian đã trôi qua nhỏ hơn thời gian trì hoãn tổng cộng: Completed after 00:00:20.2591312 Total delay time: 47 Cách thứ hai là sử dụng callback. Bạn cần nhập một ủy nhiệm chỉ định một phương thức cụ thể trong mã lệnh. Khi phương thức web hoàn tất, ủy nhiệm này sẽ được gọi với đối số là một đối tượng IAsyncResult thích hợp. Dưới đây là đoạn mã gọi phương thức BeginWait (cùng với một callback): AsyncCallback callback = new AsyncCallback(Callback); // Gọi phương thức một cách bất đồng bộ. proxy.BeginWait(callback, proxy); Và đây là callback (sẽ được kích hoạt khi thao tác hoàn tất): public static void Callback(IAsyncResult handle) { localhost.WaitService proxy = (localhost.WaitService)handle.AsyncState; int result = proxy.EndWait(handle); Console.WriteLine("Waited " + result.ToString()); } 7. 7. T o l p kh -truy-xu t-t -xaạ ớ ả ấ ừ T o l p kh -truy-xu t-t -xaạ ớ ả ấ ừ Bạn muốn tạo một lớp có thể được truy xuất từ một ứng dụng khác hay từ một máy tính khác trên mạng. Tuy vậy, bạn không cần tính tương thích xuyên-nền và bạn muốn có hiệu năng tối ưu. Làm cho lớp này trở thành khả-truy-xuất-từ-xa ( remotable ) bằng cách dẫn xuất từ lớp System.MarshalByRefObject , và tạo một host để đăng ký lớp này với kiến trúc . NET Remoting . Remoting cho phép bạn làm cho một đối tượng trở nên truy xuất được qua các biên máy và biên tiến trình. Trong khi dịch vụ Web XML là giải pháp lý tưởng khi bạn cần chia sẻ các chức 489 Chương 12: Dịch vụ Web XML và Remoting năng qua các nền hay các biên tin cậy, Remoting là lựa chọn tốt nhất cho một hệ thống mà trong đó tất cả các thành phần đều được xây dựng trên nền .NET và hệ điều hành Windows. Để sử dụng .NET Remoting, bạn cần các phần sau (mỗi phần phải thuộc về một assembly riêng biệt): • Lớp khả-truy-xuất-từ-xa Lớp này có thể được truy xuất từ các ứng dụng và các máy tính khác, và phải dẫn xuất từ System.MarshalByRefObject . • Ứng dụng host Ứng dụng này đăng ký kiểu khả-truy-xuất-từ-xa với kiến trúc .NET Remoting bằng lớp RemotingConfiguration (thuộc không gian tên System.Runtime.Remoting ). Miễn là ứng dụng host đang chạy, các client ở xa có thể tạo ra các thể hiện của lớp khả-truy-xuất-từ-xa. • Ứng dụng client Ứng dụng này có thể tạo ra các thể hiện của lớp khả-truy-xuất-từ-xa trong tiến trình của host và tương tác với chúng. Client sử dụng lớp RemotingConfiguration để đăng ký các kiểu mà nó muốn truy xuất từ xa. Hình sau mô tả sự tương tác giữa ba phần trên. Trong ví dụ này, chỉ có một client. Tuy nhiên, cũng có thể có nhiều client tạo ra các thể hiện của lớp khả-truy-xuất-từ-xa cùng một lúc. Trong trường hợp này, mỗi client sẽ có một đối tượng khả-truy-xuất-từ-xa của chính nó, và tất cả các đối tượng này sẽ thuộc về miền ứng dụng của host. Hình 12.3 Sử dụng một lớp khả-truy-xuất-từ-xa Bước đầu tiên là tạo lớp khả-truy-xuất-từ-xa. Ví dụ, lớp khả-truy-xuất-từ-xa dưới đây trả về một DataSet ; với cách này, một client có thể truy xuất thông tin từ cơ sở dữ liệu mà không phải trực tiếp kết nối đến cơ sở dữ liệu phía server. using System; using System.Data; using System.Data.SqlClient; public class ProductsDB : MarshalByRefObject { private static string connectionString = "Data Source=localhost;" + "Initial Catalog=Northwind;Integrated Security=SSPI"; public DataTable GetProducts() { string SQL = "SELECT * FROM Products"; Client Ordinary Object Component host Remotable Object Cross-application call 490 Chương 12: Dịch vụ Web XML và Remoting // Tạo các đối tượng ADO.NET. SqlConnection con = new SqlConnection(connectionString); SqlCommand com = new SqlCommand(SQL, con); SqlDataAdapter adapter = new SqlDataAdapter(com); DataSet ds = new DataSet(); // Thực thi câu truy vấn. try { con.Open(); adapter.Fill(ds, "Products"); } catch (Exception err) { Console.WriteLine(err.ToString()); } finally { con.Close(); } return ds.Tables[0]; } // Phương thức này kiểm tra Remoting có hoạt động hay không. public string GetHostLocation() { return AppDomain.CurrentDomain.FriendlyName; } } Lớp này được định nghĩa trong một Class Library Assembly có tên là RemoteObject.dll. Ở mức lý tưởng, đối tượng ở xa sẽ không giữ lại bất kỳ trạng thái nào. Tính chất này cho phép bạn sử dụng chế độ kích hoạt gọi một lần ( single-call activation ), trong đó đối tượng sẽ được tạo ra ngay đầu mỗi lời gọi phương thức và sẽ được giải phóng khi kết thúc (giống như trong dịch vụ Web XML ). Điều này bảo đảm các đối tượng không chiếm nhiều tài nguyên của server và việc quản lý thời gian sống của chúng trở nên dễ dàng hơn. Kế đến, bạn phải tạo host—đây là ứng dụng phía server quản lý tất cả các thể hiện của lớp khả-truy-xuất-từ-xa. Bạn có thể sử dụng bất kỳ kiểu ứng dụng .NET nào làm host (bao gồm: ứng dụng dựa-trên-Windows, dịch vụ Windows, và ứng dụng Console). Dưới đây là một host đơn giản ở dạng Console: . biên máy và biên tiến trình. Trong khi dịch vụ Web XML là giải pháp lý tưởng khi bạn cần chia sẻ các chức 489 Chương 12: Dịch vụ Web XML và Remoting năng qua các nền hay các biên tin cậy, Remoting. web trong một tiểu trình khác để chương trình của bạn có thể thực hiện các tác vụ khác trong khi chờ đáp ứng. Sử dụng các phương thức bất đồng bộ có sẵn trong lớp proxy. Các phương thức này. RequiresNew trong các phương thức web vì tên của nó tương xứng với hành vi thật sự (!). Các giao dịch COM+ làm việc một cách trong suốt với hầu hết các nguồn dữ liệu vì chúng cung cấp các bộ quản