2009-09-23 16 views
12

Dla tych, którzy tworzą ViewModels (do użycia przez wpisane widoki) w ASP.NET MVC, wolisz pobierać dane z usługi/repozytorium z poziomu ViewModel lub klas kontrolera?Pobieranie danych w klasie ASP.NET MVC ViewModel?

Na przykład, zaczęliśmy poprzez ViewModels istocie bycia DTOs i pozwalając nasze Sterowniki do pobierania danych (rażąco uproszczony przykład zakłada, że ​​użytkownik może zmienić tylko nazwisko pracownika):

public class EmployeeViewModel 
{ 
    public String Name; //posted back 
    public int Num; //posted back 
    public IEnumerable<Dependent> Dependents; //static 
    public IEnumerable<Spouse> Spouses; //static 
} 

public class EmployeeController() 
{ 
    ... 
    public ActionResult Employee(int empNum) 
    { 
     Models.EmployeeViewModel model = new Models.EmployeeViewModel(); 
     model.Name = _empSvc.FetchEmployee(empNum).Name; 
     model.Num = empNum; 
     model.Dependents = _peopleSvc.FetchDependentsForView(empNum); 
     model.Spouses = _peopleSvc.FetchDependentsForView(empNum); 
     return View(model); 
    } 

    [AcceptVerbs(HttpVerbs.Post)] 
    public ActionResult Employee(Models.EmployeeViewModel model) 
    { 
     if (!_empSvc.ValidateAndSaveName(model.Num, model.Name)) 
     { 
      model.Dependents = _peopleSvc.FetchDependentsForView(model.Num); 
      model.Spouses = _peopleSvc.FetchDependentsForView(model.Num); 
      return View(model); 
     } 
     this.RedirectToAction(c => c.Index()); 
    } 
} 

To wszystko wydawało się w porządku, dopóki zaczęliśmy tworzyć duże widoki (40 + pola) z wieloma opcjami i takimi. Ponieważ ekrany miałyby działanie GET i POST (z POST zwracającym widok, jeśli wystąpił błąd sprawdzania poprawności), kopiowalibyśmy kod i powodowałby, że ViewModels byłby większy niż prawdopodobnie powinien.

Myślę, że alternatywą byłoby pobranie danych za pośrednictwem usługi w ramach ViewModel. Obawiam się, że wtedy będziemy mieli pewne dane wypełnione przez ViewModel, a niektóre z Kontrolera (np. W powyższym przykładzie, Nazwa będzie wypełniana przez Kontrolera, ponieważ jest to wartość zaksięgowana, natomiast Dependenci i małżonkowie będą zapełniani przez niektóre typ funkcji GetStaticData() w ViewModel).

Myśli?

+11

IEnumerable ? Co jest z wzorcem poligamistycznym? :-D –

Odpowiedz

7

Napotkałem ten sam problem. Zacząłem tworzyć klasy dla każdej akcji, gdy kod stał się zbyt duży dla metod działania. Tak, będziesz mieć trochę odzyskiwania danych w klasach, a niektóre w metodach kontrolera. Alternatywą jest pobieranie wszystkich danych w klasach, ale połowa klas, których naprawdę nie będziesz potrzebować, zostaną utworzone dla spójności konsystencji lub mają wszystkie dane w metodach kontrolera, ale znowu niektóre z tych metod będą być zbyt skomplikowanym i wymagającym abstrahowania na zajęcia ... więc wybierz swoją truciznę. Wolałbym mieć trochę niespójności i mieć odpowiednie rozwiązanie dla tej pracy.

Co do umieszczania zachowania w ViewModelu, to nie, punkt ViewModel ma być cienką klasą do ustawiania i wyodrębniania wartości z widoku.

Zdarzały się przypadki, w których wprowadziłem metody konwersji w ViewModel. Na przykład muszę przekonwertować ViewModel na odpowiedni obiekt lub muszę załadować ViewModel z danymi z Entity.

Aby odpowiedzieć na twoje pytanie, wolę pobierać dane z za pomocą metod kontrolera/działania.

Zazwyczaj w DropDowns tworzę rozwijaną usługę. DropDowns to zwykle te same dane, które obejmują widoki. Dzięki rozwijanym menu usługi mogę używać ich w innych widokach i/lub buforować je.

