Część 3. Widoki i modele widoków

Autor: Jon Galloway

MvC Music Store to aplikacja samouczka, która wprowadza i wyjaśnia krok po kroku, jak używać ASP.NET MVC i Visual Studio do tworzenia aplikacji internetowych.

MVC Music Store to uproszczona przykładowa implementacja sklepu, która sprzedaje albumy muzyczne online i implementuje podstawową administrację witryny, logowanie użytkowników i funkcjonalność koszyka.

W tej serii samouczków szczegółowo opisano wszystkie kroki podjęte w celu utworzenia przykładowej aplikacji ASP.NET MVC Music Store. Część 3 obejmuje widoki i modele widoków.

Do tej pory po prostu zwracaliśmy ciągi z akcji kontrolera. To miły sposób, aby dowiedzieć się, jak działają kontrolery, ale nie jest to sposób, w jaki chcesz utworzyć prawdziwą aplikację internetową. Chcemy lepiej wygenerować kod HTML z powrotem do przeglądarek odwiedzających naszą witrynę — taki, w którym można łatwiej dostosować wysyłanie zawartości HTML za pomocą plików szablonów. To właśnie robią widoki.

Dodawanie szablonu widoku

Aby użyć szablonu widoku, zmienimy metodę HomeController Index, aby zwrócić element ActionResult i wyświetlić widok (), jak pokazano poniżej:

public class HomeController : Controller
{
    //
    // GET: /Home/
    public ActionResult Index()
    {
        return View();
    }
}

Powyższa zmiana wskazuje, że zamiast zwracać ciąg, zamiast tego chcemy użyć elementu "Widok", aby wygenerować wynik z powrotem.

Teraz dodamy odpowiedni szablon Widok do naszego projektu. W tym celu umieścimy kursor tekstowy w metodzie akcji Indeks, a następnie kliknij prawym przyciskiem myszy i wybierz pozycję "Dodaj widok". Spowoduje to wyświetlenie okna dialogowego Dodawanie widoku:

Zrzut ekranu przedstawiający menu pokazujące wybór widoku dodawania.Zrzut ekranu przedstawiający okno dialogowe dodawania widoku z opcjami menu do wybierania i dodawania widoku.

Okno dialogowe "Dodaj widok" umożliwia szybkie i łatwe generowanie plików szablonu Wyświetl. Domyślnie okno dialogowe "Dodaj widok" wstępnie wypełnia nazwę szablonu Widok, aby był zgodny z metodą akcji, która będzie jej używać. Ponieważ użyliśmy menu kontekstowego "Dodaj widok" w metodzie akcji Index() elementu HomeController, powyższe okno dialogowe "Dodaj widok" ma wartość "Index" jako nazwę widoku wstępnie wypełniona. Nie musimy zmieniać żadnych opcji w tym oknie dialogowym, więc kliknij przycisk Dodaj.

Po kliknięciu przycisku Dodaj program Visual Web Developer utworzy nowy szablon widoku Index.cshtml dla nas w katalogu \Views\Home, tworząc folder, jeśli jeszcze nie istnieje.

Zrzut ekranu przedstawiający menu rozwijane Eksplorator rozwiązań z różnymi plikami w sklepie M V C Music Store.

Nazwa i lokalizacja folderu pliku "Index.cshtml" jest ważna i jest zgodna z domyślnymi konwencjami nazewnictwa MVC ASP.NET. Nazwa katalogu \Views\Home jest zgodna z kontrolerem o nazwie HomeController. Nazwa szablonu widoku, Indeks, odpowiada metodzie akcji kontrolera, która będzie wyświetlać widok.

ASP.NET MVC pozwala nam uniknąć konieczności jawnego określenia nazwy lub lokalizacji szablonu widoku, gdy używamy tej konwencji nazewnictwa, aby zwrócić widok. Spowoduje to domyślne renderowanie szablonu widoku \Views\Home\Index.cshtml, gdy napiszemy kod podobny do poniższego w naszym homeController:

public class HomeController : Controller
{
    //
    // GET: /Home/
    public ActionResult Index()
    {
        return View();
    }
}

Program Visual Web Developer utworzył i otworzył szablon widoku "Index.cshtml" po kliknięciu przycisku "Dodaj" w oknie dialogowym "Dodaj widok". Zawartość pliku Index.cshtml przedstawiono poniżej.

