Skip to content

3. Kontrolery, akcje, routing

Rozważmy architekturę aplikacji ASP.NET MVC:

W tym rozdziale przyjrzymy się procesowi, w wyniku którego żądanie [1] trafia do kontrolera i akcji [2a], które je przetworzą – mechanizm ten nazywamy routingiem. Przedstawiamy również różne odpowiedzi [3], jakie akcja może zwrócić do przeglądarki. Może to być coś innego niż widok V [4b].

3.1. Struktura projektu ASP.NET MVC

Stwórzmy pierwszy projekt ASP.NET MVC za pomocą programu Visual Studio Express 2012. Dodamy go [1] do rozwiązania wykorzystanego w poprzednim rozdziale:

  • jako [2] – nazwa nowego projektu;
  • jako [3, 4] wybieramy projekt bazowy ASP.NET MVC. Ten szablon zapewnia nam pustą aplikację internetową, zawierającą jednak wszystkie zasoby (DLL, biblioteki JavaScript itp.) niezbędne do pracy.

Powstały projekt przedstawiono w pliku [5]. Uczynimy z niego [6] projekt startowy rozwiązania:

W pliku [5] należy zwrócić uwagę na następujące kwestie:

  • architektura projektu odzwierciedla jego model MVC:
  • kontrolery C zostaną umieszczone w folderze [Controllers],
  • modele danych M zostaną umieszczone w folderze [Models],
  • widoki V zostaną umieszczone w folderze [Views],
  • w [1] plik [Site.css] będzie domyślnym plikiem aplikacji CSS;
  • w pliku [2] udostępniono nam pewną liczbę bibliotek JavaScript;
  • w pliku [3] znajdują się trzy konkretne widoki: _ViewStart, _Layout oraz Error.

Plik [_ViewStart] ma następującą zawartość:


@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}
  • wiersz 1: znak @ oznacza sekwencję kodu C# w widoku. W widoku można bowiem umieścić kod C#;
  • wiersz 2: definiuje zmienną Layout, która określa widok nadrzędny wszystkich widoków. Odpowiada ona stronie głównej klasycznego frameworka ASP.NET.

Plik [_Layout] ma następującą treść:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    @RenderBody()

    @Scripts.Render("~/bundles/jquery")
    @RenderSection("scripts", required: false)
</body>
</html>

Gdy wyświetlany będzie widok z folderu [Views], jego treść zostanie wygenerowana na podstawie wiersza 11 powyżej. Oznacza to, że widok nie musi zawierać tagów <html>, <head> ani <body>. Są one dostarczane przez powyższy plik. Na razie jest on dość hermetyczny. Uprośćmy go:


<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>Tutoriel ASP.NET MVC</title>
</head>
<body>
  <h2>Tutoriel ASP.NET MVC</h2>
  @RenderBody()
</body>
</html>
  • wiersz 6: tytuł wspólny dla wszystkich widoków;
  • wiersz 9: nagłówek wspólny dla wszystkich widoków;
  • wiersz 10: treść właściwa dla wyświetlanego widoku.
  
  • [Web.config] to plik konfiguracyjny aplikacji internetowej. Jest on złożony. Konieczna będzie jego modyfikacja w przypadku wykorzystania frameworka [Spring.net] w architekturze wielowarstwowej.
  • [Global.asax] zawiera kod wykonywany podczas uruchamiania aplikacji. Zazwyczaj kod ten wykorzystuje różne pliki konfiguracyjne aplikacji, w tym [Web.config].

3.2. Domyślne przekierowanie w pliku URL

Kod pliku [Global.asax] wygląda obecnie następująco:


using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace Exemple_01
{
 
  public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
   ...
    }
  }
}
  • wiersz 6, namespace klasy. Pochodzi on bezpośrednio z nazwy projektu i znajduje się we właściwościach projektu:

Image

Ustawiamy [1] jako domyślną przestrzeń nazw. Będzie ona wówczas domyślnie używana dla wszystkich klas, które zostaną utworzone w projekcie.

