2010-08-25 11 views
6

w mojej aplikacji ASP.NET MVC, mam interfejs, który działa jako szablonu dla kilku różnych modeli Widok:Przechodząc interfejs do metody działania ASP.NET MVC Controller

public interface IMyViewModel 
{ 
    Client Client1 { get; set; } 
    Client Client2 { get; set; } 

    Validator Validate(); 
} 

Więc, moim zdaniem modele są zdefiniowane następująco:

public interface MyViewModel1 : IMyViewModel 
{ 
    Client Client1 { get; set; } 
    Client Client2 { get; set; } 

    // Properties specific to MyViewModel1 here 

    public Validator Validate() 
    { 
     // Do ViewModel-specific validation here 
    } 
} 

public interface MyViewModel2 : IMyViewModel 
{ 
    Client Client1 { get; set; } 
    Client Client2 { get; set; } 

    // Properties specific to MyViewModel2 here 

    public Validator Validate() 
    { 
     // Do ViewModel-specific validation here 
    } 
} 

Potem mają obecnie oddzielne działania kontrolera do zrobienia walidacji dla każdego innego rodzaju, za pomocą wiązania model:

[HttpPost] 
public ActionResult MyViewModel1Validator(MyViewModel1 model) 
{ 
    var validator = model.Validate(); 

    var output = from Error e in validator.Errors 
       select new { Field = e.FieldName, Message = e.Message }; 

    return Json(output); 
} 

[HttpPost] 
public ActionResult MyViewModel2Validator(MyViewModel2 model) 
{ 
    var validator = model.Validate(); 

    var output = from Error e in validator.Errors 
       select new { Field = e.FieldName, Message = e.Message }; 

    return Json(output); 
} 

To działa dobrze, ale gdybym miał 30 różnych typów modeli widoku, musiałoby być 30 osobnych akcji kontrolera, wszystkie z identyczny kod oprócz podpisu metody, która wydaje się zła praktyka.

Moje pytanie brzmi: jak mogę skonsolidować te działania sprawdzania poprawności, aby można było przekazać dowolny model widoku i nazwać go metodą sprawdzania poprawności (Validate), nie dbając o to, jaki to jest?

Początkowo próbowałem przy użyciu samego interfejsu jako parametr działania:

public ActionResult MyViewModelValidator(IMyViewModel model)... 

Ale to nie działa: Dostaję Cannot create an instance of an interface wyjątek. Myślałem, że przykład tego modelu zostanie przekazany do działania kontrolera, ale najwyraźniej tak nie jest.

Jestem pewien, że brakuje mi czegoś prostego. A może właśnie podjąłem się tego wszystkiego źle. Czy ktoś może mi pomóc?

Odpowiedz

10

Powodem, dla którego nie można korzystać z interfejsu, jest serializacja. Gdy nadejdzie żądanie zawiera tylko pary łańcuch klucz/wartość, które stanowią przedmiot:

"Client1.Name" = "John" 
"Client2.Name" = "Susan" 

gdy metoda akcja zostanie wywołana runtime MVC próbuje utworzyć wartości do wypełnienia parametrów metody jest (za pomocą procesu zwanego modelu wiążącego). Używa tego typu parametru, aby wywnioskować, jak go utworzyć. Jak zauważyłeś, parametr nie może być interfejsem ani żadnym innym typem abstrakcyjnym, ponieważ środowisko wykonawcze nie może utworzyć jego instancji. Potrzebuje konkretnego typu.

Jeśli chcesz usunąć powtarzanie kodu można napisać pomocnika:

[HttpPost]   
public ActionResult MyViewModel1Validator(MyViewModel1 model)   
{   
    return ValidateHelper(model);   
}   

[HttpPost]   
public ActionResult MyViewModel2Validator(MyViewModel2 model)   
{   
    return ValidateHelper(model);   
} 

private ActionResult ValidateHelper(IMyViewModel model) { 
    var validator = model.Validate();   

    var output = from Error e in validator.Errors   
       select new { Field = e.FieldName, Message = e.Message };   

    return Json(output); 
} 

Jednak trzeba jeszcze inną metodę działania dla każdego typu modelu. Być może istnieją inne sposoby na zreorganizowanie kodu. Wygląda na to, że jedyną różnicą w twoich klasach modeli jest zachowanie związane z poprawnością. Można znaleźć inny sposób kodowania typu sprawdzania poprawności w klasie modelu.

+0

Poszedłem na to podejście w końcu wyłącznie ze względu na ograniczenia czasowe, ale wyjaśniłeś również, dlaczego obiekt nie jest już instancją, kiedy jest przekazywany do kontrolera, co jest przydatne do poznania. –

1

Można rozważyć użycie klasy bazowej zamiast interfejsu.

+0

to powoduje podobny komunikat o błędzie. Nie można również utworzyć instancji typu abstrakcyjnego. –

+1

Można użyć nie abstrakcyjnej klasy bazowej. –

1

Myślę, że chciałbym utworzyć abstrakcyjną klasę bazową, która zaimplementowałaby IMyViewModel. Chciałbym uczynić Validate metodą abstrakcyjną i wymagać nadpisania w moim konkretnym widoku modeli odziedziczonych po MyAbstractViewModel. Wewnątrz kontrolera możesz pracować z interfejsem IMyViewModel, ale bindowanie i serializacja naprawdę wymaga konkretnej klasy do związania. Mój 0,02.

+1

Powinienem dodać jeszcze jeden komentarz - właściwie nie przekazałbym klasy abstrakcyjnej do kontrolera, ale raczej konkretną klasę. Możesz mieć usługę sprawdzania poprawności lub inną metodę, która może obsłużyć klasę abstrakcyjną lub interfejs, ale dla jasności, powinieneś naprawdę oczekiwać tego, co dostajesz do kontrolera. – Hal

4

Można to sprawdzić: http://msdn.microsoft.com/en-us/magazine/hh781022.aspx.

Jest to spowodowane tym, że DefaultModelBinder nie ma możliwości sprawdzenia, jaki konkretny typ IMyViewModel powinien utworzyć. W tym celu należy utworzyć niestandardowy segregator modelu i wskazać sposób tworzenia i wiązania instancji interfejsu.

+0

+1 IMHO to jest droga, która przebija wszystkie inne odpowiedzi (niektóre z nich oczywiście nie były testowane w rzeczywistym świecie). –

Powiązane problemy