2012-04-11 23 views
9

Tôi đã thực hiện rất nhiều nghiên cứu, bao gồm cả ở đây trên SO, và tôi dường như không thể tìm thấy hướng rõ ràng. Tôi hiện đang có một ứng dụng ASP.NET MVC3, với một lớp dịch vụ nằm trên đầu trang của một kho lưu trữ.Tôi làm cách nào để chuyển các thông báo xác thực lớp dịch vụ cho người gọi?

Trong lớp dịch vụ của tôi, tôi có các chức năng như:

public class MyService{ 

    public void CreateDebitRequest(int userId, int cardId, decimal Amount, ....) 
    { 
    //perform some sort of validation on parameters, save to database 
    } 

    public void CreateCreditRequest(.....) 
    } 
     //perform some sort of validation on parameters, save to database 
    } 

    public void CreateBatchFile() 
    { 
     //construct a file using a semi-complex process which could fail 
     //write the file to the server, which could fail 
    } 


    public PaymentTransaction ChargePaymentCard(int paymentCardId, decimal amount) 
    { 
     //validate customer is eligible for amount, call 3rd party payments api call, 
     //...save to database, other potential failures, etc. 
    } 

} 

tôi đã nhìn thấy người ta nói rằng xác nhận tham số không phải là rất đặc biệt, và do đó ném một ngoại lệ không phải là rất phù hợp. Tôi cũng không thích ý tưởng đi qua trong một paramater ra, chẳng hạn như một chuỗi, và kiểm tra cho một giá trị sản phẩm nào. Tôi đã xem xét việc triển khai lớp ValidationDictionary và biến nó thành thuộc tính của bất kỳ lớp dịch vụ nào (nó có chứa một boolean IsValid, và Danh sách các thông báo lỗi, và có thể được kiểm tra sau bất kỳ cuộc gọi hàm nào trong lớp dịch vụ để xem cách mọi thứ đã đi). Tôi có thể kiểm tra tình trạng ValidationDictionary sau khi chạy bất kỳ chức năng đưa ra:

var svc = new MyService(); 
svc.CreateBatchFile(); 
if (svc.ValidationDictionary.IsValid) 
    //proceed 
else 
    //display values from svc.ValidationDictionary.Messages... 

Điều tôi không thích về vấn đề này là tôi sẽ phải cập nhật nó cho mỗi lớp dịch vụ cuộc gọi chức năng, để tránh việc nó giữ lại giá trị cũ (nếu tôi chọn không sử dụng nó cho nhiều hoặc nhiều chức năng nhất, người ta vẫn mong đợi nó có một giá trị có ý nghĩa hoặc vô giá trị sau khi chạy bất kỳ hàm nào đã cho). Một điều tôi đã xem xét là đi qua trong ValidationDictionary cho mỗi cuộc gọi chức năng có thể có thông tin xác thực chi tiết, nhưng sau đó tôi trở lại để sử dụng một tham số ...

Bạn có đề xuất nào không? Tôi dường như không thể tìm ra cách làm sạch nào. Đôi khi trả về null cho một hàm là đủ thông tin, nhưng đôi khi tôi muốn có thêm một ít thông tin xác thực được truyền lại cho người gọi. Lời khuyên nào sẽ được đánh giá cao!

Chỉnh sửa để làm rõ: Lớp dịch vụ của tôi không biết rằng đó là ứng dụng MVC đang sử dụng nó. Tầng dịch vụ chỉ có một số chức năng công cộng nhất định như CreateBatchFile() hoặc AddDebitRequest(). Đôi khi trả về null là đủ cho người tiêu dùng (trong trường hợp này là bộ điều khiển, nhưng có thể là cái gì đó khác) để biết điều gì đã xảy ra, và đôi khi người tiêu dùng muốn thêm một số thông tin từ lớp dịch vụ (có thể chuyển sang ModelState nếu người tiêu dùng một bộ điều khiển). Làm cách nào để tôi tự động điều này lên từ chính lớp dịch vụ?

Trả lời

1

Tôi đã sử dụng một hệ thống mà nó đang truyền một mảng thông điệp (hoặc tập hợp các lớp), mỗi phần tử có mã, mô tả, thông điệp thân thiện. Chúng tôi chỉ đơn giản là kiểm tra xem có gì ở đó không. Nó hoạt động tốt giữa UI và một lớp "dịch vụ" khác, tất cả ngoại lệ đều được phát hiện độc đáo, chúng được dịch thành các quy tắc xác nhận này ... chỉ cần một ý tưởng

1

Sử dụng các đối tượng ViewModel được truyền giữa các phương thức Views và Controller. Các đối tượng ViewModel có thể xử lý xác nhận bằng phương thức Validate(ValidationDictionary validationDictionary).

