ASP.NET MVC #2 – Kontrolery
Witaj w drugiej części kursu poświęconego frameworkowi ASP.NET MVC. Tym razem na tapetę postanowiłem wziąć temat kontrolerów. Dodatkowo wspomnę o podstawach routingu w aplikacji ponieważ tematy te są niejako powiązane. Zaczynami :)
Co to ten kontroler
W poprzedniej części pokazałem jak utworzyć aplikację ASP.NET MVC z poziomu Visual Studio. Zakładam więc, że masz ją w takim stanie w jakim ją tam pozostawiliśmy.
Przejdźmy więc do rzeczy.
Kontroler jak sama nazwa wskazuje służy do kontrolowania czegoś. Jest zawarty w nazwie wzorca, na którym opiera się cała aplikacja, MVC – Model View Controller (Model Widok Kontroler), więc pewnie jest ważny.
W dużym skrócie można powiedzieć, że kontroler jest pośrednikiem pomiędzy danymi i widokami. Bierze on jakieś dane, czy to z bazy, czy to z pliku, czy pozyskane w jeszcze jakiś sposób i przekazuje je do widoku. W przypadku formularzy przekazuje też informacje w drugą stronę – z widoku do modelu (o tym, czym jest model powiem w kolejnych częściach, w tym momencie dla uproszczenia załóż, że to po prostu wpisy w bazie danych).
Stan zastany
W naszym szablonie aplikacji ASP.NET MVC Visual wrzucił od razu trzy kontrolery
- Account Controller
- Home Controller
- Manage Controller
Znajdują się one w katalogu Controllers. I to właśnie w tym katalogu domyślnie znajdują się kontrolery naszej aplikacji. Jest możliwość zmiany ich lokalizacji i budując aplikację np. w oparciu o DDD taka zmiana jest potrzebna. Jednak z uwagi na to, że dopiero zaczynasz swoją przygodę z tym frameworkiem nie będziemy się w ten temat zagłębiać. Odpowiedni czas na to przyjdzie wraz z doświadczeniem (lub ciekawością ;) ).
Pierwszy i trzeci z wymienionych kontrolerów, jeśli je otworzysz, wyglądają na rozbudowane. Nie będę ich dokładnie omawiał. Powiem tylko, że służą do zarządzania kontem użytkownika. Czyli m.in. biorą udział w rejestrowaniu konta, logowaniu czy zmianie ustawień. W tym tutorialu założymy, że po prostu są i działają. Sporo z metod w nich zawartych jest nadmiarowa w naszym przypadku ale nie przeszkadzają one.
Klasowa kontrola
No dobra ale jest jeszcze kontroler Home. I nim się teraz zajmę, a dokładniej to na jego podstawie omówię podstawową budowę kontrolera. Kod tego konkretnego nie jest skomplikowany i spokojnie mogę go tutaj wkleić:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace ToDo.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult About() { ViewBag.Message = "Your application description page."; return View(); } public ActionResult Contact() { ViewBag.Message = "Your contact page."; return View(); } } }
Kontroler, jak widzisz na załączonym obrazku, jest po prostu klasą. Dokładnie tak, jest to zwykła klasa dziedzicząca bo bazowej klasie dla kontrolerów we frameworku ASP.NET MVC czyli klasie Controller. Gdybyś chciał dodać jakieś własne elementy dla wszystkich kontrolerów to możesz oczywiście utworzyć swoją klasę nazwaną chociażby BaseController i z niej dziedziczyć. Jednak zawsze na końcu będzie to dziedziczenie z klasy Controller frameworka.
Kontroler! Akcja!
Kolejna rzecz, która się rzuca w oczy to metody zawarte w klasie naszego kontrolera Home. Są trzy. Wszystkie są publiczne i zwracają obiekt klasy ActionResult. Te metody to akcje kontrolera. Obiekt, który zwracają do coś co dostanie użytkownik. Klasa ActionResult jest w pewnym sensie klasą bazową zwracanych danych. Możemy zawęzić rodzaj zwracanej wartości i jasno powiedzieć, że zwracać będziemy ViewResult (widok. O nich następnym razem), FileResult (strumień pliku) czy JsonResult (dane w formacie JSON). Jednak w naszym przypadku spokojnie można wszędzie jako typ zwracany podawać ActonResult.
W przypadku kontrolera Home i w sumie większości kontrolerów, które napiszesz w najbliższym czasie wszystkie akcje zwracają zwracają widok. W powyższym kodzie służy do tego metoda View().
Chodź, coś Ci pokażę
Zapytasz teraz pewnie skąd framework wie jaki konkretnie widok ma zwrócić skoro nigdzie nie podajemy jego nazwy? Tutaj do gry wchodzi coś takiego jak konwencja. Twórcy frameworka przyjęli, że domyślnie wybór widoków będzie powiązany z nazwami kontrolerów i akcji. Przyjrzyjmy się chociażby akcji Index w powyższym kontrolerze Home. Jak właśnie powiedziałem akcja Index znajduje się w kontrolerze o nazwie Home. Spójrz teraz w projekcie na Solution Explorer. Konkretnie na katalog Views. Widzisz tam podkatalog Home? Rozwiń go. Czy nazwy tych plików coś Ci przypominają? ;) Odpowiadają one nazwom akcji w naszym kontrolerze Home. Tak więc nazwa kontrolera odpowiada nazwie folderu, zaś nazwa akcji odpowiada nazwie pliku w tym folderze.
Framework ASP.NET MVC, kiedy nie podasz mu nazwy pliku z widokiem będzie przeszukiwał lokalizacje w katalogu Views szukając w pierwszej kolejności wg schematu Nazwa_Kontrolera/Nazwa_Akcji.cshtml.
Dla ambitnych: jeśli pisząc testy jednostkowe będziesz chciał przetestować również kontroler to pamiętaj, że w pokazanym wyżej przypadku, a więc kiedy nie wpisujesz jawnie nazwy widoku, również w zwracanym przez akcję obiekcie ActionResult nazwa ta nie będzie podana. Spowoduje to, że dużo trudniej będzie sprawdzić czy akcja zwraca poprawny widok.
Sprawdźmy teraz czy to faktycznie działa. Najpierw zobacz co znajduje się w pliku Contact.cshtml. Jest w nim kod HTML z adresem i emailem:
@{ ViewBag.Title = "Contact"; } <h2>@ViewBag.Title.</h2> <h3>@ViewBag.Message</h3> <address> One Microsoft Way<br /> Redmond, WA 98052-6399<br /> <abbr title="Phone">P:</abbr> 425.555.0100 </address> <address> <strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br /> <strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a> </address>
Uruchom teraz aplikację. Przejdź do adresu /Home/Contact
Powinieneś w tym momencie zobaczyć w przeglądarce dokładnie te same dane, które widziałeś w pliku Contact.cshtml.
Jeśli chcesz się upewnić, że aplikacja faktycznie wywołuje akcję Contact w kontrolerze Home możesz tam wstawić breakpoint w trybie debugowania.
Wkład własny
Pora w końcu dodać w aplikacji coś od siebie. Będzie to nowy kontroler, który w kolejnych częściach będziemy rozbudowywali. Będzie on służył do obsługi TODO listy.
TODO lista opiera się na zadaniach więc tak też nazwijmy nasz kontroler – Task.
Aby dodać kontroler wyłącz aplikację. Następnie kliknij prawym przyciskiem myszy na folderze Controllers. Wybierz z menu Add->Controller….
Pojawi Ci się okno, w którym wybierasz typ kontrolera do utworzenia. Nas interesuje pierwsza pozycja, a więc pusty kontroler MVC5. Inne opcje to kontrolery z dodanymi od razu akcjami czy nawet widokami do tworzenia czy usuwania obiektów oraz kontrolery do obsługi API, gdzie nie jest zwracany żaden widok.
Dlaczego nie wybieramy kontrolera i gotowych akcji? Ponieważ po pierwsze nie będziesz wtedy wiedział jak dodaje się te rzeczy samemu. Po drugie gotowe szablony zwykle nie są warte uwagi w większości zastosować i tak czy inaczej musiałyby być mocno modyfikowane. Po trzecie żeby mogły być utworzone akcje do dodawania czy usuwania obiektów musielibyśmy już mieć stworzone klasy dla tych obiektów, a będziemy to robić w przyszłych częściach kursu.
Po kliknięciu Add pojawi się kolejne okienko gdzie wpisujemy nazwę kontrolera. W naszym przypadku będzie to TaskController.
Teraz po kliknięciu Add zostanie dodany w projekcie, w folderze Controllers, nowy kontroler TaskController. Powinien Ci się on pojawić od razu otwarty w oknie edytora. Jeśli tak nie jest to otwórz plik TaskController.cs. Powinieneś zobaczyć poniższy kod:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace ToDo.Controllers { public class TaskController : Controller { // GET: Task public ActionResult Index() { return View(); } } }
Ups! Już to naprawiam
Widzisz, że tak jak w przypadku kontrolera Home tutaj również jest dziedziczenie z klasy Controller. Oprócz tego IDE wygenerowało nam pustą akcję Index, która zwraca widok. Ale właśnie, jaki widok? Przecież żadnego widoku nie dodawaliśmy. Co się w takim razie stanie jeśli teraz uruchomisz aplikację i przejdziesz do strony /Task/Index?
Zobaczysz poniższy komunikat:
Mówi on o tym, że ASP.NET MVC nie znalazł odpowiedniego widoku.
Przy okazji możemy zobaczyć w jakich folderach on tego widoku szukał. Kolejność na tej liście podpowiada w jakiej kolejności następowało szukanie. Wzięty byłby pierwszy znaleziony plik, pozostałe będą zignorowane.
Ok, nie warto tego tak zostawiać. Co prawda o widokach będziemy się uczyć w następnej części jednak teraz przynajmniej pozbądźmy się powyższego błędu. W końcu lepiej nie zostawiać aplikacji niedziałającej.
W tym miejscu wtrącę, że Resharper, który jest świetnym dodatkiem do Visual Studio potrafi rozpoznać brak widoku. W takim wypadku zaznacza mi wywołanie metody View() na czerwono podpowiadając, że brakuje widoku dla tej akcji:
Żeby nie musieć szukać odpowiedniego folderu w katalogu Views najlepiej jest kliknąć prawym przyciskiem myszy na wywołanie metody View() i z menu wybrać Add View…
W nowym oknie zostawiamy domyślne ustawienia. Więcej o dostępnych opcjach powiem przy okazji omawiania widoków jednak już teraz zaznaczę, że raczej w całym kursie nie będziemy z nich korzystać.
Klikamy więc Add i w oknie edytora zastajemy otwarty kod naszego nowego widoku. Jak widzisz nie jest on zbyt bogaty w treść. Ale to dobrze, i tak będziesz go chciał dostosować do swojej wizji.
@{ ViewBag.Title = "Index"; } <h2>Index</h2>
Żeby pokreślić to co osiągnąłeś w dzisiejszej części kursu zamień tekst znajdujący się w znacznikach <h2> na „Stworzyłem swój pierwszy kontroler w ASP.NET MVC!”.
@{ ViewBag.Title = "Index"; } <h2>Stworzyłem swój pierwszy kontroler w ASP.NET MVC</h2>
Teraz wielka chwila.
Uruchom aplikację.
Przejdź do adresu, który wcześniej spowodował nieładny, żółty błąd i podziwiaj swoją pierwszą stronę stworzoną we frameworku ASP.NET MVC :)
Wyznacz mi trasę
Na sam koniec powiem dwa zdania o tym skąd framework wie jak przetłumaczyć adres wpisany w pasku przeglądarki na odpowiedni kontroler i akcję.
Cała magia opiera się na mechanizmie zwanym routingiem.
Routing pozwala nam w prosty sposób zdefiniować reguły tłumaczenia adresu na odpowiednie ścieżki w naszej aplikacji.
Konfiguracja routingu w Twoim programie znajduje się w katalogu App_Start, w pliku RouteConfig.cs. Otwierając go zobaczysz taki kawałek kodu:
namespace ToDo { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
W tym momencie najbardziej istotne dla nas jest wywołanie metody MapRoute(). Pozwala ona dodawać reguły mapowania adresu. Tych reguł może być więcej bo np. dla wybranego elementu systemu będziesz chciał mieć specyficzny adres URL.
W naszym przypadku zdefiniowana jest reguła o nazwie „Default„. Parametr url zawiera szablon adresu. {controller}, {action} czy {id} reprezentują odpowiednie składowe. Dwa pierwsze są powiązane z kontrolerem i akcją i to one pozwalają określić na podstawie adresu o jaki kontroler i akcję nam chodzi. Warto wspomnieć że routing zakłada, że w adresie wpisujemy nazwę kontrolera bez dopisku Controller.
Parametry przekazuje
Element {id} jest tutaj dodany jako parametr akcji. Jeśli utworzymy akcję, której jeden z parametrów będzie nosił nazwę id to będziemy mogli go podać po prostu wpisując nazwę po znaku / zamiast jawnie podawać jego nazwę. A więc chcąc przekazać do akcji parametr id = 5 wystarczy, że wpiszemy w adresie:
localhost:8100/Kontroler/Akcja/5
Zamiast
localhost:8100/Kontroler/Akcja?id=5
Ostatni parametr metody MapRoute() pozwala ustawić wartości domyślne. Tzn. wartości, które zostaną wstawione w oznaczone w adresie miejsca w przypadku kiedy wpiszemy tylko główny adres strony ( w naszym przypadku http://localhost:8100).
Jak widzisz pojawia się tutaj kontroler Home i akcja Index. Id jest oznaczone jako parametr opcjonalny i nie będzie uzupełniony przy domyślnych wartościach. Najczęściej te domyślne wartości tutaj podane odpowiadają stronie głównej naszej aplikacji. Ustawianie wartości domyślnych pozwala nam pomijać też część adresu. Możemy wpisać tylko nazwę kontrolera. Wtedy jako nazwa akcji zostanie przyjęta nazwa Index. Dzięki temu wpisując w pasku adresu:
localhost:8100/Task
Zostaniemy przekierowani do akcji Task/Index. Nie można jednak pomijać elementów ze środka adresu, czyli w naszym przypadku wpisać nazwę akcji bez nazwy kontrolera.
Mówiąc o routingu wspomniałem o parametrze akcji w kontrolerze. Chodzi o to, że skoro nasze akcje to są po prostu publiczne metody to równie dobrze możemy dodać im parametry tak jak zwykłym metodom. Np. do naszej akcji Index w kontrolerze Task dodajmy na chwilę parametr name typu string:
namespace ToDo.Controllers { public class TaskController : Controller { // GET: Task public ActionResult Index(string name) { return View(); } } }
Co się teraz stanie jak uruchomisz program i przejdziesz na stronę /Task/Index?
Nic.
Strona się po prostu otworzy mimo nie wpisania wartości parametru w adresie. Dzieje się tak ponieważ w przypadku nie podania żadnej wartości będzie przekazywana wartość null, a jak wiadomo zmienna typu string może przyjmować wartość null. Jeśli w adresie wpiszemy
/Task/Index?name=Marek
To parametr name przyjmie wartość „Marek„. Możesz to łatwo sprawdzić ustawiając breakpoint w akcji i debugując aplikację.
Akcjami przyjmującymi parametry zajmiemy się szerzej przy okazji omawiania przesyłania i zapisywania danych od użytkownika. Także bez obaw jeśli w tym momencie wydaje Ci się to trochę skomplikowane ;)
Uff, przetrwaliśmy
I to tyle w tej części kursu ASP.NET MVC.
Poznałeś dzisiaj jak wygląda kontroler i akcja. Dowiedziałeś się gdzie znajdują się kontrolery i jakie z nich dostajemy od szablonu aplikacji out-of-box. Dowiedzieliśmy się jak dodać widok. Poznaliśmy magię tłumaczenia adresu na wywołanie akcji kontrolera za pomocą routingu.
W następnej części powalczymy z wyglądem naszej aplikacji ;)
Marku,
zainteresowałem się dziedziną programowania 3 miesiące wstecz. Poznałem podstawy C# i teraz zaprzyjaźniam się ASP.NET MVC. Gdy już moje morale zaczęły pikować w desperackim poszukiwaniu kursu czytelnego dla naprawdę początkujących natrafiłem na Twój blog. Tak czytelny i zrozumiały kurs mógł napisać tylko praktyk z dużym doświadczeniem. Jesteś pierwszą osobą która nie kopiuje skomplikowanych książkowych definicji nie zrozumiałych dla nowicjuszy.
Chcę Cie zachęcić tym postem do kontynuacji zaczętego kursu ” bo trudne może być łatwe” :)