@{
    ViewBag.Title = "Index";
}
<h2>Index</h2>

Ten widok używa składni Razor, która jest bardziej zwięzła niż aparat widoku Web Forms używany w ASP.NET Web Forms i poprzednich wersjach ASP.NET MVC. Aparat widoku Web Forms jest nadal dostępny w ASP.NET MVC 3, ale wielu deweloperów uważa, że aparat widoku Razor pasuje do ASP.NET programowanie MVC naprawdę dobrze.

Pierwsze trzy wiersze ustawiają tytuł strony przy użyciu elementu ViewBag.Title. Przyjrzymy się temu, jak to działa bardziej szczegółowo wkrótce, ale najpierw zaktualizujemy tekst nagłówka tekstu i wyświetlmy stronę. <Zaktualizuj tag h2>, aby powiedzieć "To jest strona główna", jak pokazano poniżej.

@{
    ViewBag.Title = "Index";
}
<h2>This is the Home Page</h2>

Uruchomienie aplikacji pokazuje, że nasz nowy tekst jest widoczny na stronie głównej.

Zrzut ekranu przedstawiający stronę główną przeglądarki sklepu muzycznego z tekstem

Używanie układu dla typowych elementów witryny

Większość witryn internetowych ma zawartość udostępnioną między wieloma stronami: nawigacją, stopkami, obrazami logo, odwołaniami do arkusza stylów itp. Aparat widoku Razor ułatwia zarządzanie przy użyciu strony o nazwie _Layout.cshtml, która została automatycznie utworzona dla nas w folderze /Views/Shared.

Zrzut ekranu przedstawiający menu rozwijanego sklepu Music Store z wyświetloną ścieżką pliku do folderu udostępnionego znajdującego się w folderze widoku.

Kliknij dwukrotnie ten folder, aby wyświetlić zawartość pokazaną poniżej.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")"
rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")"     
            type="text/javascript"></script> 
    <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")"
            type="text/javascript"></script>
</head>
<body>
    @RenderBody()
</body>
</html>

Zawartość z naszych indywidualnych widoków będzie wyświetlana za pomocą @RenderBody() polecenia , a każda wspólna zawartość, którą chcemy wyświetlić poza tym, można dodać do znaczników _Layout.cshtml. Chcemy, aby nasz sklep MVC Music Store miał wspólny nagłówek z linkami do naszej strony głównej i obszaru Sklepu na wszystkich stronach w witrynie, więc dodamy to do szablonu bezpośrednio powyżej tej @RenderBody() instrukcji.

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")"
rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"
type="text/javascript"></script>
</head>
<body>
    <div id="header">
        <h1>
            ASP.NET MVC MUSIC STORE</h1>
        <ul id="navlist">
            <li class="first"><a href="/"
id="current">Home</a></li>
            <li><a
href="/Store/">Store</a></li>
        </ul>
    </div>
    @RenderBody()
</body>
</html>

Aktualizowanie arkusza stylów

Pusty szablon projektu zawiera bardzo usprawniony plik CSS, który zawiera tylko style używane do wyświetlania komunikatów weryfikacji. Nasz projektant dostarczył kilka dodatkowych arkuszy CSS i obrazów, aby zdefiniować wygląd i działanie naszej witryny, więc dodamy je teraz.

Zaktualizowany plik CSS i obrazy znajdują się w katalogu Zawartości MvcMusicStore-Assets.zip, który jest dostępny w witrynie MVC-Music-Store. Wybierzemy oba te elementy w Eksploratorze Windows i upuść je do folderu Zawartość rozwiązania w programie Visual Web Developer, jak pokazano poniżej:

Obok siebie zrzut ekranu przedstawiający katalog zawartości i menu rozwijane Sklep muzyczny z wyświetloną ścieżką pliku do folderu images w folderze zawartości.

Zostanie wyświetlony monit o potwierdzenie, czy chcesz zastąpić istniejący plik Site.css. Kliknij przycisk Yes (Tak).

Zrzut ekranu przedstawiający wyświetlone okno podręczne ostrzeżenia z prośbą o potwierdzenie akcji zastąpienia, z pytaniem, czy chcesz zastąpić istniejący plik.