Wróćmy do kodu klasy [Global.asax]:


using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace Exemple_01
{
 
  public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
   ...
    }
  }
}
  • wiersz 9: klasa [MvcApplication] wywodzi się z klasy [HttpApplication]. Nazwę [MvcApplication] można zmienić;
  • wiersz 11: metoda [Application_Start] jest metodą uruchamianą podczas startu aplikacji internetowej. Jest ona wykonywana tylko raz. To właśnie w tym miejscu następuje inicjalizacja aplikacji.

Kod metody [Application_Start] wygląda obecnie następująco:


    protected void Application_Start()
    {
      AreaRegistration.RegisterAllAreas();

      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
}

Na razie nie musimy rozumieć całego tego kodu. Wiersze 5–8 definiują trasy akceptowane przez aplikację internetową. Wróćmy do architektury aplikacji ASP.NET MVC:

Wyjaśniliśmy już, że [Front Controller] ma przekierować URL do akcji odpowiedzialnej za jego przetworzenie. Trasa służy do połączenia szablonu URL z akcją. Trasy te są zdefiniowane w folderze [App_Start] projektu przez klasy [WebApiConfig, FilterConfig, RouteConfig, BundleConfig]:

 

Na razie interesuje nas wyłącznie klasa [RouteConfig]:


using System.Web.Mvc;
using System.Web.Routing;

namespace Exemple_01
{
  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 }
      );
    }
  }
}

Wiersze 12–16 określają format plików URL akceptowanych przez aplikację. Nazywa się to trasą. Może istnieć kilka możliwych tras (= kilka możliwych formatów plików URL). Różnią się one między sobą nazwą (wiersz 13). Format plików URL dla tej ścieżki jest zdefiniowany w wierszu 14. W tym przypadku plik URL będzie składał się z trzech elementów:

  • {controller}: nazwa klasy pochodnej od [Controller]. Będzie ona wyszukiwana w folderze [Controllers] projektu. Zgodnie z konwencją, jeśli URL to /X/Y/Z, kontrolerem odpowiedzialnym za obsługę tego URL będzie klasa XController. Do nazwy kontrolera zawartego w URL dodawany jest przyrostek „Controller”;
  • {action}: nazwa metody w wyżej wskazanym kontrolerze. To właśnie ona odbierze parametry towarzyszące URL i je przetworzy. Metoda ta może zwracać różne wyniki:
    • void: akcja samodzielnie utworzy odpowiedź dla przeglądarki klienta
    • String: akcja zwraca klientowi ciąg znaków;
    • ViewResult: zwraca widok do klienta;
    • PartialViewResult: zwraca częściowy widok;
    • EmptyResult: do klienta wysyłana jest pusta odpowiedź;
    • RedirectResult: prosi klienta o przekierowanie do URL
    • RedirectToRouteResult: to samo, ale URL jest tworzona na podstawie ścieżek aplikacji;
    • JsonResult: wysyła odpowiedź JSON
    • JavaScriptResult: zwraca kod JavaScript do klienta;
    • ContentResult: zwraca strumień HTML do klienta bez przechodzenia przez widok;
    • FileContentResult: zwraca plik do klienta;
    • FileStreamResult: to samo, ale inną drogą;
    • FilePathResult: ...
  • {id}: parametr, który zostanie przekazany do akcji. W tym celu akcja musi posiadać parametr o nazwie id.

Wiersz 15 określa wartości domyślne na wypadek, gdy URL nie ma oczekiwanej postaci /{controller}/{action}/{id}. Wskazuje on również, że parametr {id} w URL jest opcjonalny. Poniżej znajduje się lista niekompletnych URL oraz URL uzupełniony wartościami domyślnymi:

Oryginalny plik URL
Uzupełniony plik URL
/
/Strona główna/Indeks
/Do
/Do/Index
/Do/Something
/Zrób/Coś
/Zrób/Coś/4
/Zrób/Coś/4
/Zrób/Coś/x/y/z
URL bez trasy

