2010-02-17 40 views
41

Kể từ bây giờ tôi đã sử dụng thư viện FluentValidation tuyệt vời để xác thực các lớp mô hình của tôi. Trong các ứng dụng web tôi sử dụng nó kết hợp với các plugin jquery.validate để thực hiện xác nhận phía khách hàng là tốt. Một hạn chế là nhiều logic xác thực được lặp lại ở phía máy khách và không còn tập trung tại một nơi duy nhất nữa.Xác nhận mô hình tùy chỉnh các thuộc tính phụ thuộc bằng cách sử dụng Chú thích dữ liệu

Vì lý do này, tôi đang tìm kiếm giải pháp thay thế. Có many ví dụ ra there cho biết cách sử dụng chú thích dữ liệu để thực hiện xác thực mô hình. Nó trông rất hứa hẹn. Một điều tôi không thể tìm ra là làm cách nào để xác thực một thuộc tính phụ thuộc vào giá trị thuộc tính khác.

Hãy lấy ví dụ như mô hình sau:

public class Event 
{ 
    [Required] 
    public DateTime? StartDate { get; set; } 
    [Required] 
    public DateTime? EndDate { get; set; } 
} 

tôi muốn đảm bảo rằng EndDate lớn hơn StartDate. Tôi có thể viết thuộc tính xác thực tùy chỉnh mở rộng ValidationAttribute để thực hiện logic xác thực tùy chỉnh. Đáng tiếc là tôi không thể tìm thấy một cách để có được những ví dụ mô hình:

public class CustomValidationAttribute : ValidationAttribute 
{ 
    public override bool IsValid(object value) 
    { 
     // value represents the property value on which this attribute is applied 
     // but how to obtain the object instance to which this property belongs? 
     return true; 
    } 
} 

tôi thấy rằng các CustomValidationAttribute dường như để làm công việc bởi vì nó có thuộc tính này ValidationContext có chứa các trường hợp đối tượng được xác nhận. Thật không may thuộc tính này đã được thêm vào chỉ trong .NET 4.0. Vì vậy, câu hỏi của tôi là: tôi có thể đạt được chức năng tương tự trong .NET 3.5 SP1 không?


UPDATE:

Dường như FluentValidation already supports xác nhận clientside và siêu dữ liệu trong ASP.NET MVC 2.

Tuy nhiên nó sẽ là tốt để biết tuy nhiên nếu các chú thích dữ liệu có thể được sử dụng để xác nhận tính chất phụ thuộc .

+0

có bạn hoặc có ai tìm ra cách để thu thập dữ liệu và FluentValidation hoạt động (để xác thực) cùng nhau trên cùng một lớp/mô hình không? nếu như vậy sẽ là tuyệt vời, tôi có một thread về cuộc thảo luận này với FV tác giả Jeremy, bạn có thể xem ở đây: http://fluentvalidation.codeplex.com/Thread/View.aspx?ThreadId=212371 –

Trả lời

28

MVC2 đi kèm với mẫu "PropertiesMustMatchAttribute" cho biết cách nhận DataAnnotations hoạt động cho bạn và nó sẽ hoạt động trong cả .NET 3.5 và .NET 4.0.Đó là mẫu mã trông như thế này:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] 
public sealed class PropertiesMustMatchAttribute : ValidationAttribute 
{ 
    private const string _defaultErrorMessage = "'{0}' and '{1}' do not match."; 

    private readonly object _typeId = new object(); 

    public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty) 
     : base(_defaultErrorMessage) 
    { 
     OriginalProperty = originalProperty; 
     ConfirmProperty = confirmProperty; 
    } 

    public string ConfirmProperty 
    { 
     get; 
     private set; 
    } 

    public string OriginalProperty 
    { 
     get; 
     private set; 
    } 

    public override object TypeId 
    { 
     get 
     { 
      return _typeId; 
     } 
    } 

    public override string FormatErrorMessage(string name) 
    { 
     return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, 
      OriginalProperty, ConfirmProperty); 
    } 

    public override bool IsValid(object value) 
    { 
     PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value); 
     object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value); 
     object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value); 
     return Object.Equals(originalValue, confirmValue); 
    } 
} 

