Artykuł

sty 07 2011
0

Tworzenie stron z ASP.NET MVC2 w praktyce II

Witam w drugiej części wpisu, poświęconej tworzeniu stron w oparciu o technologię ASP.NET MVC 2. Żeby nie przedłużać, przedstawię plan działań na dziś, a wygląda on następująco:

  • Dodanie klasy rozszerzającej nasz model Osoby
  • Dodanie metody Create
  • Dodanie metody Edit
  • Dodanie metody Delete

Po zrealizowaniu powyższych czynności, nasza aplikacja będzie prezentować już jakiś sensownych poziom:) Dziś trochę przyśpieszymy tempo więc będzie trochę mniej prowadzenia za rękę, bo poprzedni wpis powinien dać już solidne postawy o tym jak się poruszać w świecie ASP.NET MVC2.

I najważniejsze - jeśli realizowałeś kroki pierwszej części przed 4 stycznia 2011, to przeczytaj koniecznie erratę do części pierwszej, która została dopisana na końcu poprzedniego wpisu.

Rozszerzanie modelu Osoby

Model utworzony w poprzedniej części tekstu, jest odwzorowaniem struktury przechowywanej w tabeli bazy danych. Struktura tego modelu, wpływa bezpośrednio na wiele elementów naszej witryny, m.in na strony konkretnych widoków, które generujemy za pomocą kreatorów Visual Studio. Jak się niedługo przekonamy, w kolejnych akapitach, dane w modelu wpływają np. na etykiety w formularzach, czy też na walidację samych formularzy, dlatego warto móc kontrolować trochę bardziej te strukturę. I co ważne można to zrobić, a wszystko za pomocą Partial Class.

Partial Class to taki bardzo fajny wynalazek, który pozwala na tworzenie struktury określonej klasy. Szeroko jest on wykorzystywany właśnie w ASP.NET (szczególnie za sprawą Code-Behind. Być może nie zdawaliście sobie dotychczas sprawy, ale nasz Entity Data Model wygenerował w poprzednim wpisie właśnie taką klasę częściową i nazywa się ona Person. Klasa ta posiada takie piękne właściwości:

  • PersonId
  • PersonFirstName
  • PersonLastName
  • PersonAge
  • PersonPhone

Prawdę powiedziawszy, to nie wyglądają one zachęcająco. Dlatego też, w katalogu modelu, utworzymy nową klasę Person, której kod będzie wyglądał następująco (usingi dodacie sobie sami:P):

namespace PersonMvc.Models
{
    [MetadataType(typeof(PersonMetadata))]
    public partial class Person
    {
        class PersonMetadata
        {
            [DisplayName("Id osoby")]
            public int PersonId { get; set; }

            [DisplayName("Imię")]
            [Required(ErrorMessage = "Polę Imię jest wymagane")]
            public string PersonFirstName { get; set; }

            [DisplayName("Nazwisko")]
            [Required(ErrorMessage = "Polę Nazwisko jest wymagane")]
            public string PersonLastName { get; set; }

            [DisplayName("Wiek")]
            [Required(ErrorMessage = "Polę Wiek jest wymagane")]
            [Range(0, 100, ErrorMessage= "Wiek musi być z przedziału 0 - 100")]
            public int PersonAge { get; set; }

            [DisplayName("Telefon")]
            public int PersonPhone { get; set; }
        }
    }
}

Przeanalizujmy teraz, co tu się właściwie dzieje. Utworzyliśmy klasę częściową Person i wykorzystaliśmy ją do przekazania meta danych (atrybut na górze). Następnie, nadaliśmy każdemu polu ładną, polską nazwę wyświetlaną za pomocą atrybutu DisplayName. Zaznaczyliśmy również pola wymagane za pomocą atrybutu Required. Na koniec, zdefiniowaliśmy wiek, jako liczbę w granicach 0-100.

Choć w tej chwili efekty naszej pracy będą jakby niewidoczne, to okażą się niezwykle przydatne, już za chwilę, podczas tworzenia metody Create.

Opcja dodawania

Kiedy w poprzedniej części wpisu (link na dole), utworzyliśmy metodę listującą, to częściowo wyznaczała ona budowę dalszych akcji zawartych w tym kontrolerze. Stało się tak dlatego, że w listingu powstały już odnośniki do odpowiednich komend. Oczywiście, możemy je zmienić na nasze własne. Ja jednak będę leniwy i zastosuję konwencję którą zaproponował Microsoft. Dlatego też, stworzymy teraz w naszym kontrolerze nową metodę Create, a właściwie to nawet dwie takie metody:

public ActionResult Create()
{
    return View();
}

[HttpPost]
public ActionResult Create(Person oPerson)
{

    if (ModelState.IsValid)
    {
        m_oPersonsEntities.AddToPeople(oPerson);
        m_oPersonsEntities.SaveChanges();
        return RedirectToAction("List");
    }
    else
    {
        return View(oPerson);
    }
}

Jak widać, utworzony przez nas kod, nie jest przesadnie skomplikowany. Przeanalizujmy go szybko. Pierwsza z dwóch metod, jest metodą domyślną i jest uruchamiana w pierwszym etapie dodawania osoby. Docelowo, wyświetli ona pusty formularz, w którym użytkownik będzie mógł wprowadzić dane osoby. Druga, przeciążona wersja metody Create, wywoływana jest w sytuacji, kiedy użytkownik wypełni formularz dodawania osoby i naciśnie przycisk typu Submit (zostanie wysłane żądanie POST - patrzy atrybut).

Ta wersja metody Create, posiada dwa scenariusze działania. Pierwszy, w sytuacji kiedy dane na formularzu są poprawne (w tej chwili będą właściwie zawsze, bo nie dodaliśmy jeszcze mechanizmów walidujących) oraz drugi, w którym te dane będą nie poprawne. W przypadku pierwszym, postępowanie jest proste, bo wykonujemy trzy klarowne operacje.

  • Dodajemy nowy rekord do bazy danych
  • Zapisujemy zmiany
  • Wykonujemy przekierowanie na metodę listującą

W drugim przypadku, sytuacja jest jeszcze prostsza. Zwracamy użytkownikowi widok, z wprowadzonymi przez niego danymi. Dodatkowo, wyświetlą się informację o błędnie wprowadzonych w formularzu danych (kłaniają się nam teraz nasze atrybuty z klasy częściowej - mówiłem, że powrócą szybko:)).