3.3. Tworzenie kontrolera i pierwszej akcji

Stwórzmy pierwszy kontroler:

 
  • w [1] podaj nazwę kontrolera wraz z jego sufiksem [Controller];
  • w pliku [2] utwórz pusty kontroler o nazwie MVC;
  • w [3] został on utworzony.

Kod dla [FirstController] jest następujący:


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

namespace Exemple_01.Controllers
{
    public class FirstController : Controller
    {
        //
        // GET: /First/

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

    }
}
  • wiersz 7: wygenerowano domyślny plik namespace;
  • wiersz 9: klasa [FirstController] wywodzi się z klasy [System.Web.Mvc.Controller];
  • wiersze 14–17: domyślnie wygenerowano akcję [Index]. Jest ona publiczna. To ważne, w przeciwnym razie nie zostanie znaleziona. Zwraca ona typ [ActionResult], który jest klasą abstrakcyjną, od której wywodzi się większość wyników zwracanych zazwyczaj przez akcję. Jest to typ „uniwersalny”, który można sprecyzować, zastępując go rzeczywistą nazwą zwracanego typu;
  • wiersz 16: metoda nie wykonuje żadnej operacji. Ogranicza się do zwracania widoku, a więc typu [ViewResult]. Nazwa widoku nie jest określona. W takim przypadku framework szuka w folderze [Views / First] widoku o nazwie akcji, czyli w tym przypadku: [Index.cshtml].

Utwórzmy widok [Index.cshtml]:

  • w [1] klikamy prawym przyciskiem myszy w kodzie akcji i wybieramy opcję [Ajouter une vue];
  • w [2] kreator proponuje widok o nazwie akcji. To właśnie nam tutaj odpowiada;
  • w przypadku [3] domyślnie proponowane jest użycie strony szablonowej [_Layout.cshtml];
  • w przypadku [4], po zatwierdzeniu kreator tworzy widok w podfolderze folderu [Views] o nazwie kontrolera (First).

Kod wygenerowany dla widoku [Index] jest następujący:


@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>
  • wiersze 1–3: kod w języku C# definiujący zmienną;
  • wiersz 5: tag HTML.

Cały poprzedni kod zastępujemy następującym:


<strong>Vue [Index]...</strong>

Podsumowując:

  • mamy kontroler C o nazwie [First];
  • mamy akcję o nazwie [Index], która wywołuje wyświetlenie widoku o nazwie [Index];
  • mamy widok o nazwie V [Index].

Akcję [Index] możemy wywołać na dwa sposoby:

  • /First/Index;
  • /First, ponieważ [Index] jest również akcją domyślną w trasach.

Uruchommy aplikację (CTRL-F5). Otrzymujemy następującą stronę:

1

Image

W przypadku [1] żądana akcja URL to http://localhost:49302. Nie ma ścieżki. Wiemy, że nasz router oczekuje akcji URL w postaci /{controller}/{action}/{id}. Ponieważ elementy te nie występują, stosowane są wartości domyślne. Żądanie URL zmienia się na http://localhost:49302/Home/Index. Kontroler [Home] nie istnieje. W związku z tym żądanie URL zostaje odrzucone.

Spróbujmy teraz wprowadzić URL http://localhost:49302/First/Index, wpisując go bezpośrednio w przeglądarce:

Powyższa strona została wygenerowana przez akcję [Index] kontrolera [First]. Strona wygenerowana przez tę akcję to widok [Index], którego kod wyglądał następująco:


<strong>Vue [Index]...</strong>

Generuje ona część [1]. Część [2] pochodzi natomiast ze strony głównej [_Layout], którą zdefiniowaliśmy nieco wcześniej:


<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>Tutoriel ASP.NET MVC</title>
</head>
<body>
  <h2>Tutoriel ASP.NET MVC</h2>
  @RenderBody()
</body>
</html>

Wiersz 9 wygenerował część strony o nazwie [2]. Widok [Index] pojawia się natomiast dopiero w wierszu 10.