Bộ điều khiển sẽ phải gọi phương thức Xác thực trên đối tượng ViewModel trước khi gọi bất kỳ phương thức nào trong lớp dịch vụ. Điều này chỉ cần thiết cho các hành động http POST.

Lượt xem của bạn sau đó sẽ phải hiển thị thông báo xác thực.

Giải pháp này yêu cầu các đối tượng viewmodel được truyền giữa hành động điều khiển và khung nhìn, nhưng hiện nay chủ yếu được xử lý bởi ModelBinder trong MVC.

điều khiển của bạn (http bưu điện) hành động này sẽ giống như thế này:

[HttpPost] 
public ActionResult Foo(BarViewModel viewModel) 
{ 
    viewModel.Validate(ValidationDictionary); 

    if (!ModelState.IsValid) 
    { 
     return View(viewModel); 
    } 

    // Calls to servicelayer 
} 

phương pháp Validate của bạn trong ViewModel của bạn sẽ trông như thế này:

public void Validate(ValidationDictionary validationDictionary) 
{ 
    if (SomeProperty.Length > 30) 
    { 
     validationDictionary.AddError("SomeProperty", "Max length is 30 chars"); 
    } 
} 
+0

Hmmm ... Tôi nghĩ tôi đang nói về mức thấp hơn. Chúng tôi sẽ giả định rằng người dùng đã nhấn một nút và xác thực chế độ xem mô hình đã biến mất mà không gặp trục trặc. Bộ điều khiển của tôi sau đó sẽ gọi một hàm trong lớp dịch vụ và chức năng đó có thể thất bại vì nhiều lý do khác nhau (hoặc có thể xác thực viewmodel là rộng hơn dịch vụ thực sự yêu cầu, vì vậy khi dịch vụ xác thực các tham số cho chính nó, nó không thành công) . Làm thế nào tôi sẽ vượt qua thông tin này từ lớp dịch vụ sao lưu cho bộ điều khiển, để thậm chí có được đến điểm mà tôi có thể hiển thị với viewmodel hoặc modelstate? – Josh

+1

Tôi đảm bảo rằng tất cả các logic xác nhận đều tồn tại trong viewmodel. Tại sao bạn muốn logic xác thực trải rộng khắp nơi? Xác thực đầu vào nằm trong mô-đun giao diện người dùng. Nếu một cái gì đó là sai trong lớp dịch vụ, bạn nên ném ngoại lệ, để cung cấp đăng nhập với các thông báo lỗi tốt hơn. Ứng dụng MVC của bạn sau đó có thể chuyển hướng đến các trang lỗi thân thiện dựa trên mã ngoại lệ/http. – Marcus

+0

Giả sử tôi đang ở trong lớp dịch vụ và xác thực rằng ngày được chuyển vào CreateDebitRequest (...) không quá ba ngày trong tương lai. Có vẻ như tôi nên xác nhận điều này ở đây trong lớp dịch vụ, ngoài bất cứ nơi nào tôi xác nhận nó cao hơn trong chuỗi. Bạn có đồng ý với điều đó? Nếu vậy, sau đó bạn có nghĩ rằng tôi thực sự nên thực hiện xác nhận trong lớp dịch vụ của tôi, nhưng chỉ cần ném ngoại lệ khi xác nhận không thành công? Hoặc tôi có nên không được xác nhận ngày này trong lớp dịch vụ của tôi ở tất cả (tôi sẽ lo lắng về việc dựa vào người tiêu dùng dịch vụ)? Cảm ơn ... – Josh

9

Đây là những gì tôi làm. Có một lớp để xác thực của bạn và thay vì truyền các tham số, hãy vượt qua một mô hình khung nhìn. Vì vậy, trong trường hợp một cái gì đó của bạn như thế này, nơi ValidationResult chỉ là một lớp đơn giản w MemberName và ErrorMessage tính /:

public class DebitRequestValidator{ 

    public IEnumerable<ValidationResult> Validate(DebitRequestModel model){ 

    //do some validation 
    yield return new ValidationResult { 
     MemberName = "cardId", 
     ErrorMessage = "Invalid CardId." 
    } 
    } 

}

Sau đó, tạo ra một phương pháp mở rộng điều khiển để sao chép các kết quả này xác nhận lại tình trạng mô hình.

public static class ControllerExtensions 
{ 
    public static void AddModelErrors(this ModelStateDictionary modelState, IEnumerable<ValidationResult> validationResults) 
    { 
     if (validationResults == null) return; 

     foreach (var validationResult in validationResults) 
     { 
      modelState.AddModelError(validationResult.MemberName, validationResult.ErrorMessage); 
     } 
    } 
} 

Sau đó, trong điều khiển của bạn làm điều gì đó như

[HttpPost] 
public ActionResult DebitRequest(DebitRequestModel model) { 
    var validator = new DebitRequestValidator(); 
    var results = validator.Validate(model); 
    ModelState.AddModelErrors(results); 
    if (!ModelState.IsValid) 
    return View(model) 

    //else do other stuff here 
} 