Folder Zawartość aplikacji będzie teraz wyświetlany w następujący sposób:

Zrzut ekranu sklepu muzycznego z menu rozwijanego z wyróżnionym folderem zawartości przedstawiającym nowy folder obrazu z listą obrazów poniżej.

Teraz uruchomimy aplikację i zobaczmy, jak wyglądają nasze zmiany na stronie głównej.

Zrzut ekranu przedstawiający stronę główną okna przeglądarki sklepu muzycznego z wybranym obrazem wraz ze słowami

  • Przejrzyjmy, co się zmieniło: metoda akcji indeksu homeControllera została znaleziona i wyświetlona szablon \Views\Home\Index.cshtmlView, mimo że nasz kod o nazwie "return View()", ponieważ nasz szablon Widok jest zgodne ze standardową konwencją nazewnictwa.
  • Strona główna wyświetla prosty komunikat powitalny zdefiniowany w szablonie widoku \Views\Home\Index.cshtml.
  • Strona główna używa naszego szablonu _Layout.cshtml, więc wiadomość powitalna jest zawarta w standardowym układzie HTML witryny.

Przekazywanie informacji do widoku za pomocą modelu

Szablon Widok, który wyświetla tylko zakodowany kod HTML, nie będzie bardzo interesującą witryną internetową. Aby utworzyć dynamiczną witrynę internetową, zamiast tego chcemy przekazać informacje z naszych akcji kontrolera do szablonów wyświetlania.

W wzorcu Model-View-Controller termin Model odnosi się do obiektów reprezentujących dane w aplikacji. Często obiekty modelu odpowiadają tabelom w bazie danych, ale nie muszą.

Metody akcji kontrolera, które zwracają obiekt ActionResult, mogą przekazać obiekt modelu do widoku. Dzięki temu kontroler może w sposób czysty spakować wszystkie informacje potrzebne do wygenerowania odpowiedzi, a następnie przekazać te informacje do szablonu Widok, aby wygenerować odpowiednią odpowiedź HTML. Jest to najłatwiej zrozumieć, widząc go w działaniu, więc zacznijmy.

Najpierw utworzymy niektóre klasy modelu, aby reprezentować gatunki i albumy w naszym sklepie. Zacznijmy od utworzenia klasy Gatunek. Kliknij prawym przyciskiem myszy folder "Models" w projekcie, wybierz opcję "Dodaj klasę" i nadaj plikowi nazwę "Genre.cs".

Zrzut ekranu przedstawiający trzy pola menu obok siebie z kierunkami ścieżki pliku od prawej do lewej do zaznaczenia klasy

Zrzut ekranu przedstawiający opcje menu dodaj nowy element, wyświetlając trzy menu, które wybierają szablon, styl sortowania i typ; następnie pasek pola nazwy u dołu.

Następnie dodaj właściwość Nazwa ciągu publicznego do klasy, która została utworzona:

public class Genre
{
    public string Name { get; set; }
}

Uwaga: jeśli zastanawiasz się, { get; set; } notation korzysta z funkcji właściwości zaimplementowanych automatycznie w języku C#. Daje to nam korzyści wynikające z nieruchomości bez konieczności deklarowania pola zaplecza.

Następnie wykonaj te same kroki, aby utworzyć klasę Album (o nazwie Album.cs), która ma właściwość Title i Gatunek:

public class Album
{
    public string Title { get; set; }
    public Genre Genre { get; set; }
}

Teraz możemy zmodyfikować element StoreController tak, aby używał widoków, które wyświetlają dynamiczne informacje z modelu. Jeśli — do celów demonstracyjnych w tej chwili — nazwaliśmy nasze albumy na podstawie identyfikatora żądania, możemy wyświetlić te informacje, tak jak w poniższym widoku.

Zrzut ekranu przedstawiający stronę główną w przeglądarce z logo obrazu, bieżącą nazwą albumu i kliknięciem przycisków strony głównej i sklepu w prawym górnym rogu.

