Dzisiaj trochę o samych widokach w mvc i ich organizacji.
Zgodnie z przyjętą konwencją widoki powinny znajdować się w folderze Views, tam będzie szukać ich domyślnie włączony silnik renderujący. Nazwa widoku powinna odpowiadać nazwie metody w kontrolerze (konwencja i dobre praktyki). Oczywiście w metodzie Foo można wykorzystać widok o nazwie Goo, ale nie jest to najlepszą praktyką i powodować będzie, że trudniej nam/wam/innym będzie odnaleźć się w projekcie.
Zacznijmy od stworzenia nowego projektu, ale teraz zamiast Web Application wybieramy Empty, to spowoduje że powstanie czysty szkielet aplikacji MVC. Bez całej masy dodatkowych zależności, które nie są nam teraz potrzebne. Może nie wszystkie i nie teraz.
Tak stworzony projekt należy lekko upgradować, ale nie skupiajmy się nad tym teraz, źródła aplikacji dostępne są na repo github.com
Konwencje
Teraz aby w ogóle zacząć należy dodać kontroler:
W konstruktorze (@5) tworzymy model, który będzie wykorzystywany w każdej z metod. Poniżej jest kilka akcji kontrolera a w nich Index (@18), który jest domyślną akcję (kiedy indziej o tym skąd mvc wie jaką akcje ma wywołać). Akcja ta przekazuje instancje person do widoku. Nie podaje tutaj nazwy widoku, który ma być wywołany, pozostawię to mvc do rozstrzygnięcia. Wg konwencji powinien zostać wywołany widok z folder Views\Home\Index.cshtml
Następna jest akcja Secret (@23). Tutaj w jawny sposób podałem nazwę widoku z którego chce skorzystać, w związku z tym w ramach konwencji wywołane zostanie widok z Views\Home plus nazwa podana jako argument w tym przypadku ThisIsTotalyDifferentView.cshtml
Jest także akcja IDontNeedModel (@28) która pokazuje, że nie trzeba danych przesyłać do widoku aby ten został wygenerowany. Istnieją inne sposoby niż przekazywanie danych przez model do widoku, ale nie chce o nich mówić, uważam je za bardzo złą praktykę.
Akcje WithoutLayout (@28) oraz ReallyWithoutLayout (@33) od strony kontrolera nie różnią się od siebie. To co kryją w sobie wyjdzie dopiero niżej.
Akcja NoMatchingViewFound powstała po to abyście mogli zobaczyć jak wygląda błąd i co się wyświetli, gdy MVC nie poradzi sobie z odnalezieniem widoku dla akcji.
Tutaj różnica w zachowaniu ze starym MVC – w konfiguracji, którą aktualnie uruchamiamy nie zostaną wyświetlone błędy o szukaniu ścieżek, stos wywołań, czy inne rzeczy. Przeglądarka stwierdzi nasz/wasz serwer nie działa, bo nie odpowiada – tyle.
Namespace w kodzie:
Aby ułatwić sobie życie i poprawić trochę czytelność widoków poprzez usunięcie pełnego namespace’a przy deklaracji modelu, należy zebrać w jednym miejscu wszystkie namespacy, które będą brane pod uwagę podczas kompilacji widoków. Aby do zrobić należy dodać plik _ViewImports.cshtml do folderu Views. W nim należy zamieścić wszystkie namespace, które będą nam potrzebne, a których nie będziemy chcieli potem powielać w kodzie samego widoku. W moim projekcie na razie plik ten wygląda tak:
Od tej pory nie będę musiał takiego wpisu umieszczać swoich widokach, wystarczy że będzie tam tylko deklaracja modelu. Namespace może zostać pominięty.
We wcześniejszych wersjach, gdy chcieliśmy osiągnąć podobny efekt należało skorzystać z web.configu, który powinniśmy umieścić także w folderze Views. Plik ten mało czytelny (XML!) oraz zawierał także dużo innych wpisów. Moim zdaniem jest to krok w dobrym kierunku, boje się tylko że plik ten może urosnąć do sporych rozmiarów w przypadku większych projektów. Wpis ten powstawał kilka dni i później doszedłem do myśli: że jeśli projekt jest dobrze ułożony, to namespace który się tam znajdzie powinien być czymś takim Project.Layers.ViewModels.
Ciekawe spostrzeżenie: początkowo klasa PersonModel umieszczona była w namespace MvcCoreRazor.Controllers ale przeniosłem ją do MvcCoreRazor.Models, tak aby przykład był bardziej czytelny. Wykorzystałem do tego R#. Po takim zabiegu, plik _ViewImports nie został zmieniony, co za tym idzie wszystkie widoki korzystające z tego modelu straciły do niego referencje. Musiałem więc ręcznie poprawić wpis, aby kompilacja mogła się zakończyć sukcesem. Ma to sens, ale warto być tego świadomym, gdy po takich operacjach widoki zaczną świecić się na czerwono.
Wspólny mianownik
Każda strona html ma kilka rzeczy które się powtarzają, cały head, nagłówki, menu, stopki, etc. Aby nie duplikować tego wszystkiego można wykorzystać layout. Czyli wspólny układ stron, gdzie tylko cześć (np. body) będzie się zmieniać w zależności od strony na której się aktualnie znajduje użytkownik.
Na początek strona która nie posiada layout i zawiera wszystkie element html:
Na początu widać definicję modelu (@1), potem jawnie określam, że nie chce layoutu (@4), potem deklaracje, nagłówki, meta i wreszcie właściwe wyświetlenie danych w sekcji body. Poniżej zobaczycie definicję layoutu, który zawierać będzie wszystko to czego nie chce mi się powtarzać w każdym z widoków, a jednocześnie chce to mieć na każdym z tych widoków (zjeść ciastko i mieć ciastko):
Wygląda to jak html z jedną opcją która nie pasuje: @RenderBody() w tym miejscu wyświetlać się będą inne widoku, który korzystają z tego layoutu.
A jak wyglądają inne widoki? A może być że tak:
I teraz dzięki _ViewImport w linijce @1 nie musimy podawać pełnego namespace do klasy PersonModel, wystarczy że podamy tylko nazwę klasy. Następnie (@3) deklarujemy nazwę layoutu z którego chcemy korzystać. A potem właściwa część strony. Jakieś dane które zostały przesłane w modelu i wyświetlone na widoku. Zwróćcie uwagę że Layout nie posiada definicji modelu (i co teraz?).
Czy za każdym razem trzeba podawać layout? Otóż nie, jest jeszcze jeden plik który może coś ułatwić/zautomatyzować _ViewStart.cshtml
Umieszczony w folderze Views będzie uruchomiony dla każdego widoku, co spowoduje że tak zdefiniowany widok:
Korzystać będzie z Layout zdefiniowanego w ViewStart.cshtml. Warto o tym pamiętać, żeby nie być zdziwionym czemu coś się renderuje tak a nie inaczej. Przypomnę jeszcze raz, że aby to zmienić należy jawnie podać inny layout lub ustawić go na null aby móc wyrwać się z pajęczyny mvc.
No i tyle.