8

Tôi đã có một ViewModel có một số xác thực DataAnnotations và sau đó để xác thực phức tạp hơn triển khai IValidatableObject và sử dụng phương thức Validate.IValidatableObject Xác thực phương pháp kích hoạt khi DataAnnotations không thành công

Hành vi tôi mong đợi là this one: trước hết là DataAnnotations và sau đó, chỉ khi không có lỗi, phương thức Xác thực. Làm cách nào tôi biết được điều này không phải lúc nào cũng đúng. My ViewModel (một bản trình diễn) có ba tệp được nộp string, một decimal và một decimal?. Tất cả ba thuộc tính đều chỉ có thuộc tính Bắt buộc. Đối với stringdecimal? hành vi là hành vi mong đợi, nhưng đối với decimal, khi trống, Xác thực bắt buộc không thành công (cho đến nay rất tốt) và sau đó thực thi phương thức Xác thực. Nếu tôi kiểm tra tài sản, giá trị của nó bằng không.

Điều gì đang xảy ra ở đây? Tôi đang thiếu gì?

Lưu ý: Tôi biết rằng Thuộc tính bắt buộc giả sử để kiểm tra xem giá trị có rỗng không. Vì vậy, tôi hy vọng sẽ được yêu cầu không sử dụng thuộc tính Bắt buộc trong các loại không thể null (vì nó sẽ không bao giờ kích hoạt), hoặc, bằng cách nào đó thuộc tính hiểu các giá trị POST và lưu ý rằng trường không được lấp đầy. Trong trường hợp đầu tiên, thuộc tính sẽ không kích hoạt và phương thức Validate sẽ kích hoạt. Trong trường hợp thứ hai, thuộc tính sẽ kích hoạt và phương thức Validate sẽ không kích hoạt. Nhưng kết quả của tôi là: các thuộc tính gây nên và phương thức Validate kích hoạt.

Đây là mã (không có gì quá đặc biệt):

Bộ điều khiển:

public ActionResult Index() 
{ 
    return View(HomeModel.LoadHome()); 
} 

[HttpPost] 
public ActionResult Index(HomeViewModel viewModel) 
{ 
    try 
    { 
     if (ModelState.IsValid) 
     { 
      HomeModel.ProcessHome(viewModel); 
      return RedirectToAction("Index", "Result"); 
     } 
    } 
    catch (ApplicationException ex) 
    { 
     ModelState.AddModelError(string.Empty, ex.Message); 
    } 
    catch (Exception ex) 
    { 
     ModelState.AddModelError(string.Empty, "Internal error."); 
    } 
    return View(viewModel); 
} 

mẫu:

public static HomeViewModel LoadHome() 
{ 
    HomeViewModel viewModel = new HomeViewModel(); 
    viewModel.String = string.Empty; 
    return viewModel; 
} 

public static void ProcessHome(HomeViewModel viewModel) 
{ 
    // Not relevant code 
} 

ViewModel:

public class HomeViewModel : IValidatableObject 
{ 
    [Required(ErrorMessage = "Required {0}")] 
    [Display(Name = "string")] 
    public string String { get; set; } 

    [Required(ErrorMessage = "Required {0}")] 
    [Display(Name = "decimal")] 
    public decimal Decimal { get; set; } 

    [Required(ErrorMessage = "Required {0}")] 
    [Display(Name = "decimal?")] 
    public decimal? DecimalNullable { get; set; } 

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
     yield return new ValidationResult("Error from Validate method"); 
    } 
} 

Xem:

012.
@model MVCTest1.ViewModels.HomeViewModel 

@{ 
    Layout = "~/Views/Shared/_Layout.cshtml"; 
} 