Zaczniemy od zmiany akcji Szczegóły sklepu, aby wyświetlić informacje dotyczące pojedynczego albumu. Dodaj instrukcję "using" na początku klasy StoreControllers , aby uwzględnić przestrzeń nazw MvcMusicStore.Models, więc nie musimy wpisywać mvcMusicStore.Models.Album za każdym razem, gdy chcemy użyć klasy albumu. Sekcja "usings" tej klasy powinna być teraz wyświetlana jak poniżej.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcMusicStore.Models;

Następnie zaktualizujemy akcję Kontrolera szczegółów, aby zwracała wartość ActionResult, a nie ciąg, tak jak w przypadku metody Index elementu HomeController.

public ActionResult Details(int id)

Teraz możemy zmodyfikować logikę, aby zwrócić obiekt Album do widoku. W dalszej części tego samouczka będziemy pobierać dane z bazy danych , ale na razie użyjemy "fikcyjnych danych", aby rozpocząć pracę.

public ActionResult Details(int id)
 {
    var album = new Album { Title = "Album " + id };
    return View(album);
 }

Uwaga: Jeśli nie znasz języka C#, możesz założyć, że użycie zmiennej var oznacza, że nasza zmienna albumu jest opóźniona. To nie jest poprawne — kompilator języka C# używa wnioskowania typu na podstawie tego, co przypisujemy do zmiennej w celu określenia, że album ma typ Album i kompiluje lokalną zmienną albumu jako typ albumu, więc uzyskujemy obsługę sprawdzania czasu kompilacji i edytora kodu programu Visual Studio.

Teraz utwórzmy szablon Widok, który używa naszego albumu do wygenerowania odpowiedzi HTML. Zanim to zrobimy, musimy skompilować projekt, aby okno dialogowe Dodawanie widoku wiedziało o naszej nowo utworzonej klasie Album. Projekt można skompilować, wybierając element menu Debug⇨Build MvcMusicStore (aby uzyskać dodatkowe środki, możesz użyć skrótu Ctrl-Shift-B do skompilowania projektu).

Zrzut ekranu przedstawiający edytor dokumentów sklepu muzycznego z kartą

Teraz, po skonfigurowaniu klas pomocniczych, możemy przystąpić do kompilowania szablonu Widok. Kliknij prawym przyciskiem myszy metodę Details i wybierz pozycję "Dodaj widok..." z menu kontekstowego.

Zrzut ekranu przedstawiający menu szablonu widoku, wyświetlanie fragmentu kodu i wyróżnianie opcji

Utworzymy nowy szablon Widok, taki jak wcześniej, za pomocą narzędzia HomeController. Ponieważ tworzymy ją na podstawie kontrolki StoreController, zostanie ona domyślnie wygenerowana w pliku \Views\Store\Index.cshtml.

W przeciwieństwie do poprzednich, zaznaczymy pole wyboru "Utwórz silnie typizowane" widoku. Następnie wybierzemy klasę "Album" z listy rozwijanej "Wyświetl klasę danych". Spowoduje to utworzenie szablonu Widok w oknie dialogowym "Dodawanie widoku", który oczekuje przekazania do niego obiektu Album.

Zrzut ekranu przedstawiający okno menu Dodawania widoku z polem wyboru

Po kliknięciu przycisku "Dodaj" zostanie utworzony szablon \Views\Store\Details.cshtml View zawierający następujący kod.

@model MvcMusicStore.Models.Album
@{
    ViewBag.Title = "Details";
}
<h2>Details</h2>

Zwróć uwagę na pierwszy wiersz, który wskazuje, że ten widok jest silnie wpisany do klasy Album. Aparat widoku Razor rozumie, że został przekazany obiekt Album, dzięki czemu możemy łatwo uzyskać dostęp do właściwości modelu, a nawet korzystać z funkcji IntelliSense w edytorze visual Web Developer.

<Zaktualizuj tag h2>, aby wyświetlał właściwość Tytuł albumu, modyfikując ten wiersz w następujący sposób.

<h2>Album: @Model.Title</h2>

Zwróć uwagę, że funkcja IntelliSense jest wyzwalana po wprowadzeniu kropki po słowie @Model kluczowym, pokazując właściwości i metody obsługiwane przez klasę Album.

Teraz uruchommy ponownie nasz projekt i przejdźmy do adresu URL /Store/Details/5. Zobaczymy szczegóły albumu, jak pokazano poniżej.

