Microsoft.NET: ASP.NET MVC + Entity Framework (Model First) Do realizacji projektu potrzebne jest zintegrowane środowisko programistyczne Microsoft Visual Studio 2012. W ramach projektu budowana jest prosta aplikacja ASP.NET MVC 4 do ogłaszania informacji o podróżach samochodowych w celu znalezienia współuczestników podróży do partycypacji w kosztach. Dla uproszczenia w projekcie pominięte są kwestie autoryzacji dostępu przy korzystaniu z serwisu. Model bazy danych dla aplikacji będzie tworzony zgodnie ze strategią Model First. Kroki ćwiczenia: 1. Utworzenie nowego projektu (Web Project). a) Uruchom narzędzie Microsoft Visual Studio jeśli nie jest jeszcze uruchomione. b) Z menu głównego wybierz File New Project. Wybierz szablon ASP.NET MVC 4 Web Application z kategorii Visual C# -> Web. Jako nazwę projektu podaj TravelMates. Pozostałe opcje pozostaw domyślne i kliknij przycisk OK. c) W kolejnym kroku kreatora jako szablon projektu MVC 4 wybierz Internet Application. Upewnij się, że jako silnik widoków wybrany jest Razor (sprawdź jakie inne opcje są do wyboru). Nie zaznaczaj opcji tworzenia projektu dla testów jednostkowych.
d) Obejrzyj strukturę projektu w panelu Solution Explorer zwracając uwagę na utworzone przez kreator foldery. e) Obejrzyj kod startowy aplikacji w pliku Global.asax.cs. Odszukaj w folderze App_Start plik zawierający reguły routingu dla aplikacji i obejrzyj jego zawartość. f) Uruchom projekt kombinacją klawiszy Ctrl+F5. g) Przetestuj nawigację po stronach aplikacji zwracając uwagę na zawartość paska adresu w przeglądarce. h) Zarejestruj się do aplikacji (tj. utwórz nowe konto) klikając Log On, a następnie Register. i) Odszukaj kod strony odpowiedzialny za wyświetlanie linku umożliwiającego logowanie lub nazwę użytkownika z możliwością wylogowania się. Sprawdź w jaki sposób można uzyskać w kodzie strony informację o zalogowanym użytkowniku (obejrzyj kod stosownego fragmentu widoku). 2. Utworzenie obiektowego modelu danych w projekcie i odwzorowanie go na schemat bazy danych a) W panelu Solution Explorer wywołaj prawym klawiszem myszy menu kontekstowe dla projektu i wybierz opcję Add -> New Item. Wybierz opcję tworzenia modelu danych
encji Entity Framework (ADO.NET Entity Data Model) jako nazwę jego pliku podając TravelMates.edmx. b) W wywołanym kreatorze modelu danych wybierz opcję Empty Model i kliknij Finish.
c) W automatycznie otwartym do edycji w trybie graficznym modelu danych umieść na diagramie nową encję przeciągając ją z palety (Toolbox). d) Zaznacz encję na diagramie i poprzez paletę właściwości (Properties) ustaw nazwę encji na Trip, a następnie nazwę zbioru instancji encji na Trips e) Obejrzyj właściwości atrybutu Id zwracając uwagę na ustawienie automatycznej generacji wartości tego atrybutu w bazie danych. f) Korzystając z opcji menu kontekstowego Add->Scalar Property dodaj do encji Trip następujące właściwości zgodnie z poniższą tabelką:
Name Nullable Type FromCity False String ToCity False String StartDate False DateTime OrganizedBy True String PhoneNumber False String g) Zapisz projekt. Następnie z poziomu diagramu encji wywołaj menu kontekstowe i wybierz opcję Generate Database from Model.
h) W oknie kreatora, które się pojawi kliknij New Connection. W ustawieniach nowego połączenia jako źródło podaj Microsoft SQL Server Database File, a następnie kliknij przycisk Browse. Zleć utworzenie nowego pliku bazy danych przechodząc do katalogu, w którym masz prawo zapisu, jako nazwę pliku podając TripsXXXXX, gdzie XXXXX to Twój numer indeksu. Rozszerzenie *.mdf pliku zostanie nadane automatycznie. Kliknij OK i w okienku, które się pojawi, potwierdź decyzję o utworzeniu nowego pliku bazy danych.
i) Po powrocie do głównego okna kreatora kliknij Next. j) W drugim kroku kreatora obejrzyj wygenerowany skrypt i kliknij Finish. k) Zapisz wszystkie zmiany. Wróć do modelu encji. Obejrzyj odwzorowanie encji na tabelę (w oknie Mapping Details). Następnie obejrzyj kod źródłowy klasy encji. 3. Uruchomienie skryptu SQL w bazie danych. Komentarz: Z pozoru nic prostszego Visual Studio umożliwia uruchomienie skryptu otwartego w edytorze poprzez wywołanie menu kontekstowego i wybranie z niego opcji Execute SQL. Niestety uruchomienie skryptu SQL w dołączonym pliku bazy danych z poziomu Visual Studio jest problematyczne. Ponieważ nasz skrypt zawiera właściwie dwa polecenia SQL, uruchomimy je na pliku bazy danych jedno po drugim, w pewnym sensie omijając ograniczenia Visual Studio. a) Z menu kontekstowego węzła pliku bazy danych w panelu Server Explorer wybierz opcję New Query. Zamknij okno dialogowe z prośbą o wybór obiektów, na których ma operować tworzone zapytanie. b) Skopiuj ze skryptu treść polecenia CREATE TABLE i wklej polecenie do okna edytora zapytania. c) Uruchom polecenie SQL, np. klikając odpowiednią ikonę w pasku narzędziowym. d) Powtórz kroki b) i c) dla polecenia ALTER TABLE ze skryptu.
4. Wprowadzenie przykładowych danych do tabeli. a) Przejdź do panelu Server Explorer. Poprzez odpowiednie połączenie z bazą danych odszukaj utworzoną w bazie danych tabelę na którą odwzorowana jest encja Trip. Przejdź do podglądu danych tabeli. b) Wprowadź do tabeli dwa przykładowe wiersze 5. Utworzenie kontrolerów i widoków do przeglądania informacji o zgłoszonych podróżach. a) Z menu kontekstowego dla folderu Controllers w panelu Solution Explorer wybierz opcję Add->Controller. Jako jego nazwę podaj TripsController, a jako szablon scaffoldingu wybierz MVC controller with empty read/write actions (celowo nie wybierzemy kontrolera, który tworzy również widoki, aby przećwiczyć ich ręczne dodawanie).
b) Obejrzyj kod wygenerowanego kontrolera. Zwróć uwagę na komentarze wskazujące przewidywany sposób wywoływania poszczególnych akcji kontrolera. Odszukaj atrybuty metod reprezentujących akcje wywoływane inną metodą HTTP niż GET. Zauważ że dla operacji edycji, dodawania i usuwania kreator wygenerował pary metod o tej samej nazwie: jedna dla metody GET, a druga dla POST. Zastanów się która będzie uruchamiana jako pierwsza i która wykona rzeczywistą operację modyfikacji modelu. c) Zastąp ciało metody Index utworzonego kontrolera poniższym kodem. var trips = db.trips; return View(trips); d) Zapisz wszystkie zmiany, a następnie przebuduj projekt wybierając opcję Rebuild z menu kontekstowego węzła projektu w oknie Solution Explorer. e) Pozostając w edytorze kodu kontrolera wywołaj menu kontekstowe z poziomu sygnatury metody Index i wybierz z niego opcję Add View. Pozostaw zaproponowaną nazwę widoku. (Dzięki temu, że jest taka sama jak nazwa akcji kontrolera, metoda akcji zwracająca widok nie musi podawać jawnie jego nazwy.) Zaznacz, że widok ma być silnie typowany. Jako klasę modelu wybierz klasę encji Trip. Jako szablon scaffoldingu wybierz List. Pozostaw zaproponowane ustawienia dotyczące strony wzorcowej.
f) Obejrzyj kod wygenerowanego widoku. Zapisz wszystkie zmiany. Uruchom aplikację. Wprowadź w pasku adresu przeglądarki adres prowadzący do listy podróży. g) Przejdź do edycji strony wzorcowej. Zmień nagłówek na Travel Mates i dodaj pozycję menu prowadzącą do listy podróży (wzoruj się na istniejących już trzech pozycjach menu). Ponownie uruchom i przetestuj aplikację. h) Wróć do edycji kodu kontrolera i zastąp ciało metody Details poniższym kodem. var trip = db.trips.firstordefault(t => t.id == id); i) Tym samym sposobem co poprzednio utwórz silnie typowany widok, który będzie prezentował szczegółowe informacje na temat podanej podróży. W kreatorze jako zawartość widoku podaj Details. j) Uruchom aplikację i podejrzyj szczegóły o podróżach. k) Zmodyfikuj adres w przeglądarce, aby sprawdzić co się stanie przy próbie podejrzenia szczegółów na temat podróży o nieistniejącym identyfikatorze. l) Zastąp ciało akcji Details w kontrolerze poniższym kodem. var trip = db.trips.firstordefault(t => t.id == id); if (trip == null) ViewBag.BadId = id; return View("NotFound"); else m) Utwórz kolejny widok dla akcji Details. Nazwij go NotFound. Tym razem widok nie będzie silnie typowany. (Dlatego kontroler przekazuje mu dane przez słownik ViewBag, a nie obiekt modelu.)
n) W kodzie dodanej właśnie strony widoku zastąp tytuł strony przez Trip not found, a jej zawartość przez: <h2>trip @ViewBag.BadId does not exist.</h2> o) Zapisz zmiany. Uruchom aplikację. Sprawdź co się stanie przy próbie obejrzenia informacji o nieistniejącej podróży. 6. Obsługa dodawania nowych podróży. a) Zastąp ciało akcji Create (wołanej przez GET) w kontrolerze poniższym kodem. Gdy użytkownik jest zalogowany, jego nazwa pojawi się domyślnie jako organizator podróży. Trip trip = new Trip(); if (HttpContext.User!= null) trip.organizedby = HttpContext.User.Identity.Name; b) Zastąp parametr akcji Create (wołanej przez POST) przez trip typu Trip. Dzięki temu framework MVC przekaże do metody gotowy obiekt encji z danymi z formularza (bez tej modyfikacji, w ciele metody konieczne by było ręczne utworzenie takiego obiektu i wypełnienie go przekazanymi jako parametr danymi z formularza).
c) Zastąp ciało akcji Create (wołanej przez POST) w kontrolerze poniższym kodem. try if (ModelState.IsValid) db.trips.add(trip); db.savechanges(); return RedirectToAction("Index"); catch d) Utwórz kreatorem stronę widoku do wprowadzania danych o nowej podróży. Wywołaj kreator z poziomu kodu akcji kontrolera Create wołanej przez GET. W kreatorze jako zawartość widoku podaj Create. e) Zapisz zmiany. Uruchom aplikację i przetestuj dodawanie informacji o nowych podróżach. Sprawdź czy gdy użytkownik jest zalogowany, automatycznie wypełnia się pole organizatora podróży. UWAGA: Podczas wprowadzania danych zwróć uwagę na kompletność informacji i format daty. Ponieważ nie zaimplementowaliśmy jeszcze walidacji na poziomie aplikacji ani obsługi błędów przy interakcji z bazą danych, przy niekompletnych lub niepoprawnych danych ponownie przedstawiany jest formularz bez żadnych informacji o przyczynach niezapisania danych do bazy. 7. ZADANIA DODATKOWE DLA CHĘTNYCH: a) Zaimplementuj obsługę edycji informacji o podróżach. b) Zaimplementuj obsługę usuwania informacji o podróżach (W tym wypadku najpierw wyświetlana powinna być strona z prośbą o potwierdzenie decyzji.) 8. Obsługa błędów zwracanych przez bazę danych. a) W sekcji obsługi wyjątku metody Create wołanej przez POST dodaj poniższy wiersz (to samo dla metody Edit wołanej przez POST). ViewBag.ErrorMessage = "Nie udało się zapisać danych. Sprawdź czy data jest poprawna."; b) W sekcji obsługi wyjątku metody Delete wołanej przez POST dodaj poniższy wiersz. ViewBag.ErrorMessage = "Nie udało się usunąć danych. Sprawdź czy dane nie zostały usunięte przez innego użytkownika."; c) Na końcu treści stron widoków Create, Edit i Delete umieść poniższy element wyświetlający przekazywany komunikat o błędzie. <p class="error">@viewbag.errormessage</p>
d) Przetestuj zachowanie aplikacji przy próbie dodania informacji dla roku 0001. 9. Walidacja danych na poziomie modelu poprzez adnotacje. UWAGA: Ponieważ wykorzystując Entity Framework nie zastosowaliśmy strategii Code First, nie możemy umieścić adnotacji bezpośrednio przy atrybutach klasy encji. Kod tej klasy został wygenerowany na podstawie graficznego modelu i nie powinien być zmieniany ręcznie przez programistę. Wykorzystamy więc mechanizm klas częściowych i klasę metadanych powiązaną z klasą encji a) Dodaj w katalogu Models klasę częściową Trip bez ciała. Możesz użyć w tym celu kreator Add->Class. Zwróć uwagę aby klasa była umieszczona w tej samej przestrzeni nazw co klasa częściowa Trip wygenerowana przez kreator. b) W tym samym pliku źródłowym i w tej samej przestrzeni nazw umieść poniższą klasę metadanych. Obejrzyj zawarte w niej adnotacje i zaimportuj dla nich odpowiednie przestrzenie nazw. public class TripMetaData public object Id get; set; [Required] public object FromCity get; set; [Required] public object ToCity get; set; [Required] [DataType(DataType.Date)] public object StartDate get; set; [Required] [StringLength(30, MinimumLength = 2)] public object OrganizedBy get; set; [Required] public object PhoneNumber get; set; c) Definicję dodanej w punkcie a) klasy częściowej poprzedź poniższą adnotacją aby powiązać klasę encji z klasą metadanych. [MetadataType(typeof(TripMetaData))] d) Obejrzyj w kodzie stron widoków do edycji i dodawania danych elementy odpowiedzialne za wyświetlanie błędów walidacji na poziomie komponentów modelu. e) Uruchom aplikację i przetestuj wszystkie dodane mechanizmy walidacji dodając lub edytując wycieczki.
DODATEK Kompletny przykładowy kod kontrolera implementowanego w tutorialu. using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Web; using System.Web.Mvc; namespace TravelMates.Controllers public class TripsController : Controller // GET: /Trips/ public ActionResult Index() var trips = db.trips; return View(trips); // GET: /Trips/Details/5 public ActionResult Details(int id) var trip = db.trips.firstordefault(t => t.id == id); if (trip == null) ViewBag.BadId = id; return View("NotFound"); else // GET: /Trips/Create public ActionResult Create() Trip trip = new Trip(); if (HttpContext.User!= null) trip.organizedby = HttpContext.User.Identity.Name;
// POST: /Trips/Create [HttpPost] public ActionResult Create(Trip trip) try if (ModelState.IsValid) db.trips.add(trip); db.savechanges(); return RedirectToAction("Index"); catch ViewBag.ErrorMessage = "Nie udało się zapisać danych. Sprawdź czy data jest poprawna."; // GET: /Trips/Edit/5 public ActionResult Edit(int id) Trip trip = db.trips.firstordefault(t => t.id == id); if (trip == null) ViewBag.BadId = id; return View("NotFound"); else // POST: /Trips/Edit/5 [HttpPost] public ActionResult Edit(Trip trip) try if (ModelState.IsValid) db.entry(trip).state = EntityState.Modified; db.savechanges(); return RedirectToAction("Index"); catch ViewBag.ErrorMessage = "Nie udało się zapisać danych. Sprawdź czy data jest poprawna.";
// GET: /Trips/Delete/5 public ActionResult Delete(int id) Trip trip = db.trips.firstordefault(t => t.id == id); if (trip == null) ViewBag.BadId = id; return View("NotFound"); else // POST: /Trips/Delete/5 [HttpPost, ActionName("Delete")] // Ustawiamy ActionName aby metoda mogla nazywac sie // inaczej niz Delete wywolywane przez GET // nie moze byc dwoch metod Delete o tej samej sygnaturze // inne wyjscie to drugi nieuzywany parametr FormCollection public ActionResult DeleteConfirmed(int id) Trip trip = db.trips.firstordefault(t => t.id == id); try db.trips.remove(trip); db.savechanges(); return RedirectToAction("Index"); catch ViewBag.ErrorMessage = "Nie udało się usunąć danych. Sprawdź czy dane nie zostały usunięte przez innego użytkownika.";