@using (Html.BeginForm(null, null, FormMethod.Post)) 
{ 
    <div> 
     @Html.ValidationSummary() 
    </div> 
    <label id="lblNombre" for="Nombre">Nombre:</label> 
    @Html.TextBoxFor(m => m.Nombre) 
    <label id="lblDecimal" for="Decimal">Decimal:</label> 
    @Html.TextBoxFor(m => m.Decimal) 
    <label id="lblDecimalNullable" for="DecimalNullable">Decimal?:</label> 
    @Html.TextBoxFor(m => m.DecimalNullable) 
    <button type="submit" id="aceptar">Aceptar</button> 
    <button type="submit" id="superAceptar">SuperAceptar</button> 
    @Html.HiddenFor(m => m.Accion) 
} 
+1

'thập phân' sẽ không bao giờ là rỗng. Nó không phải là nullable như 'string' hoặc' decimal? '. Hành vi bạn mong đợi là gì? – Joao

+0

@Jota Tôi cho rằng thuộc tính Bắt buộc sẽ không hoạt động vì 'thập phân' sẽ không bao giờ là null hoặc Phương thức xác thực không được gọi vì một số xác thực DataAnnotation không thành công. – Diego

+1

Bạn đã đặt 'decimal' thành nullable nếu không [Bắt buộc] sẽ không bao giờ kích hoạt. Có lý do nào để không làm điều đó không? – Joao

Trả lời

14

cân nhắc sau khi bình luận trao đổi:

Các đồng thuận và expected behavior among developersIValidatableObject 'phương pháp s Validate() chỉ được gọi nếu không có thuộc tính validation được kích hoạt. Nói tóm lại, các thuật toán dự kiến ​​là thế này (lấy từ các liên kết trước đó):

    thuộc tính
  1. Validate tài sản cấp
  2. Nếu bất kỳ xác nhận là không hợp lệ, hủy bỏ xác nhận trả lại thất bại (s)
  3. Validate đối tượng cấp thuộc tính
  4. Nếu bất kỳ xác nhận là không hợp lệ, hủy bỏ xác nhận trả lại thất bại (s)
  5. Nếu trên khuôn khổ máy tính để bàn và đối tượng thực hiện IValidatableObje ct, sau đó gọi phương thức Validate của nó và trả lại bất kỳ thất bại (s)

Tuy nhiên, sử dụng mã câu hỏi của, Validate được gọi là ngay cả sau khi [Required] gây.Điều này có vẻ rõ ràng là lỗi MVC. Được báo cáo là here.

Ba cách giải quyết có thể:

  1. Có một workaround here mặc dù với một số vấn đề tuyên bố với nó là việc sử dụng, ngoài việc vi phạm các hành vi MVC mong đợi. Với một vài thay đổi để tránh thể hiện nhiều hơn một lỗi trong cùng lĩnh vực ở đây là các mã:

    viewModel 
        .Validate(new ValidationContext(viewModel, null, null)) 
        .ToList() 
        .ForEach(e => e.MemberNames.ToList().ForEach(m => 
        { 
         if (ModelState[m].Errors.Count == 0) 
          ModelState.AddModelError(m, e.ErrorMessage); 
        })); 
    
  2. Quên IValidatableObject và chỉ sử dụng các thuộc tính. Đó là sạch sẽ, trực tiếp, tốt hơn để xử lý nội địa hóa và tốt nhất của tất cả các tái sử dụng trong số tất cả các mô hình. Chỉ cần triển khai ValidationAttribute cho mỗi xác thực bạn muốn thực hiện. Bạn có thể xác nhận tất cả các mô hình hoặc thuộc tính cụ thể, tùy thuộc vào bạn. Ngoài các thuộc tính có sẵn theo mặc định (DataType, Regex, Bắt buộc và tất cả những thứ đó) có một số thư viện có các xác thực được sử dụng nhiều nhất. Công cụ triển khai "số bị thiếu"FluentValidation.

  3. Chỉ triển khai IValidatableObject giao diện vứt bỏ data annotations. Điều này có vẻ là một lựa chọn hợp lý nếu nó là một mô hình rất cụ thể và nó không đòi hỏi phải xác nhận nhiều. Trên hầu hết các trường hợp, nhà phát triển sẽ thực hiện tất cả việc xác thực thông thường và thông thường (ví dụ: Bắt buộc, v.v.) dẫn đến sao chép mã trên các xác thực đã được triển khai theo mặc định nếu các thuộc tính được sử dụng. Cũng không có khả năng sử dụng lại.