Jeśli wyświetlimy kod źródłowy otrzymanej strony, zauważymy, że strona [Index] została rzeczywiście dołączona (wiersz 10 poniżej) do strony [Layout]:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>Tutoriel ASP.NET MVC</title>
</head>
<body>
  <h2>Tutoriel ASP.NET MVC</h2>
  <strong>Vue [Index]...</strong>
</body>
</html>

Spróbujmy teraz z plikami URL i [/First]:

  

Plik URL [/First] był niekompletny. Został uzupełniony domyślnymi wartościami trasy i stał się plikiem [/First/Index]. Otrzymujemy zatem ten sam wynik, co poprzednio.

3.4. Akcja z wynikiem typu [ContentResult] – 1

Utwórzmy nową akcję w kontrolerze [First]:


using System.Text;
using System.Web.Mvc;

namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
    // Strona główna
    public ViewResult Index()
    {
      return View();
    }
    // Action01
    public ContentResult Action01()
    {
      return Content("<h1>Action [Action01]</h1>", "text/plain", Encoding.UTF8);
    }
  }
}

Nowa akcja jest zdefiniowana w wierszach 14–18. Polega ona po prostu na zwróceniu ciągu znaków za pomocą metody [Content] (wiersz 16) klasy [Controller] (wiersz 6). Parametry tej metody to:

  1. ciąg znaków odpowiedzi;
  2. wskaźnik rodzaju wysyłanego tekstu: „text/plain”, „text/html”, „text/xml”, ... Wskaźnik ten nosi nazwę typu MIME (http://fr.wikipedia.org/wiki/Type_MIME);
  3. trzeci parametr pozwala określić typ kodowania użytego dla tekstu.

Zamiast korzystać z abstrakcyjnego typu [ActionResult], nasze metody określają rzeczywisty typ renderowania (wiersze 9 i 14).

Wywołajmy metodę URL [/First/Action01]. Otrzymujemy następującą stronę:

  

Przyjrzyjmy się kodowi źródłowemu otrzymanej strony:

<h1>Action [Action01]</h1>

Przeglądarka otrzymała jedynie tekst wysłany przez akcję i nic więcej. Tryb ten jest interesujący, gdy chcemy poprosić serwer WWW o czyste dane bez otaczającej je otoczki HTML. Warto zauważyć, że przeglądarka nie zinterpretowała tagu <h1>. Aby zrozumieć dlaczego, przyjrzyjmy się w przeglądarce Chrome wymianie danych:

Przeglądarka wysłała następujące nagłówki:

1
2
3
4
5
6
7
8
GET /First/Action01 HTTP/1.1
Host: localhost:49302
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0,8
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4

Serwer odpowiedział następującymi nagłówkami:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMQ==?=
X-Powered-By: ASP.NET
Date: Mon, 23 Sep 2013 15:22:33 GMT
Content-Length: 141
  • wiersz 3: określa typ dokumentu. Znajdujemy tu atrybuty ustawione w metodzie [Action01]. To właśnie dlatego, że poinformowano przeglądarkę, iż dokument jest typu „text/plain”, a nie „text/html”, nie zinterpretowała ona tagu <h1>, który znajdował się w otrzymanym dokumencie.

3.5. Akcja z wynikiem typu [ContentResult] – 2

Rozważmy następującą trzecią akcję:


using System.Text;
using System.Web.Mvc;

namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
   ...
    // Akcja02
    public ContentResult Action02()
    {
      string data = "<action><name>Action02</name><description>renvoie un texte XML</description></action>";
      return Content(data, "text/xml", Encoding.UTF8);
    }
  }
}
  • wiersz 12: definiujemy tekst XML;
  • wiersz 13: wysyła się go do przeglądarki, określając, że jest to XML o typie MIME „text/xml”.

W przeglądarce wyświetla się następująca strona:

 