Khi bạn sử dụng thuộc tính đó, chứ không phải là đặt nó trên một tài sản của lớp mô hình của bạn, bạn đặt nó trên chính lớp:

[PropertiesMustMatch("NewPassword", "ConfirmPassword", ErrorMessage = "The new password and confirmation password do not match.")] 
public class ChangePasswordModel 
{ 
    public string NewPassword { get; set; } 
    public string ConfirmPassword { get; set; } 
} 

Khi "IsValid "được gọi trên thuộc tính tùy chỉnh của bạn, toàn bộ cá thể mô hình được truyền cho nó để bạn có thể nhận được các giá trị thuộc tính phụ thuộc theo cách đó. Bạn có thể dễ dàng theo dõi mẫu này để tạo thuộc tính so sánh ngày hoặc thậm chí thuộc tính so sánh chung hơn.

Brad Wilson has a good example on his blog cho biết cách thêm phần phía máy khách của quá trình xác thực, mặc dù tôi không chắc chắn ví dụ đó có hoạt động ở cả .NET 3.5 và .NET 4.0 hay không.

+2

Tôi đã thử điều này nhưng tôi không bao giờ có thể nhận được lỗi xác nhận để hiển thị trên các trang aspx/lượt xem của tôi. Tôi đã thử gọi validationmessagefor bằng cách sử dụng một chuỗi rỗng, cũng đã thử sử dụng tóm tắt xác thực và nó không hiển thị ở đó (như trong ví dụ thuộc tính) –

+1

Tôi lãng phí một vài giờ cố gắng để làm việc này và nghĩ rằng mã của tôi đã sai, cho đến khi tôi cuối cùng nhận ra tôi chỉ đơn giản là không thử nghiệm nó đúng khi tôi thấy bài đăng này: http://stackoverflow.com/questions/3586324/custom-validation-attribute-is-not-called-asp-net-mvc (về cơ bản xác nhận cấp trường/thuộc tính sẽ kích hoạt trước tiên, vì vậy bạn cần những xác nhận hợp lệ hoàn toàn trước khi nó sẽ kích hoạt phương thức isvalid() thuộc tính cấp lớp của bạn). – jimasp

+0

Sau này tôi tìm thấy một bài đăng tốt hơn về xác thực lớp học sau khi vấn đề xác thực trường tại đây: http://stackoverflow.com/questions/3099397/property-level-validation-errors-hinder-the-validation-of-class-level-validation – jimasp

3

Vì các phương pháp của DataAnnotations of .NET 3.5 không cho phép bạn cung cấp đối tượng thực tế được xác thực hoặc ngữ cảnh xác nhận, bạn sẽ phải thực hiện một chút thủ thuật để thực hiện việc này. Tôi phải thừa nhận tôi không quen thuộc với ASP.NET MVC, vì vậy tôi không thể nói làm thế nào để làm điều này chính xác kết hợp với MCV, nhưng bạn có thể thử bằng cách sử dụng một giá trị thread-tĩnh để vượt qua các đối số chính nó. Đây là một ví dụ với một cái gì đó mà có thể làm việc.

Đầu tiên tạo ra một số loại 'phạm vi đối tượng' cho phép bạn vượt qua đối tượng xung quanh mà không cần phải vượt qua chúng thông qua các cuộc gọi stack:

public sealed class ContextScope : IDisposable 
{ 
    [ThreadStatic] 
    private static object currentContext; 

    public ContextScope(object context) 
    { 
     currentContext = context; 
    } 

    public static object CurrentContext 
    { 
     get { return context; } 
    } 

    public void Dispose() 
    { 
     currentContext = null; 
    } 
} 

Tiếp theo, tạo validator của bạn để sử dụng ContextScope:

public class CustomValidationAttribute : ValidationAttribute 
{ 
    public override bool IsValid(object value) 
    { 
     Event e = (Event)ObjectContext.CurrentContext; 

     // validate event here. 
    } 
} 

Và cuối cùng nhưng không kém, đảm bảo rằng các đối tượng là quá khứ xung quanh thông qua các ContextScope:

Event eventToValidate = [....]; 
using (var scope new ContextScope(eventToValidate)) 
{ 
    DataAnnotations.Validator.Validate(eventToValidate); 
} 

Điều này có hữu ích không?

+0