Sau đó, theo quan điểm của bạn, bạn có thể hiển thị lỗi như bình thường.

@Html.ValidationMessageFor(m => m.CardId) 
+0

Lớp dịch vụ của tôi không biết rằng nó là một ứng dụng MVC đang tiêu thụ nó. Nó chỉ có một số chức năng công cộng như CreateBatchFile hoặc AddDebitRequest. Đôi khi trở về null là đủ cho bộ điều khiển để biết những gì đã xảy ra, và đôi khi bộ điều khiển muốn một số thông tin thêm từ lớp dịch vụ (có thể để vượt qua cùng với ModelState và whatnot). Làm cách nào để tôi tự động điều này lên từ chính lớp dịch vụ? – Josh

+0

@Josh - Lớp dịch vụ trong ví dụ này không phụ thuộc vào MVC, chỉ có lớp ValidationResult, mà chỉ là một lớp thông thường mà tôi đã tạo. Bạn có thể làm điều tương tự bằng cách đi qua trong ModelState, nhưng tôi nghĩ rằng việc trả về một IEnumerable là một cách tốt hơn bởi vì nó sẽ dễ dàng hơn để kiểm tra. –

+0

Tôi sẽ trả về IEnumberable ở đâu?Tôi hiểu rằng việc xác nhận nên được thực hiện ở một mức nào đó trước khi tôi nhận được lời gọi hàm dịch vụ, nhưng điều gì sẽ xảy ra nếu lỗi xảy ra trong hàm thực tế đó trong lớp dịch vụ (ví dụ trong svc.CreateBatchFile())? Tôi có phải vượt qua trong một IEnumerable trống để các chức năng dịch vụ như là một param ra, và sau đó kiểm tra nó sau khi gọi chức năng dịch vụ của tôi? Ví dụ: svc.CreateBatchFile (out validationResults)? Cảm ơn – Josh

1

Nếu bạn chỉ cần làm ViewModel Validation, FluentValidation là một thư viện tuyệt vời.

Nếu bạn muốn bao gồm xác thực doanh nghiệp làm phản hồi cho người dùng, bạn có thể sử dụng mẫu bộ điều hợp, mẫu này sẽ cung cấp cho bạn những gì bạn muốn.

Tạo giao diện (IValidationDictionary hoặc tương tự). Giao diện này sẽ định nghĩa một phương thức AddError và sẽ được chuyển tới dịch vụ của bạn để thêm các thông báo lỗi.

public interface IValidationDictionary 
{ 
    void AddError(string key, string errorMessage); 
} 

Tạo ModelStateAdapter cho ứng dụng mvc của bạn.

public class ModelStateAdapter : IValidationDictionary 
{ 
    private ModelStateDictionary _modelState; 

    public ModelStateAdapter(ModelStateDictionary modelState) 
    { 
     _modelState = modelState; 
    } 

    public void AddError(string key, string errorMessage) 
    { 
     _modelState.AddModelError(key, errorMessage); 
    } 
} 

cuộc gọi dịch vụ của bạn cần xác nhận sẽ yêu cầu các IValidationDictionary

public class MyService 
    {   
     public void CreateDebitRequest(int userId, int cardId, decimal Amount, .... , IValidationDictionary validationDictionary) 
      { 
       if(userId == 0) 
        validationDictionary.AddError("UserId", "UserId cannot be 0"); 
      } 
    } 

Sau đó bạn sẽ có một sự phụ thuộc vào IValidationDictionary nhưng không phải trên MVC mà cũng sẽ làm cho giải pháp của bạn kiểm chứng.

Nếu bạn cần triển khai các dịch vụ trong ứng dụng không có ModelStateDictionary, bạn sẽ chỉ triển khai giao diện IValidationDictionary trên lớp được sử dụng để giữ lỗi của bạn.

khiển dụ:

public ActionResult Test(ViewModel viewModel) 
{ 
    var modelStateAdapter = new ModelStateAdapter(ModelState); 
    _serviceName.CreateDebitRequest(viewModel.UserId, viewModel.CardId, ... , modelStateAdapter); 

    if(ModelState.IsValid) 
     return View("Success") 

    return View(viewModel); 
} 

Pro của phương pháp này:

  • Không phụ thuộc vào các thư viện gọi
  • Có thể thử các IValidationDictionary để kiểm tra.

Con của phương pháp này:

  • Bạn cần phải vượt qua IValidationDictionary cho mọi phương pháp mà bạn muốn làm xác nhận trên đó sẽ được trả lại cho người dùng.

    Hoặc

    bạn cần phải khởi từ điển xác nhận của dịch vụ (nếu bạn quyết định có IValidationDictionary như một lĩnh vực tư nhân), trong mỗi hành động điều khiển bạn muốn xác nhận đối với.

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