W zależności od układu, pola 40 plus mogą spowodować zaśmiecenie widoku. W zależności od rodzaju danych spróbowałbym objąć wiele pól w wielu widokach za pomocą interfejsu kart lub kreatora.

+0

Dzięki za opinie. Ma to zastosowanie do bezpiecznej aplikacji klienckiej, a nie do aplikacji skierowanej do publiczności, więc 40 pól zwykle nie jest śmieszne (chociaż większość naszych ponad 100 zaplanowanych wyświetleń jest bliższa 10-15 pól). Kiedy mówisz, że tworzysz rozwijaną usługę, masz na myśli to samo co powyżej, czy usługa wypełnia obiekt SelectList i przekazuje go do ViewModel? –

+0

Wygląda na to, że Twoje dane są zależne od bieżącego pracownika. Wygląda dobrze, o ile to działa dla ciebie. Kiedy mówiłem o dropdownservice, myślałem o opuszczeniu stref czasowych lub rozwijaniu krajów. Tego typu dane nie zmieniają się często i można je łatwo buforować. –

+0

W rzeczywistości mamy również tego rodzaju dane, które przechowujemy w pamięci podręcznej, ale wywołujemy je w ten sam sposób (pamięć podręczna jest zarządzana w warstwie usługi). W jaki sposób obsługiwałbyś buforowane informacje w inny sposób, wywołując je w swoim ViewModelu? –

3

To coś więcej ;-) Możesz pobrać w segregatorze lub filtrze akcji. W przypadku drugiej opcji sprawdź blog Jimmy'ego Bogarda gdzieś w okolicy here. Osobiście robię to w segregatorach modelowych. Używam ViewModel w ten sposób: My custom ASP.NET MVC entity binding: is it a good solution?. Jest przetwarzane przez mój zwyczaj modelu spoiwa:

public object BindModel(ControllerContext c, BindingContext b) 
{ 
    var id = b.ValueProvider[b.ModelName]; // don't remember exact syntax 
    var repository = ServiceLocator.GetInstance(GetRepositoryType(b.ModelType)); 
    var obj = repository.Get(id); 
    if (obj == null) 
    b.ModelState.AddModelError(b.ModelName, "Not found in database"); 
    return obj; 
} 

public ActionResult Action(EntityViewModel<Order> order) 
{ 
    if (!ModelState.IsValid) 
     ...; 
} 

Można również zobaczyć przykład modelu spoiwa robi repozytorium dostęp w S#arp Architecture.

Jeśli chodzi o dane statyczne w modelach widoku, nadal analizuję podejścia.Na przykład można mieć panel modele pamiętać podmioty zamiast list i

public class MyViewModel { MyViewModel publicznych (kolejność Order, IEmployeesSvc _svc) { }

public IList<Employee> GetEmployeesList() 
    { 
     return _svc.GetEmployeesFor(order.Number); 
    } 

}

Sam decydujesz, w jaki sposób wstrzykujesz _svc do ViewModel, ale jest to zasadniczo to samo, co robisz dla kontrolera. Po prostu strzeż się, że ViewModel jest również tworzony przez MVC za pomocą konstruktora bez parametrów, więc możesz użyć ServiceLocator lub rozszerzyć MVC do tworzenia ViewModel - na przykład w niestandardowym segregatorze modelu. Lub możesz użyć podejścia Jimmy'ego Bogarda z AutoMapper, które obsługuje również kontenery IoC.

Podejściem powszechnym jest to, że za każdym razem, gdy widzę powtarzający się kod, staram się go wyeliminować. 100 działań kontrolera wykonujących mapowanie w trybie domain-viewmodel oraz wyszukiwanie repozytorium jest złe. Sporządzenie pojedynczego modelu w ogólny sposób jest dobre.

1

Oto inne rozwiązanie: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/06/29/how-we-do-mvc-view-models.aspx

