Để tiếp tục loạt bài, hãy xem qua phần kiểm tra dữ liệu trong ứng dụng của chúng ta. Khả năng cập nhật đã rất tuyệt, nhưng một khi đã cho phép cập nhật, bạn cũng cần kiểm tra dữ liệu để đảm bảo nó hợp lệ. RIA Services cung cấp một hình mẫu rõ ràng, kinh điển cho việc này. Trước tiên hãy xem qua những gì ta đã có sẵn: giá trị của bất kỳ ô dữ liệu nào cũng đều phải khớp với kiểu của nó. Ví dụ, bạn sẽ không bao giờ phải viết lệnh để đảm bảo người dụng không gõ “forty-two” vào một ô văn bản gắn với một trường có kiểu int. Bạn cũng có được một số hỗ trợ rất tốt và trông rất đẹp trên phần giao diện.
Chú ý: nếu bạn không nhìn thấy giống như trên hình này, hãy kiểm tra và đảm bảo rằng “ValidatesOnExceptions=True” có trong biểu thức gắn nối của một trường.
Tất nhiên, những kiểu kiểm tra như vậy thông thường là chưa đủ, trong các ứng dụng thật bạn cần thêm một số phép kiểm tra cao hơn. Và với việc việc kiểm tra này, bạn tất nhiên phải thực hiện trước khi dữ liệu được đưa vào xử lý bởi vì không thể biết chắc client sẽ gửi gì cho bạn để cập nhật, thêm nữa, bạn muốn kiểm tra ngay trên client để giảm tải cho server, đồng thời việc thông báo lỗi cũng dễ dàng và thân thiện hơn. Trong các mô hình cổ điển, bạn sẽ phải thực hiện việc kiểm tra hai lần để đảm bảo điểu đó. Nhưng điều này cũng dễ dẫn đến lỗi cũng như làm dữ liệu mất đồng bộ. Vậy nên RIA Services đưa ra một mô hình chung cho việc xác thực dữ liệu.
Các trường hợp phổ biến nhất có thể được xử lý bởi việc áp dụng các thuộc tính vào cho mô hình dữ liệu trên server. Các thuộc tính đó được dùng chung bởi nhiều thành phần trong .NET Framework như ASP.NET Dynamic Data, ASP.NET MVC và RIA Services. Bạn có thể tìm thấy danh sách đầy đủ trong System.ComponentModel.DataAnnotations. Ở đây cung cấp cho bạn một ví dụ:
[Display(Order = 0)] [Required(ErrorMessage = "Please provide a name")] public string Name { get; set; } [Range(0, 999)] public Nullable<decimal> Price { get; set; } [RegularExpression(@"^http\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?$", ErrorMessage = "Please use standard Url format")] public string Uri { get; set; }Như bạn thấy ở trên, việc kiểm tra dữ liệu trên client được thực hiện tự động, nhưng chúng cũng được chạy lại lần nữa trước khi phương thức Update trên server chạy. Điều này cho phép bạn tách việc kiểm tra ra khỏi quá trình xử lý cập nhật. Một điều tuyệt vời khác là các phép kiểm tra đó sẽ được áp dụng theo cùng cách dù cho thực thể đó được xử lý ở bất kỳ chỗ nào. Và tất nhiên bạn hoàn toàn có thể bản địa hóa các thông báo lỗi bằng việc truyền một resource ID thay cho một chuỗi văn bản cụ thể. Bạn cũng có thể đọc các thuộc tính xác thực như trên từ một file cấu hình hoặc từ CSDL thay vì dùng các thuộc tính trong code.
Nhưng rõ ràng nó chưa thể áp dụng được trong mọi trường hợp. Đôi khi bạn vẫn cần viết hẳn một thủ tục để thực hiện các phép kiểm tra phức tạp. Hãy xem một ví dụ hoàn chỉnh, vì không thể có thuộc tính nào có thể dùng để kiểm tra theo cách chúng ta muốn, nên ta cần viết một thủ tục nhỏ bằng C# để làm điều này, sau đó có thể áp dụng cho Description bằng cách khai báo thuộc tính tương ứng.
[CustomValidation(typeof(PlateValidationRules), "IsDescriptionValid")] public string Description { get; set; }
1: public static ValidationResult IsDescriptionValid(string description) 2: { 3: if (description != null && description.Split().Length < 5) 4: { 5: var vr = new ValidationResult("Valid descriptions must have 5 or more words.", 6: new string[] { "Description" }); 7: return vr; 8: } 9: 10: return ValidationResult.Success; 11: } 12:
Ở dòng 1, phương thức cần được khai báo để trả về một ValidationResult – đây là một lớp từ DataAnnotations chứa thông tin về bất kỳ lỗi xác thực nào. Phương thức này cũng nhận vào một tham số cùng kiểu với trường dữ liệu mà nó được áp dụng. Bạn có thế làm điều này tại cấp độ thực thể để cho phép áp dụng việc kiểm tra với nhiều trường khác nhau.
Dòng 3, tôi cố ý làm vài phép kiểm tra để xác định xem mô tả có hợp lệ hay không.
Trên dòng 5 và 6, tôi trả về một lỗi và chỉ ra nó được áp dụng cho trường nào.
Giờ hãy chạy ưng dụng. Bạn sẽ thấy chúng ta có thể chỉnh sửa mô tả và chuyển sang ô nhập liệu khác mà không bị lỗi, tuy nhiên, khi submit, chúng ta sẽ nhận được một thông báo lỗi giống hệt cái ta thấy trước đây. Chú ý chúng ta có thể gửi một số thực thể và mỗi cái trong chúng có thể gây lỗi. RIA Services sẽ đảm bảo giữ tất cả và yêu cầu người dùng chỉnh sửa các thông tin lỗi như cách chúng ta thấy ở đây.
Chú ý, nếu bạn thấy một hộp thoại kiểu như thế này:
Thì có nghĩa là bạn cần viết một trình xử lý cho SubmitChanges trong DomainDataSource.
<riaControls:DomainDataSource SubmittedChanges="plateDomainDataSource_SubmittedChanges"
private void plateDomainDataSource_SubmittedChanges(object sender, SubmittedChangesEventArgs e) { if (e.HasError && e.EntitiesInError.All(t => t.HasValidationErrors)) { e.MarkErrorAsHandled(); } else { System.Windows.MessageBox.Show(e.Error.ToString(), "Load Error", System.Windows.MessageBoxButton.OK); e.MarkErrorAsHandled(); } }
Giờ thì thật tuyệt là chúng ta đã có đầy đủ sức mạnh của .NET để viết các quy tắc xác thực. Nhưng vẫn còn một nhược điểm là chúng ta chỉ có thể áp dụng các phép kiểm tra khi submit. Cái mà tôi thực sự muốn là các phép kiểm tra này phải được thực thi trên cả server và client. May mắn là ta có sức mạnh của .NET trên cả server lẫn client, vậy nên ta có thể chia sẻ việc kiểm tra này trên cả hai phía. Để làm điều này, bạn chỉ đơn giản đổi đuôi tên file chứa các quy tắc kiểm tra thành “.shared.cs”. Việc thay đổi này sẽ làm MSBuild dịch file này cho cả client và server.
Giờ đoạn code của bạn sẽ được thực hiện cả trên client và server. Do vậy nếu đây là một Description bị lỗi, nó sẽ không phải gửi lên server rồi mới kiểm tra được.
Và tất nhiên, trong một ứng dụng thật, sẽ có trường hợp bạn muốn kiểm tra dữ liệu ở cả hai phía, nhưng có thể có các quy tắc chỉ áp dụng cho server, và RIA Services hỗ trợ đầy đủ việc này. Chỉ đơn giản định nghĩa các quy tắc bạn muốn dùng chung trong các file .shared.cs và các quy tắc chi dành riêng cho server trong các file khác.