Zrzut ekranu przedstawiający okno przeglądarki strony głównej z logo obrazu w lewym górnym rogu i nazwą albumu pod nim.

Teraz wprowadzimy podobną aktualizację dla metody akcji Przeglądaj sklepu. Zaktualizuj metodę tak, aby zwracała wartość ActionResult i zmodyfikuj logikę metody, aby utworzyć nowy obiekt gatunku i zwrócić go do widoku.

public ActionResult Browse(string genre)
 {
    var genreModel = new Genre { Name = genre };
    return View(genreModel);
 }

Kliknij prawym przyciskiem myszy metodę Browse i wybierz pozycję "Dodaj widok..." z menu kontekstowego dodaj widok, który jest silnie typizowane, dodaj silnie typizowane do klasy Gatunek.

Zrzut ekranu przedstawiający menu kontekstowe z zaznaczonym polem

<Zaktualizuj element h2> w kodzie widoku (w pliku /Views/Store/Browse.cshtml), aby wyświetlić informacje o gatunku.

@model MvcMusicStore.Models.Genre
@{
    ViewBag.Title = "Browse";
}
<h2>Browsing Genre: @Model.Name</h2>

Teraz uruchommy ponownie nasz projekt i przejdźmy do /Store/Browse? Genre=Disco URL. Zobaczymy stronę Przeglądania, jak pokazano poniżej.

Zrzut ekranu przedstawiający okno strony głównej przeglądarki z wyświetlonym komunikatem

Na koniec dokonajmy nieco bardziej złożonej aktualizacji metody akcji Indeks sklepu i wyświetlmy, aby wyświetlić listę wszystkich gatunków w naszym sklepie. W tym celu użyjemy listy gatunków jako obiektu modelu, a nie tylko jednego gatunku.

public ActionResult Index()
{
    var genres = new List<Genre>
    {
        new Genre { Name = "Disco"},
        new Genre { Name = "Jazz"},
        new Genre { Name = "Rock"}
    };
    return View(genres);
 }

Kliknij prawym przyciskiem myszy metodę akcji Indeks sklepu i wybierz polecenie Dodaj widok tak jak poprzednio, wybierz pozycję Gatunek jako klasę Modelu, a następnie naciśnij przycisk Dodaj.

Zrzut ekranu przedstawiający menu okna

Najpierw zmienimy deklarację @model , aby wskazać, że widok będzie oczekiwać kilku obiektów gatunku, a nie tylko jednego. Zmień pierwszy wiersz pliku /Store/Index.cshtml w następujący sposób:

@model IEnumerable<MvcMusicStore.Models.Genre>

Informuje to aparat widoku Razor, że będzie działać z obiektem modelu, który może przechowywać kilka obiektów gatunku. Używamy gatunku IEnumerable<, a nie gatunku> listy<, ponieważ jest to bardziej ogólne, dzięki czemu możemy zmienić nasz typ modelu później na dowolny typ obiektu, który obsługuje interfejs IEnumerable.>

Następnie przejdziemy w pętli przez obiekty gatunku w modelu, jak pokazano w poniższym kodzie widoku ukończonego.

@model IEnumerable<MvcMusicStore.Models.Genre>
@{
    ViewBag.Title = "Store";
}
<h3>Browse Genres</h3>
<p>
    Select from @Model.Count()
genres:</p>
<ul>
    @foreach (var genre in Model)
    {
        <li>@genre.Name</li>
    }
</ul>

Zwróć uwagę, że w miarę wprowadzania tego kodu mamy pełną obsługę funkcji IntelliSense, dlatego po wpiseniu "@Model" zobaczymy wszystkie metody i właściwości obsługiwane przez typ Gatunek typu IEnumerable.

Zrzut ekranu przedstawiający fragment kodu H T M L z paskiem menu po wybraniu polecenia

W naszej pętli "foreach" program Visual Web Developer wie, że każdy element jest typu Gatunek, więc widzimy funkcję IntelliSense dla każdego typu gatunku.

Zrzut ekranu przedstawiający kod

Następnie funkcja tworzenia szkieletu zbadała obiekt Gatunek i ustaliła, że każda z nich będzie miała właściwość Name, więc przechodzi w pętli i zapisuje je. Generuje również łącza Edytuj, Szczegóły i Usuń do każdego pojedynczego elementu. Skorzystamy z tego później w naszym menedżerze sklepu, ale na razie chcemy mieć prostą listę.