Tworzymy widok

Widok tworzymy w sposób standardowy, czyli klikamy prawym przyciskiem wewnątrz pierwszej metody Create i wybieramy opcję Add View. W nowym oknie (screen 1), tworzymy widok:

  • Silnie typowany
  • Oparty na klasie Person
  • Wykorzystujący szablon Create

Zatwierdzamy okno przyciskiem Add. W nowo utworzonym widoku (screen 2), musimy dokonać małych modyfikacji. Usuwamy pola, które są odpowiedzialne za id osoby (id to jest nadawane automatycznie przez własność Identity w bazie danych). Możemy również spolszczyć resztę formularza.

Utworzyliśmy właśnie w pełni funkcjonalną akcję dodawania. Czas więc przejść do edycji:).

Opcja edycji

Możemy już listować nasze ludziki i dodawać nowe, ale co w sytuacji, kiedy się okaże, że dane które wprowadziliśmy są błędne, lub po prostu zdewaluowały się w czasie? W końcu ludzie się starzeją i telefony też się zmieniają, a w przypadku kobiet można wyjść również za mąż, zmieniając tym samym nazwisko. Abstrahując, że nie jest to jakiś poważny projekt i wiele rzeczy w rzeczywistości zrobiło by się inaczej, to jednak opcja edycji może być przydatna, dlatego teraz też ją właśnie dorobimy.

Podobnie jak w przypadku akcji dodawania, to również i tutaj będziemy musieli stworzyć dwie metody do obsługi akcji. Niestety, tutaj sprawy się już trochę bardziej skomplikują. Spójrzcie na poniższy kod, o który został rozszerzony nasz kontroler:

public ActionResult Edit(int ID)
{
    var oPerson = m_oPersonsEntities.People.Where(oQueryPerson =>
        oQueryPerson.PersonId == ID).First();
    if (null == oPerson)
    {
        return RedirectToAction("List");
    }
    return View(oPerson);
}

[HttpPost]
public ActionResult Edit(Person oPerson)
{
    if (ModelState.IsValid)
    {
        var oPersonInDb = m_oPersonsEntities.People.Where(oQueryPerson => 
            oQueryPerson.PersonId == oPerson.PersonId).First();
        if (null == oPersonInDb)
        {
            return RedirectToAction("List");
        }
        oPersonInDb.PersonFirstName = oPerson.PersonFirstName;
        oPersonInDb.PersonLastName = oPerson.PersonLastName;
        oPersonInDb.PersonAge = oPerson.PersonAge;
        oPersonInDb.PersonPhone = oPerson.PersonPhone;
        m_oPersonsEntities.SaveChanges();
        return RedirectToAction("List");
    }
    else
    {
        return View(oPerson);
    }
}