Główne punkty tam:

  1. mapowanie odbywa mediatora - w tym przypadku jest to AutoMapper ale może to być własne klasy (choć bardziej do kodu). Dzięki temu zarówno Domain, jak i ViewModel koncentrują się na logice domeny/prezentacji. Mediator (mapper) będzie zawierał logikę (głównie automatyczną) do mapowania, w tym wstrzykiwane usługi.
  2. Mapowanie jest stosowane automatycznie, wszystko co musisz zrobić, to powiedzieć filtrowi akcji typy źródłowe/docelowe - bardzo czyste.
  3. (Wydaje się być dla ciebie ważna) AutoMapper obsługuje zagnieżdżone odwzorowania/typy, dzięki czemu możesz mieć swój ViewModel połączony z kilkoma niezależnymi modelami widoku, dzięki czemu twój "screen DTO" nie jest brudny.

jak w tym modelu

public class WholeViewModel 
{ 
    public Part1ViewModel ModelPart1 { get; set; } 
    public Part2ViewModel ModelPart2 { get; set; } 
} 

ponownie obsłudze mapowania dla poszczególnych częściach View, a nie pisać żadnych nowych linii kodu, ponieważ nie jesteś już dla mapowania modele z częściowym widokiem.

Jeśli nie chcesz AutoMapper, trzeba mieć interfejsy IViewModelMapper, a następnie Twój kontener IoC pomoże filtr działania, aby znaleźć odpowiednie

container.Resolve(typeof(IViewModelMapper<>).MakeGenericType(mysourcetype, mydesttype)) 

i będzie również dostarczać wszelkich wymaganych usług zewnętrznych do tego elementu odwzorowującego (jest to możliwe również w AutoMapper). Ale oczywiście AutoMapper może wykonywać rekurencje i tak dalej, po co pisać dodatkową AutoMapper ;-)

3

Nie będę pobierać danych z bazy danych w ViewModel. ViewModel istnieje w celu promowania separacji problemów (między twoim widokiem a twoim modelem). Zaplątanie się w logikę perswazji w tym rodzaju pokonuje cel.

Na szczęście środowisko ASP.NET MVC zapewnia nam więcej punktów integracji, w szczególności modelBinder.

Mam wdrożenie generycznego ModelBinder ciągnięcie informacji z warstwy usługowej w: -

http://www.iaingalloway.com/going-further-a-generic-servicelayer-modelbinder

Nie używać ViewModel, ale to łatwo naprawić. W żadnym wypadku nie jest to jedyna realizacja. Jeśli chodzi o projekt w realnym świecie, prawdopodobnie lepiej będzie, jeśli skorzystasz z mniej ogólnego, bardziej spersonalizowanego rozwiązania.

Jeśli jesteś pilny, Twoje metody GET nie muszą nawet wiedzieć, że istnieje warstwa usługi.

Rozwiązanie prawdopodobnie wyglądał: -

kontrolera metody działania: -

public ActionResult Details(MyTypeIndexViewModel model) 
{ 
    if(ModelState.IsValid) 
    { 
    return View(model); 
    } 
    else 
    { 
    // Handle the case where the ModelState is invalid 
    // usually because they've requested MyType/Details/x 
    // and there's no matching MyType in the repository 
    // e.g. return RedirectToAction("Index") 
    } 
} 

ModelBinder: -

public object BindModel 
(
    ControllerContext controllerContext, 
    BindingContext bindingContext 
) 
{ 
    // Get the Primary Key from the requestValueProvider. 
    // e.g. bindingContext.ValueProvider["id"] 
    int id = ...; 

    // Get an instance of your service layer via your 
    // favourite dependancy injection framework. 
    // Or grab the controller's copy e.g. 
    // (controllerContext.Controller as MyController).Service 
    IMyTypeService service = ...; 

    MyType myType = service.GetMyTypeById(id) 

    if (myType == null) 
    { 
    // handle the case where the PK has no matching MyType in the repository 
    // e.g. bindingContext.ModelState.AddModelError(...) 
    } 


    MyTypeIndexViewModel model = new MyTypeIndexViewModel(myType); 

    // If you've got more repository calls to make 
    // (e.g. populating extra fields on the model) 
    // you can do that here. 

    return model; 
} 

ViewModel: -