Steven, điều này có vẻ tốt đẹp. Điều duy nhất mà tôi sẽ sửa đổi là lưu trữ ngữ cảnh hiện tại vào HttpContext thay vì sử dụng 'ThreadStatic'. Tôi chỉ đơn giản là tránh nó trong một ứng dụng ASP.NET. –

+0

Bạn có thể giải thích lý do tại sao bạn tin rằng chúng ta nên tránh điều này trong một ứng dụng ASP.NET.Tôi sử dụng cấu trúc này trong các ứng dụng sản xuất của riêng mình, vì vậy tôi rất quan tâm vì sao điều này là xấu. – Steven

+4

Có rất nhiều bài viết trên internet tại sao điều này là xấu. Đây là một trong: http://www.hanselman.com/blog/ATaleOfTwoTechniquesTheThreadStaticAttributeAndSystemWebHttpContextCurrentItems.aspx Vấn đề với ThreadStatic là trong ASP.NET bạn không có quyền kiểm soát cuộc sống luồng và khi các luồng được tái sử dụng, có những trường hợp mà biến có thể được sửa đổi. Mọi thứ trở nên xấu hơn nếu bạn sử dụng các trang và bộ điều khiển không đồng bộ. Ví dụ một yêu cầu có thể bắt đầu trên một sợi và kết thúc trên một chủ đề khác. Vì vậy, trong ASP.NET ** chỉ ** cách để có một lưu trữ theo yêu cầu thực sự là HttpContext. –

14

Tôi có vấn đề này rất và gần đây mở nguồn giải pháp của tôi: http://foolproof.codeplex.com/

hết sức rõ ràng của giải pháp cho ví dụ trên sẽ là:

public class Event 
{ 
    [Required] 
    public DateTime? StartDate { get; set; } 

    [Required] 
    [GreaterThan("StartDate")] 
    public DateTime? EndDate { get; set; } 
} 
+0

Tôi nghĩ rằng việc xác thực ngày của GreaterThan chỉ hoạt động với định dạng Hoa Kỳ ngày – GraemeMiller

7

Thay vì PropertiesMustMatch các CompareAttribute có thể được sử dụng trong MVC3 . Theo liên kết này http://devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1:

public class RegisterModel 
{ 
    // skipped 

    [Required] 
    [ValidatePasswordLength] 
    [DataType(DataType.Password)] 
    [Display(Name = "Password")] 
    public string Password { get; set; }      

    [DataType(DataType.Password)] 
    [Display(Name = "Confirm password")] 
    [Compare("Password", ErrorMessage = "The password and confirmation do not match.")] 
    public string ConfirmPassword { get; set; } 
} 

CompareAttribute là một mới, validator rất hữu ích mà không phải là thực sự phần của System.ComponentModel.DataAnnotations, nhưng đã được bổ sung vào System.Web .Mvc DLL của nhóm. Trong khi không được đặt tên đặc biệt (chỉ so sánh mà nó làm là kiểm tra bình đẳng, vì vậy có lẽ EqualTo sẽ là rõ ràng hơn), dễ dàng thấy từ cách sử dụng mà trình xác thực này kiểm tra . bằng giá trị của một thuộc tính khác. Bạn có thể xem từ mã, rằng thuộc tính mất một thuộc tính chuỗi là tên của thuộc tính khác mà bạn đang so sánh. Việc sử dụng cổ điển của loại trình xác thực này là những gì chúng tôi đang sử dụng nó cho đây: mật khẩu xác nhận.

3

Phải mất một thời gian ngắn kể từ khi câu hỏi của bạn đã được yêu cầu, nhưng nếu bạn vẫn như siêu dữ liệu (ít nhất là đôi khi), bên dưới có thêm một giải pháp thay thế, cho phép bạn cung cấp các biểu thức logic khác nhau để các thuộc tính:

[Required] 
public DateTime? StartDate { get; set; }  
[Required] 
[AssertThat("StartDate != null && EndDate > StartDate")] 
public DateTime? EndDate { get; set; } 

Nó hoạt động cho máy chủ cũng như cho phía máy khách. Thêm chi tiết can be found here.

+0

cảm ơn bạn rất nhiều thư viện này rất hữu ích cho hầu hết mọi thứ. – Enzero

Các vấn đề liên quan