Przejdźmy do analizy kodu. Pierwsza wersja metody Edit, przyjmuje teraz parametr ID, będący identyfikatorem danego kontaktu. Skąd pobierany jest ten argument? A no właściwie to z samego pasku adresu. Spójrzmy na przykładowy link:

http://localhost:16180/Home/Edit/1

W tym przypadku, to właśnie jedynka po ostatnim slashu, jest naszym ID. Ale teraz pytanie, skąd nasza aplikacja wie, że w adresie takiej postaci zawarto akurat ID? Odpowiedź na to pytanie możemy znaleźć w pliku Global.asax, który zawiera reguły Routingu, czyli taki drogowskaz do naszej aplikacji jak poruszać się po URLach w niej użytych (dla osób obeznanych z PHP i serwerem Apache, jest to coś na kształt reguł zapisywanych w pliku .htaccess, np. do tworzenia przyjaznych linków). Reguła domyślna, stworzona dla naszej aplikacji wygląda tak:

routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}", // URL with parameters
    new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

Jak widać, nasz URL składa się z trzech elementów - kontrolera, akcji oraz właśnie parametru ID. Co ważne, parametr naszej metody musi nazywać właśnie ID (wielkość liter jest ignorowana), inaczej działanie naszego kodu, w takiej postaci zakończy się błędem.

Wróćmy jednak do istoty sprawy, czyli omówienia pierwszej metody Edit. Celem tej metody jest pobranie danych dla osobnika o określonym ID. Wykorzystujemy do tego metodę Where, która operuje naszej kolekcji ludzików. Wewnątrz metody Where, stosujemy z kolei wyrażenia Lambda, które poznaliśmy przy okazji wpisu na temat LINQ. Jeśli nasze zapytanko, nie zwróci żadnego osobnika, to wtedy przekierowujemy osobnika na listę osób. W przeciwnym wypadku (bardzo przez nas pożądanym) przekazujemy obiekt klasy Person do strony widoku.

Druga z metod edycji, ma pewne elementy znane już z metody Create. Odbiera ona dane przekazane żądaniem POST. Na początku, dokonamy rutynowego sprawdzenia, czy ID osoby przekazanej w żądaniu istnieje w bazie, jeśli nie, robimy przekierowanie do listy. Opcja ta zabezpiecza nas przed edycja nie istniejących w bazie danych osób. Jeśli ID edytowanego rekordu istnieje w bazie danych, to możemy zacząć nadpisywać pola rekordu pobranego z bazy danych (pamiętajmy, że to obiektowość, więc działają tutaj bardzo ładnie referencje) danymi zwróconymi z formularza (proszę spojrzeć na nazewnictwo obiektów). Kiedy już to zrobimy, to zapisujemy zmiany na obiekcie naszego modelu i robimy przekierowanie do akcji listy.

Tworzymy widok

Mam już akcję utworzoną w kontrolerze, czas utworzyć widok dla karty edycji (nie muszę już chyba mówić jak uruchomić opcję tworzenia widoku:)?). Na ekranie tworzenia widoku, ustalamy opcje, tak jak na screenie 3, czyli w skrócie: widok o nazwie Edit; silnie typowany; oparty na klasie Person; z szablonem dla opcji Edit.

Utworzony widok, musimy delikatnie zmodyfikować. Fragment kodu:

<div class="editor-field">
    <%: Html.TextBoxFor(model => model.PersonId) %>
    <%: Html.ValidationMessageFor(model => model.PersonId) %>
</div>

Zmieniamy na:

<div class="editor-field">
    <%: Html.TextBoxFor(model => model.PersonId, new { @readonly = "readonly"} ) %>
</div>

Powyższy kod, wyrzuci walidację dla ID oraz ustawi to pole tylko do odczytu. W przypadku edycji, na tym etapie ma ono charakter czysto informacyjny. Oprócz tej modyfikacji, możemy jeszcze spolszczyć do końca formularz. Efekt końcowy widoczny jest na screenie 4.

Opcja usuwania