public class MyTypeIndexViewModel 
{ 
    public MyTypeIndexViewModel(MyType source) 
    { 
    // Bind all the properties of the ViewModel in here, or better 
    // inherit from e.g. MyTypeViewModel, bind all the properties 
    // shared between views in there and chain up base(source) 
    } 
} 

budować swoją warstwę usługową i zarejestruj swój moduł ModelBinder w normalny sposób.

+1

Twoje pomysły są dla mnie intrygujące i chcę zaprenumerować Twój biuletyn. – anewcomer

0

Rozważ przekazanie swoich usług do niestandardowego ViewModel na swoim konstruktorze (ala Dependency Injection). To usuwa kod populacji modelu ze sterownika i pozwala mu skupić się na kontrolowaniu logicznego przepływu aplikacji. Niestandardowe ViewModels są idealnym miejscem do streszczenia przygotowania takich rzeczy jak SelectLists, od których zależeć będą listy rozwijane.

Dużo kodu w kontrolerze dla rzeczy takich jak pobieranie danych nie jest uważane za najlepszą praktykę. Głównym zadaniem administratora jest "kontrolowanie" przepływu aplikacji.

0

Przesyłanie tego jednego późno ... Bounty już prawie się skończyło. Ale ...

Innym odwzorowujący patrzeć na to Automapper: http://www.codeplex.com/AutoMapper

i przegląd, w jaki sposób z niego korzystać: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/01/22/automapper-the-object-object-mapper.aspx

Bardzo podoba mi się to składnia.

// place this somewhere in your globals, or base controller constructor 
Mapper.CreateMap<Employee, EmployeeViewModel>(); 

Teraz w twoim kontrolerze użyłbym wielu modeli podglądu. Wymusza to DRY, umożliwiając ponowne użycie tych modeli widoków w innym miejscu aplikacji. Nie chciałbym ich wszystkich powiązać z 1 rzutnikiem. Chciałbym refaktoryzacji na coś takiego:

public class EmployeeController() 
{ 
    private IEmployeeService _empSvc; 
    private ISpouseService _peopleSvc; 

    public EmployeeController(
     IEmployeeService empSvc, ISpouseService peopleSvc) 
    { 
    // D.I. hard at work! Auto-wiring up our services. :) 
    _empSvc = empSvc; 
    _peopleSvc = peopleSvc; 

    // setup all ViewModels here that the controller would use 
    Mapper.CreateMap<Employee, EmployeeViewModel>(); 
    Mapper.CreateMap<Spouse, SpouseViewModel>(); 
    } 

    public ActionResult Employee(int empNum) 
    { 
    // really should have some validation here that reaches into the domain 
    // 

    var employeeViewModel = 
     Mapper.Map<Employee, EmployeeViewModel>(
      _empSvc.FetchEmployee(empNum) 
     ); 

    var spouseViewModel = 
     Mapper.Map<Spouses, SpousesViewModel>(
      _peopleSvc.FetchSpouseByEmployeeID(empNum) 
     ); 

    employeeViewModel.SpouseViewModel = spouseViewModel; 

    return View(employeeViewModel);  
    } 

    [AcceptVerbs(HttpVerbs.Post)] 
    public ActionResult Employee(int id, FormCollection values)  
    { 
    try 
    { 
     // always post to an ID, which is the employeeID 
     var employee = _empSvc.FetchEmployee(id); 

     // and bind using the built-in UpdateModel helpers. 
     // this will throw an exception if someone is posting something 
     // they shouldn't be posting. :) 
     UpdateModel(employee); 

     // save employee here 

     this.RedirectToAction(c => c.Index()); 
    } 
    catch 
    { 
     // check your domain model for any errors. 
     // check for any other type of exception. 
     // fail back to the employee screen 
     RedirectToAction(c => c.Employee(id)); 
    } 
    } 
} 

Generalnie staram się trzymać z dala od zapisywania wielu jednostek w akcji kontrolera. Zamiast tego chciałbym refaktoryzować obiekt domeny pracownika, aby miał metody AddSpouse() i SaveSpouse(), które przyniosłyby obiekt Oblubieńca. Ta koncepcja nazywa się AggregateRoots, kontrolując wszystkie zależności od katalogu głównego - czyli obiektu Employee(). Ale to tylko ja.

Powiązane problemy