Przyjrzyjmy się w przeglądarce Chrome odpowiedzi HTTP z serwera:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMg==?=
X-Powered-By: ASP.NET
Date: Mon, 23 Sep 2013 15:29:34 GMT
Content-Length: 176
  • wiersz 3: określa rodzaj dokumentu. Znajdujemy tu atrybuty ustawione w metodzie [Action02];
  • wiersz 12: rozmiar w bajtach dokumentu wysłanego przez serwer.

Dokument wysłany przez serwer wygląda następująco (kopia z przeglądarki Chrome):

 

3.6. Akcja z wynikiem typu [JsonResult]

Dodajmy następującą akcję do kontrolera [First]:


    // Akcja03
    public JsonResult Action03()
    {
      dynamic personne = new ExpandoObject();
      personne.nom = "someone";
      personne.age = 20;
      return Json(personne,JsonRequestBehavior.AllowGet);
}
  • wiersz 4: zmienna typu dynamic. Podczas wykonywania można dowolnie dodawać właściwości do takiej zmiennej. Właściwość jest tworzona w momencie jej inicjalizacji;
  • wiersze 5–6: inicjuje się dwie właściwości nom i age;
  • wiersz 7: zwracamy reprezentację obiektu w formacie JSON (Javascript Object Notation). JSON umożliwia serializację obiektu do ciągu znaków oraz, odwrotnie, deserializację ciągu znaków do obiektu. Jest to alternatywa dla serializacji/deserializacji XML;
  • wiersz 2: akcja zwraca typ [JsonResult]. Typ ten może być zwrócony wyłącznie w odpowiedzi na żądanie POST. Jeśli chcemy zwrócić ten typ dla metody GET, należy podać jako drugi parametr konstruktora klasy Json (wiersz 7) wartość JsonRequestBehavior.AllowGet.

Po wysłaniu żądania URL [/First/Action03] przeglądarka wyświetla następujący komunikat:

 

Odpowiedź serwera HTTP wygląda natomiast następująco:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMw==?=
X-Powered-By: ASP.NET
Date: Mon, 23 Sep 2013 15:48:53 GMT
Content-Length: 58
  • wiersz 3: wskazuje, że wysłany dokument to JSON;
  • wiersz 10: wysłany dokument ma 58 bajtów. Jest to następujący fragment:
[{"Key":"nom","Value":"someone"},{"Key":"age","Value":20}]

Element dynamiczny [personne] jest postrzegany przez JSON jako tablica słowników, w której każdy słownik:

  • odpowiada polu zmiennej [personne];
  • posiada dwa klucze: „Key” i „Value”. Kluczowi „Key” przypisana jest nazwa pola, a kluczowi „Value” – wartość pola.

3.7. Akcja z wynikiem typu [string]

Dodajmy następującą akcję do kontrolera [First]:


    // Akcja04
    public string Action04()
    {
      return "<h3>Contrôleur=First, Action=Action04</h3>";
}

Gdy wywołujemy URL [/First/Action04] w przeglądarce Chrome, otrzymujemy następującą odpowiedź:

 

Widać, że tag <h3> został zinterpretowany. Przyjrzyjmy się odpowiedzi HTTP z serwera:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNA==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 07:49:00 GMT
Content-Length: 156

oraz następujący dokument:

<h3>Contrôleur=First, Action=Action04</h3>

W wierszu 3 widać, że serwer wskazał, iż wysyła tekst w formacie HTML. Dlatego przeglądarka zinterpretowała tag <h3>. Jeśli więc chcemy wysłać zwykły tekst, lepiej jest zwrócić kod [ContentResult] zamiast [string]. Typ [ContentResult] pozwala nam bowiem określić typ „text/plain”, aby zaznaczyć, że wysyłamy tekst nieformatowany, a zatem niepodlegający interpretacji przez przeglądarkę.

3.8. Akcja z wynikiem typu [EmptyResult]

Niech będzie to następująca nowa akcja:


    // Akcja05
    public EmptyResult Action05()
    {
      return new EmptyResult();
}

Akcja ta zwraca po prostu typ [EmptyResult]. W tym przypadku serwer wysyła do klienta pustą odpowiedź, jak pokazuje jego odpowiedź HTTP:

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Cache-Control: private
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNQ==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:11:12 GMT
Content-Length: 0
  • wiersz 9: serwer informuje klienta, że wysyła mu pusty dokument.

3.9. Akcja z wynikiem typu [RedirectResult] – 1

Niech nowa akcja będzie następująca:


    // Akcja06
    public RedirectResult Action06()
    {
      return new RedirectResult("/First/Action05");
}

Akcja zwraca typ [RedirectResult]. Typ ten pozwala wysłać do klienta polecenie przekierowania do parametru konstruktora URL (wiersz 4). Klient wyśle wówczas nowe żądanie o typie GET do [/First/Action05]. W sumie klient wysyła więc dwa żądania.

Przeglądarka wyświetla wynik drugiego żądania:

 

Przyjrzyjmy się odpowiedzi serwera HTTP w przeglądarce Chrome:

 

Powyżej widać oba żądania przeglądarki. Przyjrzyjmy się pierwszemu żądaniu [Action06]. Odpowiedź serwera HTTP wygląda następująco:

HTTP/1.1 302 Found
Cache-Control: private
Content-Type: text/html; charset=utf-8
Location: /First/Action05
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNg==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:16:59 GMT
Content-Length: 132
  • wiersz 1: serwer odpowiedział kodem 302 Found. Wcześniej był to kod 200 OK, co oznacza, że żądany dokument został znaleziony. Kod 302 wskazuje, że wymagane jest przekierowanie. Adres przekierowania podano w wierszu 4. Znajdujemy tam adres przekierowania, który podaliśmy w kodzie akcji;
  • wiersz 11: serwer informuje, że wraz z odpowiedzią HTTP wysyła dokument typu text/html (wiersz 3) o wielkości 132 bajtów (wiersz 11). Gdy sprawdzamy w przeglądarce Chrome odpowiedź na żądanie [Action06], jest ona pusta, jak można było się spodziewać. Prawdopodobnie istnieje jakieś wyjaśnienie, ale go nie znam.

Z powodu przekierowania przeglądarka wysyła nowe żądanie GET do adresu URL podanego w wierszu 4 powyżej, co widać w przeglądarce Chrome w wierszu 1 poniżej:

1
2
3
4
5
6
7
GET /First/Action05 HTTP/1.1
Host: localhost:49302
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0,8
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4

3.10. Akcja z wynikiem typu [RedirectResult] – 2

Niech nowa akcja będzie następująca:


    // Akcja07
    public RedirectResult Action07()
    {
      return new RedirectResult("/First/Action05",true);
}

W wierszu 4 dodano drugi parametr do konstruktora typu [RedirectResult]. Jest to wartość logiczna, której domyślną wartością jest false. Gdy ustawimy tę wartość na true, zmienia się odpowiedź HTTP wysyłana do klienta. Przyjmuje ona postać:

HTTP/1.1 301 Moved Permanently

W ten sposób kod odpowiedzi wysyłany do klienta to teraz 301 Moved Permanently. Przekierowanie przebiega tak samo jak poprzednio, ale zaznacza się, że jest to przekierowanie stałe. Pozwala to wyszukiwarkom na zastąpienie w wynikach wyszukiwania starego kodu URL nowym.

3.11. Akcja z wynikiem typu [RedirectToRouteResult]

Niech nowa akcja będzie następująca:


    // Akcja08
    public RedirectToRouteResult Action08()
    {
      return new RedirectToRouteResult("Default",new RouteValueDictionary(new {controller="First",action="Action05"}));
}
  • wiersz 2: akcja zwraca typ [RedirectToRouteResult]. Ten typ pozwala przekierować klienta do URL określonego nie za pomocą ciągu znaków, jak poprzednio, ale za pomocą ścieżki.

Trasy są zdefiniowane w [App_Start/RouteConfig]. Obecnie istnieje tylko jedna:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
  • W wierszu 4 klient jest proszony o przekierowanie się do trasy o nazwie [Default] z zmienną controller=First i zmienną action=Action05. System routingu wygeneruje wówczas trasę przekierowania URL z trasy /First/Action05. Pokazuje to odpowiedź serwera o numerze HTTP:
HTTP/1.1 302 Found
Cache-Control: private
Content-Type: text/html; charset=utf-8
Location: /First/Action05
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wOA==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:58:19 GMT
Content-Length: 132
  • wiersz 1: przekierowanie;
  • wiersz 4: adres przekierowania wygenerowany przez system routingu dla URL.

3.12. Akcja z wynikiem typu [void]

Niech nowa akcja będzie następująca:


    // Akcja09
    public void Action09()
    {
      string nom = Request.QueryString["nom"] ?? "inconnu";
      Response.AddHeader("Content-Type", "text/plain");
      Response.Write(string.Format("<h3>Action09</h3>nom={0}", nom));
}
  • wiersz 2: akcja nie zwraca żadnego wyniku. Sama zapisuje się w strumieniu odpowiedzi wysyłanej do klienta;
  • wiersz 4: pobieramy ewentualny parametr o nazwie [nom] z żądania. Dostęp do niego można uzyskać poprzez właściwość [Request] kontrolera [Controller], z którego dziedziczy kontroler [First]. Parametr [nom], przekazany w postaci [/First/Action09?nom=quelquechose], jest dostępny w Request.QueryString["nom"]. Składnia wiersza 4 jest równoważna następującej:
string nom=Request.QueryString["nom"];
if(nom==null){
    nom="inconnu";
}
  • wiersz 5: odpowiedź wysłana do klienta jest dostępna poprzez właściwość [Response] kontrolera [Controller], z którego dziedziczy kontroler [First];
  • wiersz 5: ustawia się nagłówek HTTP [Content-Type], który określa rodzaj dokumentu, który serwer zamierza wysłać do klienta. W tym przypadku „text/plain” oznacza, że dokument jest zwykłym tekstem, który nie powinien być interpretowany przez przeglądarkę;
  • wiersz 6: do strumienia odpowiedzi wpisuje się ciąg znaków. Zawiera on znaczniki HTML, które nie powinny być interpretowane przez przeglądarkę, ponieważ otrzymała ona wcześniej nagłówek HTTP [Content-Type : text/plain"]. Właśnie to chcemy sprawdzić.

Skompilujmy projekt i wywołajmy najpierw URL [/First/Action09?nom=someone ][1], a następnieURL [/First/Action09 ] [2]:

Przyjrzyjmy się teraz w przeglądarce Chrome odpowiedzi HTTP z serwera:

1
2
3
4
5
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
...
Content-Length: 144
  • wiersz 3: widzimy nagłówek HTTP, który sami ustaliliśmy w kodzie akcji.

3.13. Drugi kontroler

Utwórzmy w projekcie drugi kontroler. Postępujmy zgodnie z metodą opisaną w paragrafie 3.1, strona 40. Nazwiemy go [Second].

  

Wygenerowany kod wygląda następująco:


namespace Exemple_01.Controllers
{
  public class SecondController : Controller
  {
    //
    // GET: /Second/

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

  }
}

Zmodyfikujmy go w następujący sposób:


using System.Text;
using System.Web.Mvc;

namespace Exemple_01.Controllers
{
  public class SecondController : Controller
  {
    // /Sekunda/Akcja01
    public ContentResult Action01()
    {
      return Content("Contrôleur=Second, Action=Action01", "text/plain", Encoding.UTF8);
    }

  }
}

Następnie wywołajmy adres URL [/Second/Action01] w przeglądarce. Otrzymujemy następującą odpowiedź:

 

Ten kod URL został wywołany za pomocą polecenia HTTP GET, co pokazują logi HTTP dotyczące tego żądania w przeglądarce Chrome:

GET /Second/Action01 HTTP/1.1

URL może być również wywołany za pomocą polecenia HTTP POST. Aby to pokazać, ponownie skorzystajmy z aplikacji [Advanced Rest Client]:

  • w [1] uruchamiamy aplikację (w zakładce [Applications] w nowej karcie przeglądarki Chrome);
  • w [2] wybieramy opcję [Request];
  • w [3] należy określić żądaną wartość URL;
  • w [4] należy wskazać, że URL ma zostać zażądane wraz z POST;

Włączamy logi przeglądarki Chrome za pomocą (CTRL-I), aby uzyskać odpowiedź HTTP z serwera. Po wykonaniu [Send], poprzedzającego żądania, wymiana danych HTTP wygląda następująco:

Przeglądarka wysyła następujące żądanie:

POST /Second/Action01 HTTP/1.1
Host: localhost:49302
Connection: keep-alive
Content-Length: 0
Origin: chrome-extension://hgmloofddffdnphfgcellkdfbfbjeloo
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
  • wiersz 1: żądanie URL zostało faktycznie wysłane wraz z POST;
  • wiersz 4: rozmiar w bajtach wysłanych elementów. Tutaj ich nie ma.

Odpowiedź serwera HTTP wygląda następująco:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxTZWNvbmRcQWN0aW9uMDE=?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 10:47:59 GMT
Content-Length: 148
  • wiersz 3: serwer wysyła dokument tekstowy bez formatowania (plain);
  • wiersz 12: 148 znaków.

Wysłany dokument wygląda następująco:

 

Otrzymujemy ten sam dokument, co w przypadku GET.

3.14. Akcja filtrowana według atrybutu

Utwórzmy następującą nową akcję:


    // /Second/Action02
    [HttpPost]
    public ContentResult Action02()
    {
      return Content("Contrôleur=Second, Action=Action02", "text/plain", Encoding.UTF8);
}

Akcja [Action02] jest analogiczna do akcji [Action01], ale określa się, że jest ona dostępna wyłącznie poprzez polecenie HTTP POST (wiersz 2). Można użyć również innych atrybutów:

HttpGet
służy wyłącznie do polecenia GET
HttpHead
dotyczy wyłącznie polecenia HEAD
HttpOptions
służy wyłącznie do obsługi zamówienia OPTIONS
HttpPut
służy wyłącznie do obsługi zamówienia PUT
HttpDelete
obsługuje wyłącznie zamówienie DELETE

Wprowadźmy bezpośrednio w przeglądarce adresy URL i [/Second/Action02]. Zostaną one wówczas wywołane przez GET. Przeglądarka wyświetli wówczas następującą odpowiedź:

 

Odpowiedź serwera HTTP brzmiała następująco:

1
2
3
HTTP/1.1 404 Not Found
...
Content-Length: 3807
  • wiersz 1: kod HTTP 404 Not Found oznacza, że serwer nie znalazł żądanego dokumentu. W tym przypadku akcja [Action02] nie mogła obsłużyć żądania GET, ponieważ obsługuje ona wyłącznie polecenia POST;
  • wiersz 3: rozmiar zwróconego dokumentu. Jest to strona wyświetlona przez przeglądarkę:
 

3.15. Pobieranie elementów trasy

W obu opisanych powyżej akcjach zapisywaliśmy coś w rodzaju:


public ContentResult Action02()
    {
      return Content("Contrôleur=Second, Action=Action02", "text/plain", Encoding.UTF8);
}

Nazwy kontrolera i akcji były wpisane na stałe w kodzie. Jeśli zmienimy te nazwy, kod przestanie działać poprawnie. Dostęp do kontrolera i akcji można uzyskać w następujący sposób:


    // /Sekunda/Akcja03
    public ContentResult Action03()
    {
      string texte = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return Content(texte, "text/plain", Encoding.UTF8);
}

Trasa zdefiniowana w [App_Start/RouteConfig] wygląda następująco:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

W wierszu 3 trzy elementy trasy można uzyskać za pomocą RouteData.Values["élément"] z elementem w [controller, action, id].

Zaproś URL i [http://localhost:49302/Second/Action03]:

 

Udało nam się pobrać zarówno nazwę kontrolera, jak i nazwę akcji.