Po uruchomieniu aplikacji i przejściu do /Store zobaczymy, że jest wyświetlana zarówno liczba, jak i lista gatunków.

Zrzut ekranu przedstawiający okno przeglądarki z tytułem

Nasz adres URL /Store, który zawiera listę gatunków, obecnie zawiera listę nazw gatunków po prostu jako zwykły tekst. Zmieńmy to tak, aby zamiast zwykłego tekstu zamiast zwykłego tekstu mieliśmy link Nazwy gatunków do odpowiedniego /Store/Browse URL, tak aby kliknięcie gatunku muzycznego, takiego jak "Disco", spowoduje przejście do /Store/Browse?genre=Disco URL. Możemy zaktualizować nasz szablon \Views\Store\Index.cshtml View, aby wyświetlić te linki przy użyciu kodu podobnego do poniższego (nie wpisz tego w pliku — ulepszymy go):

<ul>
    @foreach (var genre in Model)
    {
        <li><a href="/Store/Browse?genre=@genre.Name">@genre.Name</a></li>
    }
</ul>

To działa, ale może to prowadzić do problemów później, ponieważ opiera się na zakodowanym na stałe ciągu. Jeśli na przykład chcemy zmienić nazwę kontrolera, musimy przeszukać nasz kod, wyszukując linki, które muszą zostać zaktualizowane.

Alternatywną metodą, z jaką możemy skorzystać, jest skorzystanie z metody pomocnika HTML. ASP.NET MVC zawiera metody pomocnika HTML, które są dostępne w naszym kodzie szablonu Wyświetl w celu wykonywania różnych typowych zadań w następujący sposób. Metoda pomocnika Html.ActionLink() jest szczególnie przydatna i ułatwia tworzenie linków HTML <> i dba o denerwujące szczegóły, takie jak upewnienie się, że ścieżki adresów URL są prawidłowo zakodowane.

Html.ActionLink() ma kilka różnych przeciążeń, aby umożliwić określenie jak najwięcej informacji potrzebnych dla linków. W najprostszym przypadku podasz tylko tekst linku i metodę Action, aby przejść do po kliknięciu hiperlinku na kliencie. Na przykład możemy połączyć się z metodą "/Store/" Index() na stronie Szczegóły sklepu z tekstem linku "Przejdź do indeksu sklepu" przy użyciu następującego wywołania:

@Html.ActionLink("Go
to the Store Index", "Index")

Uwaga: W tym przypadku nie trzeba określać nazwy kontrolera, ponieważ po prostu łączymy się z inną akcją w tym samym kontrolerze, który renderuje bieżący widok.

Nasze linki do strony Przeglądania muszą jednak przekazać parametr, dlatego użyjemy innego przeciążenia metody Html.ActionLink, która przyjmuje trzy parametry:

    1. Tekst linku, w którym będzie wyświetlana nazwa gatunku
    1. Nazwa akcji kontrolera (Przeglądaj)
    1. Wartości parametrów trasy, określając zarówno nazwę (gatunek), jak i wartość (nazwa gatunku)

Łącząc te wszystkie elementy, napiszemy te linki do widoku Indeks sklepu:

<ul>
    @foreach (var genre in Model)
    {
        <li>@Html.ActionLink(genre.Name,
"Browse", new { genre = genre.Name })</li>
    }
</ul>

Teraz, gdy ponownie uruchomimy nasz projekt i uzyskamy dostęp do adresu URL /Store/, zobaczymy listę gatunków. Każdy gatunek jest hiperlinkiem — kliknięcie spowoduje przejście do naszego adresu URL /Store/Browse?genre=[gatunek].

Zrzut ekranu przedstawiający okno przeglądarki z tytułem Przeglądaj gatunek z komunikatem

Kod HTML listy gatunków wygląda następująco:

<ul>
    <li><a href="/Store/Browse?genre=Disco">Disco</a>
</li>
    <li><a href="/Store/Browse?genre=Jazz">Jazz</a>
</li>
    <li><a href="/Store/Browse?genre=Rock">Rock</a>
</li>
</ul>