trả lời trước khi nhận xét:

Trước hết tôi đã tạo ra một dự án mới, từ đầu với chỉ mã mà bạn cung cấp. KHÔNG BAO GIỜ đã kích hoạt cả chú thích dữ liệu và phương thức Xác thực cùng một lúc.

Dù sao, biết điều này,

Theo thiết kế, MVC3 thêm một thuộc tính [Required] với các loại giá trị không nullable, như int, DateTime hay, vâng, decimal. Vì vậy, ngay cả khi bạn loại bỏ thuộc tính bắt buộc từ đó decimal nó hoạt động giống như nó là một trong đó.

Điều này gây tranh cãi cho sự sai trái của nó (hoặc không) nhưng cách nó được thiết kế.

Trong bạn ví dụ:

  • 'DataAnnotation' trigger nếu [Bắt buộc] là hiện tại và không có giá trị được đưa ra. Hoàn toàn dễ hiểu từ quan điểm của tôi
  • Trình kích hoạt 'DataAnnotation' nếu không có [Bắt buộc] nhưng giá trị không thể vô hiệu. Có thể gây tranh cãi nhưng tôi có xu hướng đồng ý với nó bởi vì nếu thuộc tính không có giá trị, thì giá trị phải được nhập, nếu không thì không hiển thị giá trị đó cho người dùng hoặc chỉ sử dụng giá trị rỗng decimal.

Hành vi này, vì nó dường như, có thể được tắt với điều này trong phương pháp Application_Start của bạn:

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false; 

Tôi đoán tên của tài sản là tự giải thích.

Dù sao, tôi không hiểu tại sao bạn muốn người dùng nhập nội dung nào đó không bắt buộc và không làm cho thuộc tính đó không thể thực hiện được. Nếu đó là null thì đó là công việc của bạn để kiểm tra xem nó, nếu bạn không thể để trống số, trước khi xác nhận, trong bộ điều khiển.

public ActionResult Index(HomeViewModel viewModel) 
{ 
    // Complete values that the user may have 
    // not filled (all not-required/nullables) 

    if (viewModel.Decimal == null) 
    { 
     viewModel.Decimal = 0m; 
    } 

    // Now I can validate the model 

    if (ModelState.IsValid) 
    { 
     HomeModel.ProcessHome(viewModel); 
     return RedirectToAction("Ok"); 
    } 
} 

Bạn nghĩ gì về phương pháp này hoặc không nên theo cách này?

+0

Tôi đồng ý với bạn khi bạn nói đúng quyền được yêu cầu đối với các loại không thể vô hiệu. Vấn đề của tôi (có vẻ như bạn không thể sinh sản) là thuộc tính bắt buộc (hoặc tường minh) bắt buộc được kích hoạt và sau đó phương thức Validate được gọi. Tôi chỉ mong đợi phương pháp này không bao giờ được gọi khi một số xác thực DataAnnotations không thành công (không có vấn đề gì nếu nó ẩn hoặc rõ ràng) – Diego

+0

Chỉ có một kịch bản mà tôi có thể tái tạo hành vi đó (và quên rằng * AddImplicitRequiredAttributeForValueTypes * tồn tại): khi sử dụng non-nullable 'thập phân' không có [Bắt buộc] trên đầu trang của nó. Dường như yêu cầu tiềm ẩn không được tính là kích hoạt DataAnnotation và Validate được gọi. – Joao

+0

Đây cũng là kịch bản của bạn? – Joao

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