W przypadku opcji usuwania będzie bardzo prosto. Jedna wersja metody, do tego bardzo krótka oraz brak widoku - czysta poezja:) Tak więc, rozszerzmy nasz kontroler o nową metodę Delete, która podobnie jak metoda edycyjna, pobiera parametr ID z paska adresu:

public ActionResult Delete(int ID)
{
    var oPerson = m_oPersonsEntities.People.Where(oQueryPerson =>
        oQueryPerson.PersonId == ID).First();
    if (null != oPerson)
    {
        m_oPersonsEntities.DeleteObject(oPerson);
        m_oPersonsEntities.SaveChanges();
    }
    return RedirectToAction("List");
}

Na początku pobieramy obiekt, dla zadanego ID. Jeśli obiekt ten jest różny od nulla (innymi słowy istnieje osoba o zadanym ID), to usuwamy go z naszej kolekcji. Zapisujemy zmiany i osobnik przepadł kompletnie. Niezależnie od wszystkiego wracamy na akcję listy, by obejrzeć innych potencjalnych osobników do usunięcia;)

Walidacja po stronie klienta

Można powiedzieć, że nasza aplikacja osiągnęła pełną zakładaną funkcjonalność. Możemy listować, dodawać, edytować oraz usuwać osobników z naszej bazy danych. To, że funkcjonalność jest kompletna, nie zawsze znaczy, że wszystko jest zrobione idealnie. Np. dodaliśmy walidację, ale jest ona po stronie serwera, tzn. że użytkownik musi przeładować stronę, aby zobaczyć czy dane przez niego wprowadzone są poprawne. W dzisiejszym świecie jest to zaiste karygodna sprawa. Od czego jest Ajax i walidacja po stronie klienta?

Walidacja, tak na dobrą sprawę potrzebna nam jest tylko przy dodawaniu (metoda Create) oraz edycji (metoda Edit).

Z Solution Explorera otwieramy plik Site.Master znajdujący się w katalogu Views/Shared. Następnie rozwijamy katalog Scripts w Solution Explorerze i przeciągamy dwa następujące pliki do sekcji Head naszej strony:

  • MicrosoftAjax.js
  • MicrosoftMvcValidation.js

Co powinno poskutkować umieszczeniem dwóch nowych linii w sekcji Head:

<script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="../../Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>
<p>Teraz w plikach Create.aspx oraz Edit.aspx przed linią <em>Html.BeginForm</em>, musimy dodać następujący wiersz:</p>
<pre class="brush: xhtml"><% Html.EnableClientValidation(); %>

No i już możemy się cieszyć z walidacji po stronie klienta:)

Nawigacja

Na koniec, został nam element, o który w sumie powinniśmy zadbać może nawet na samym początku, czyli nawigacja. W tej chwili, nie ma odnośnika do akcji List, która w sumie okazało się nie jako być kluczową w całym tym naszym małym bałaganie:)

Nowy odnośnik, umieścimy pośród linków znajdujących się w prawym górnym rogu. Tak właśnie, jak zrobiono to na screenie 5. Aby dodać taki ładny odnośnik, musimy zmodyfikować plik Site.Master znajdujący się w katalogu Views/Shared.

Odnajdujemy tam element o identyfikatorze menucontainer i pośród linków tam się już znajdujących dodajemy nowy:

<li><%: Html.ActionLink("Lista", "List", "Home") %></li>

ActionLink, jest jedna z wielu fajnych metod dostępnych w jeszcze fajniejszej klasie HtmlHelper szeroko stosowanej w ASP.NET MVC. Posiada ona kilka przeciążonych wersji. W tym przypadku, jej parametry to kolejno:

  • Nazwa wyświetlana
  • Nazwa akcji
  • Nazwa kontrolera

Tym samym, określamy dokładnie gdzie nasz użytkownik ma się udać;)

Podsumowanie

O ASP.NET MVC2 można pisać zapewne wiele, nie to było jednak celem wpisu tego jak i poprzedniego. Celem takim było zaś, przedstawienie technologii, która pozwala w szybki i sprawny sposób na tworzenie przemyślanych, logicznych dobrze funkcjonujących serwisów, które można zrobić bez jakiejś wielkiej wiedzy;)

P.S. Gotowy projekt, wraz z kilkoma prostymi usprawnieniami, znajduje się w dziale Download:)

Data ostatniej modyfikacji: 22.10.2012, 15:01.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus