2012-04-11 9 views
9

Zrobiłem dużo badań, w tym tutaj na SO, i nie mogę znaleźć wyraźnego kierunku. Obecnie mam aplikację ASP.NET MVC3, z warstwą usługi, która znajduje się na szczycie repozytorium.W jaki sposób można przekazać wiadomości sprawdzania poprawności warstwy usługi do osoby dzwoniącej?

W moim warstwy usługowej, mam takie funkcje jak:

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. 
    } 

} 

Widziałem ludzie mówią, że walidacja parametr nie jest bardzo wyjątkowy i tak rzuca wyjątek nie jest bardzo przylegające. Nie podoba mi się też pomysł podania w out-out parametru, takiego jak ciąg znaków i sprawdzenia pustej wartości. Zastanawiam się nad zaimplementowaniem klasy ValidationDictionary i uczynieniem jej własnością dowolnej klasy usług (zawierałaby ona boolean IsValid oraz listę komunikatów o błędach i mogłaby zostać sprawdzona po dowolnym wywołaniu funkcji w warstwie serwisowej, aby zobaczyć, jak rzeczy poszły). Mogę sprawdzić status ValidationDictionary po uruchomieniu dowolną funkcję:

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

Rzecz nie podoba mi o to, że będę musiał go zaktualizować do każdej warstwy usługa wywołania funkcji, aby uniknąć konieczności zachowa stare wartości (jeśli zdecydowałem się nie używać go dla wielu lub większości funkcji, można by się spodziewać, że po uruchomieniu jakiejkolwiek funkcji będzie mieć wartość znaczącą lub zerową). Kolejną rzeczą, którą rozważałem jest przekazywanie w ValidationDictionary dla każdego wywołania funkcji, które może zawierać szczegółowe informacje o sprawdzaniu poprawności, ale wracam do korzystania z parametru out ...

Czy któryś z was ma rekomendacje? Nie mogę wymyślić żadnego prostego sposobu robienia tego. Czasami zwracanie wartości null dla funkcji jest wystarczającą informacją, ale czasami potrzebuję trochę więcej informacji o sprawdzaniu poprawności przekazywanych do dzwoniącego. Każda rada byłaby doceniona!

Edit wyjaśnienie: warstwa Moje usługi nie jest świadoma, że ​​jest to aplikacja MVC, że jest to czasochłonne. Warstwa usługi ma tylko pewne publiczne funkcje, takie jak CreateBatchFile() lub AddDebitRequest(). Czasami zwracanie wartości zerowej wystarcza konsumentowi (w tym przypadku kontrolerowi, ale może być czymś innym), aby wiedzieć, co się stało, a czasami konsument chciałby uzyskać więcej informacji z warstwy usługi (może przejść do ModelState, jeśli konsument jest kontroler). Jak mogę to spaprać z samej warstwy usługi?

Odpowiedz

1

Użyłem systemu, w którym przekazywał on tablicę wiadomości (lub zbiór klas), każdy element miał kody, opisy, przyjazne wiadomości. Kiedyś sprawdzaliśmy, czy coś tam było. Udało się to znakomicie między interfejsem użytkownika a kolejną warstwą "usługi", wszystkie wyjątki zostały ładnie uchwycone, zostały przetłumaczone na te reguły sprawdzania poprawności ... po prostu pomysł

1

Użyj obiektów ViewModel, które są przekazywane między metodami Widoki i metody kontrolera. Obiekty ViewModel mogą obsłużyć sprawdzanie poprawności metodą Validate(ValidationDictionary validationDictionary).

Sterownik będzie musiał wywołać metodę sprawdzania poprawności obiektu ViewModel przed wywołaniem dowolnej metody w warstwie usługi. To powinno być konieczne tylko w przypadku działań HTTP POST.

Twoje widoki będą musiały wyświetlać komunikaty sprawdzania poprawności.

To rozwiązanie wymaga przesyłania obiektów viewmodel między operacją kontrolera a widokiem, ale obecnie jest ona w większości obsługiwana przez moduł ModelBinder w MVC.

Twój kontroler (HTTP POST) działania będzie wyglądać następująco:

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

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

    // Calls to servicelayer 
} 

Twoja metoda Weryfikuj w ViewModel będzie wyglądać następująco:

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

Hmmm ... Myślę, że mówię o niższym poziomie. Zakładamy, że użytkownik nacisnął przycisk, a walidacja modelu widoku wyszła bez żadnych problemów. Mój kontroler wywoła funkcję w warstwie usługi, a ta funkcja może się nie powieść z różnych powodów (a może sprawdzanie poprawności widoku było bardziej hojne, niż wymaga tego usługa, więc gdy usługa sprawdza poprawność samych parametrów, to się nie powiedzie) . W jaki sposób przekazałbym te informacje z warstwy usługi z powrotem do kontrolera, aby przejść do punktu, w którym mogłem wyświetlać z viewmodel lub modelstate? – Josh

+1

Upewniam się, że wszystkie logiki walidacji istnieją w viewmodel. Dlaczego chcesz, aby logika walidacji rozprzestrzeniała się w każdym miejscu? Walidacja wejścia należy do modułu interfejsu użytkownika. Jeśli coś jest nie tak w warstwie usługi, powinieneś rzucić wyjątek, aby zapewnić rejestrowanie z lepszymi komunikatami o błędach. Twoja aplikacja MVC może następnie przekierować na przyjazne strony błędów oparte na kodzie wyjątku/http. – Marcus

+0

Załóżmy, że jestem w warstwie usługi i sprawdzam, czy data przekazana do CreateDebitRequest (...) nie będzie więcej niż trzy dni w przyszłości. Wygląda na to, że powinienem sprawdzać to tutaj w warstwie usługi, oprócz tego, gdzie sprawdziłem to wyżej w łańcuchu. Zgodziłbyś się z tym? Jeśli tak, to czy uważasz, że faktycznie powinienem przeprowadzać sprawdzanie poprawności w mojej warstwie usługi, ale po prostu zgłaszać wyjątki, gdy sprawdzanie poprawności nie powiedzie się? Czy nie powinienem w ogóle zatwierdzać tej daty w mojej warstwie serwisowej (byłbym zdenerwowany powołując się na konsumenta usług)? Dzięki ... – Josh

9

To, co robię. Miej klasę do sprawdzania poprawności i zamiast przekazywać parametry przekazuj model widoku. Więc w twoim przypadku coś takiego, gdzie ValidationResult jest tylko prosta klasa w/membername i ErrorMessage właściwości:

public class DebitRequestValidator{ 

    public IEnumerable<ValidationResult> Validate(DebitRequestModel model){ 

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

}

Następnie należy utworzyć metodę rozszerzenia kontroler do skopiowania tych wyników weryfikacji stanu modelu.

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); 
     } 
    } 
} 

Następnie w kontrolerze zrobić coś takiego

[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 
} 

Następnie w widoku można wyświetlać błędy jak zwykle.

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

Moja warstwa usług nie jest świadoma, że ​​jest to aplikacja MVC, która ją zużywa. Ma tylko pewne funkcje publiczne, takie jak CreateBatchFile lub AddDebitRequest. Czasami zwracanie wartości null wystarcza, aby kontroler wiedział, co się stało, a czasami kontroler chciałby uzyskać więcej informacji z warstwy usługi (może przejść do ModelState i cokolwiek). Jak mogę to spaprać z samej warstwy usługi? – Josh

+0

@Josh - Warstwa usługi w tym przykładzie nie ma zależności od MVC, tylko klasa ValidationResult, która jest zwykłą klasą, którą utworzyłem. Możesz zrobić to samo, przechodząc w ModelState, ale myślę, że zwrócenie IEnumerable jest lepszym sposobem, ponieważ byłoby łatwiej przetestować. –

+0

Gdzie powinienem zwrócić ten IEnumberable ?Rozumiem, że sprawdzanie poprawności powinno być wykonywane na pewnym poziomie, zanim dojdę do wywołania funkcji usługi, ale co się stanie, jeśli błędy wystąpią w tej faktycznej funkcji w warstwie usługi (na przykład w svc.CreateBatchFile())? Czy musiałbym przekazać puste IEnumerable do funkcji serwisowej jako out-out, a następnie sprawdzić to po wywołaniu funkcji serwisowej? Ex: svc.CreateBatchFile (out validationResults)? Dzięki – Josh

1

Jeśli jesteś po prostu robi ViewModel walidacji FluentValidation jest doskonałą bibliotekę.

Jeśli chcesz uwzględnić sprawdzanie poprawności biznesowej jako informację zwrotną dla użytkownika, możesz użyć wzoru adaptera, który dostarczy Ci tego, czego potrzebujesz.

Utwórz interfejs (IValidationDictionary lub coś podobnego). Ten interfejs definiuje metodę AddError i zostanie przekazany do Twojej usługi w celu dodania komunikatów o błędach.

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

Utwórz modelStateAdapter dla aplikacji mvc.

public class ModelStateAdapter : IValidationDictionary 
{ 
    private ModelStateDictionary _modelState; 

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

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

Twoje połączenia usług, które wymagają walidacji wymagałoby 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"); 
      } 
    } 

Będziesz wtedy mieć zależność od IValidationDictionary ale nie na MVC, które także rozwiązanie sprawdzalne.

Jeśli chcesz wdrożyć usługi w aplikacji, która nie miała numeru ModelStateDictionary, wystarczy zaimplementować interfejs IValidationDictionary w klasie używanej do przechowywania błędów.

Controller przykład:

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 na tego podejścia:

  • Nie zależność od powołania bibliotek
  • To możliwe mock IValidationDictionary do testów.

Con tego podejścia:

  • Trzeba zdać IValidationDictionary do każdej metody, które chcesz zrobić walidację że zamierza zostać zwrócone użytkownikowi.

    Albo

    trzeba zainicjować słownika sprawdzania przez serwis (jeśli zdecydujesz się IValidationDictionary jako prywatny pola), w każdej akcji kontrolera, który chcesz potwierdzić przed.