Skip to content

3. Контролери, дії, маршрутизація

Розглянемо архітектуру додатка ASP.NET MVC:

У цьому розділі ми розглянемо процес, завдяки якому запит [1] надходить до контролера та дії [2a], які його оброблять — цей механізм називається маршрутизацією. Крім того, ми розглянемо різні відповіді [3], які дія може надати браузеру. Це може бути щось інше, ніж представлення V [4b].

3.1. Структура проєкту ASP.NET MVC

Створимо перший проект ASP.NET MVC за допомогою Visual Studio Express 2012. Ми додамо його [1] до рішення, яке використовувалося в попередньому розділі:

  • в [2] — ім’я нового проєкту;
  • [3, 4] — ми обираємо базовий проект ASP.NET MVC. Цей шаблон надає нам порожній веб-додаток, однак із усіма ресурсами (DLL, бібліотеки JavaScript тощо), необхідними для роботи.

Отриманий проект представлено у файлі [5]. Ми зробимо його [6] стартовим проектом рішення:

У [5] слід звернути увагу на наступні моменти:

  • архітектура проекту відображає його модель MVC:
  • контролери C будуть розміщені в папці [Controllers],
  • моделі даних M будуть розміщені в папці [Models],
  • представлення V будуть розміщені в папці [Views],
  • у [1] файл [Site.css] стане файлом CSS за замовчуванням для додатка;
  • у [2] нам надається певна кількість бібліотек JavaScript;
  • у [3] містяться три особливі подання: _ViewStart, _Layout, Error.

Файл [_ViewStart] має такий вигляд:


@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}
  • рядок 1: символ @ позначає послідовність C# у подання. Дійсно, у подання можна включати код C#;
  • рядок 2: визначає змінну Layout, яка визначає батьківський вигляд для всіх інших виглядів. Вона відповідає головній сторінці класичного фреймворку ASP.NET.

Файл [_Layout] має такий вигляд:


<!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>

Коли буде відображатися представлення з папки [Views], його тіло буде відтворюватися за допомогою 11-го рядка вище. Це означає, що представлення не повинно містити теги <html>, <head>, <body>. Вони надаються вищезазначеним файлом. Наразі цей файл є досить незрозумілим. Спростимо його:


<!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>
  • рядок 6: загальний заголовок для всіх подань;
  • рядок 9: заголовок, спільний для всіх переглядів;
  • рядок 10: вміст, характерний для відображуваного виду.
  
  • [Web.config] — це файл конфігурації веб-додатка. Він є складним. Його доведеться змінювати під час використання фреймворку [Spring.net] у багатошаровій архітектурі.
  • [Global.asax] містить код, що виконується під час запуску додатка. Зазвичай цей код використовує різні конфігураційні файли додатка, зокрема [Web.config].

3.2. Маршрутизація за замовчуванням у URL

Наразі код [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()
    {
   ...
    }
  }
}
  • рядок 6, namespace класу. Він походить безпосередньо від назви проєкту та міститься у властивостях проєкту:

Image

У [1] ми встановлюємо простір імен за замовчуванням. Він буде використовуватися за замовчуванням для всіх класів, які будуть створені в проєкті.

Повернемося до коду класу [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()
    {
   ...
    }
  }
}
  • рядок 9: клас [MvcApplication] походить від класу [HttpApplication]. Ім’я [MvcApplication] можна змінити;
  • рядок 11: метод [Application_Start] — це метод, що виконується під час запуску веб-додатка. Він виконується лише один раз. Саме тут відбувається ініціалізація додатка.

Наразі код [Application_Start] виглядає так:


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

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

Наразі немає потреби розуміти цей код повністю. Рядки 5–8 визначають маршрути, які підтримує веб-додаток. Повернемося до архітектури додатка ASP.NET MVC:

Ми вже пояснювали, що [Front Controller] має перенаправити URL до дії, яка відповідає за її обробку. Маршрут слугує для встановлення зв’язку між шаблоном URL та дією. Ці маршрути визначаються у папці [App_Start] проекту за допомогою класів [WebApiConfig, FilterConfig, RouteConfig, BundleConfig]:

 

Наразі нас цікавить лише клас [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 }
      );
    }
  }
}

Рядки 12–16 визначають формат URL, який підтримує програма. Це називається маршрутом. Може існувати кілька можливих маршрутів (= кілька можливих шаблонів URL). Вони відрізняються між собою за назвою (рядок 13). Формат URL для маршруту визначено у рядку 14. У цьому випадку URL матиме три складові:

  • {controller}: ім’я класу, похідного від [Controller]. Його шукатимуть у папці [Controllers] проекту. За угодою, якщо URL — це /X/Y/Z, контролер, відповідальний за обробку цього URL, буде класом XController. До назви контролера, зазначеного в URL, додається суфікс Controller;
  • {action} — ім’я методу у зазначеному вище контролері. Саме цей метод отримає параметри, що супроводжують URL, і обробить їх. Цей метод може повертати різні результати:
    • void: дія самостійно сформує відповідь для браузера клієнта
    • String: дія повертає клієнту рядок символів;
    • ViewResult: повертає клієнту представлення;
    • PartialViewResult: повертає клієнту частковий вигляд;
    • EmptyResult: клієнту надсилається порожня відповідь;
    • RedirectResult: запитує клієнта перенаправити на URL
    • RedirectToRouteResult: те саме, але URL формується на основі маршрутів додатка;
    • JsonResult: надсилає відповідь JSON
    • JavaScriptResult: повертає код JavaScript клієнту;
    • ContentResult: повертає потік HTML клієнту, оминаючи представлення;
    • FileContentResult: повертає файл клієнту;
    • FileStreamResult: те саме, але іншим шляхом;
    • FilePathResult: ...
  • {id}: параметр, який буде передано до дії. Для цього дія повинна мати параметр із назвою id.

У рядку 15 визначено значення за замовчуванням на випадок, якщо URL не має очікуваного формату /{controller}/{action}/{id}. Також зазначено, що параметр {id} у URL є необов’язковим. Ось список неповних URL та URL, доповнений значеннями за замовчуванням:

Оригінальний URL
Заповнений файл URL
/
/Home/Index
/Do
/Do/Індекс
/Do/Something
/Зробити/Щось
/Зробити/Щось/4
/Зробити/Щось/4
/Зробити/Щось/x/y/z
URL без маршруту

3.3. Створення контролера та першої дії

Створимо перший контролер:

 
  • у [1] вкажіть назву контролера з суфіксом [Controller];
  • у [2] створіть порожній контролер MVC;
  • у [3] він був створений.

Код для [FirstController] такий:


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();
        }

    }
}
  • рядок 7: було згенеровано namespace за замовчуванням;
  • рядок 9: клас [FirstController] походить від класу [System.Web.Mvc.Controller];
  • рядки 14–17: за замовчуванням було згенеровано дію [Index]. Вона є публічною. Це важливо, інакше її не буде знайдено. Вона повертає тип [ActionResult], який є абстрактним класом, від якого походять більшість результатів, що зазвичай повертає дія. Це «універсальний» тип, який можна уточнити, замінивши його на реальну назву типу, що повертається;
  • рядок 16: метод нічого не робить. Він просто повертає представлення, тобто тип [ViewResult]. Назва представлення не вказана. У цьому випадку фреймворк шукає в папці [Views / First] вигляд із назвою дії, тобто в даному випадку: [Index.cshtml].

Створимо вигляд [Index.cshtml]:

  • у [1] клацаємо правою кнопкою миші в коді дії та вибираємо опцію [Ajouter une vue];
  • у [2] майстер пропонує подачу з назвою дії. Це саме те, що нам потрібно;
  • у [3] за замовчуванням пропонується використання шаблону [_Layout.cshtml];
  • у [4] після підтвердження майстер створює представлення у підпапці папки [Views], що має назву контролера (First).

Код, згенерований для подання [Index], має такий вигляд:


@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>
  • рядки 1–3: код на C#, що визначає змінну;
  • рядок 5: тег HTML.

Замінюємо весь попередній код на цей:


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

Підсумуємо:

  • у нас є контролер C під назвою [First];
  • у нас є дія з назвою [Index], яка вимагає відображення подання з назвою [Index];
  • у нас є вигляд V [Index].

Ми можемо викликати дію [Index] двома способами:

  • /First/Index;
  • /First, оскільки [Index] також є дією за замовчуванням у маршрутах.

Запустимо додаток (CTRL-F5). Ми отримаємо таку сторінку:

1

Image

У [1] запитувана URL мала вигляд http://localhost:49302. Шляху немає. Ми знаємо, що наш маршрутизатор очікує URL у формі /{controller}/{action}/{id}. Оскільки ці елементи відсутні, використовуються значення за замовчуванням. Запит URL перетворюється на http://localhost:49302/Home/Index. Контролер [Home] не існує. Тому запит URL відхиляється.

Тепер спробуємо URL http://localhost:49302/First/Index, ввівши його безпосередньо в браузері:

Наведена вище сторінка була згенерована дією [Index] контролера [First]. Сторінка, згенерована цією дією, є поданням [Index], код якого був таким:


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

Вона генерує частину [1]. Частина [2], у свою чергу, походить із головної сторінки [_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>
  @RenderBody()
</body>
</html>

У рядку 9 було згенеровано частину [2] сторінки. Вигляд [Index] з’являється лише в рядку 10.

Якщо переглянути вихідний код отриманої сторінки, можна побачити, що сторінка [Index] дійсно включена (рядок 10 нижче) у сторінку [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>

Тепер спробуємо URL та [/First]:

  

URL [/First] був неповним. Його було доповнено значеннями за замовчуванням для маршруту, і він перетворився на [/First/Index]. Отже, ми отримуємо той самий результат, що й раніше.

3.4. Дія з результатом типу [ContentResult] - 1

Створимо нову дію в контролері [First]:


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

namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
    // Індекс
    public ViewResult Index()
    {
      return View();
    }
    // Action01
    public ContentResult Action01()
    {
      return Content("<h1>Action [Action01]</h1>", "text/plain", Encoding.UTF8);
    }
  }
}

Нова дія визначена у рядках 14–18. Вона просто повертає рядок за допомогою методу [Content] (рядок 16) класу [Controller] (рядок 6). Параметри методу такі:

  1. рядок-відповідь;
  2. покажчик типу надісланого тексту: «text/plain», «text/html», «text/xml» тощо. Цей показник називається типом MIME (http://fr.wikipedia.org/wiki/Type_MIME);
  3. третій параметр дозволяє вказати тип кодування, що використовується для тексту.

Замість використання абстрактного типу [ActionResult] наші методи вказують фактичний тип, що відображається (рядки 9 та 14).

Запросімо URL [/First/Action01]. Отримаємо таку сторінку:

  

Розглянемо вихідний код отриманої сторінки:

<h1>Action [Action01]</h1>

Браузер отримав лише текст, надісланий акцією, і нічого більше. Цей режим є цікавим, коли потрібно отримати від веб-сервера «чисті» дані без оточуючого коду HTML. Зверніть увагу, що браузер не проінтерпретував тег <h1>. Щоб зрозуміти, чому, давайте подивимося в Chrome на обмін даними:

Браузер надіслав такі заголовки:

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

Сервер відповів йому такими заголовками:

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
  • рядок 3: визначає тип документа. Тут ми бачимо атрибути, задані в методі [Action01]. Саме тому, що браузеру було вказано, що документ має тип «text/plain», а не «text/html», він не проінтерпретував тег <h1>, який містився в отриманому документі.

3.5. Дія з результатом типу [ContentResult] - 2

Розглянемо наступну, третю дію:


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

namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
   ...
    // Дія02
    public ContentResult Action02()
    {
      string data = "<action><name>Action02</name><description>renvoie un texte XML</description></action>";
      return Content(data, "text/xml", Encoding.UTF8);
    }
  }
}
  • рядок 12: визначається текст XML;
  • рядок 13: його надсилають до браузера, вказавши, що це XML з типом MIME «text/xml».

У браузері відображається така сторінка:

 

Давайте подивимося в Chrome на відповідь HTTP від сервера:

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
  • рядок 3: визначає тип документа. Тут містяться атрибути, задані в методі [Action02];
  • рядок 12: розмір у байтах документа, надісланого сервером.

Документ, надісланий сервером, виглядає так (копія з Chrome):

 

3.6. Дія з результатом типу [JsonResult]

Додамо наступну дію до контролера [First]:


    // Дія03
    public JsonResult Action03()
    {
      dynamic personne = new ExpandoObject();
      personne.nom = "someone";
      personne.age = 20;
      return Json(personne,JsonRequestBehavior.AllowGet);
}
  • рядок 4: змінна типу dynamic. Під час виконання до такої змінної можна вільно додавати властивості. Властивість створюється одночасно з її ініціалізацією;
  • рядки 5–6: ініціалізуються дві властивості nom та age;
  • рядок 7: повертається представлення об’єкта у форматі JSON (Javascript Object Notation). JSON дозволяє серіалізувати об’єкт у рядок символів і, навпаки, десеріалізувати рядок у об’єкт. Це альтернатива серіалізації/десеріалізації XML;
  • рядок 2: дія повертає тип [JsonResult]. Цей тип може бути повернутий лише для запиту POST. Якщо потрібно повернути його для методу GET, необхідно вказати у другому параметрі конструктора класу Json (рядок 7) значення JsonRequestBehavior.AllowGet.

При запиті URL [/First/Action03] браузер відображає наступне:

 

Відповідь сервера на запит HTTP виглядає так:

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
  • рядок 3: вказує, що надісланий документ має формат JSON;
  • рядок 10: розмір надісланого документа становить 58 байт. Він має такий вигляд:
[{"Key":"nom","Value":"someone"},{"Key":"age","Value":20}]

Динамічний елемент [personne] сприймається JSON як масив словників, де кожен словник:

  • відповідає полю змінної [personne];
  • має два ключі: «Key» та «Value». Ключ «Key» має значення, пов’язане з назвою поля, а ключ «Value» — значення поля.

3.7. Дія з результатом типу [string]

Додамо таку дію до контролера [First]:


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

Коли ми запитуємо URL [/First/Action04] у браузері Chrome, отримуємо таку відповідь:

 

Бачимо, що тег <h3> було інтерпретовано. Розглянемо відповідь HTTP від сервера:

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

та наступний документ:

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

У третьому рядку видно, що сервер вказав надіслати текст у форматі HTML. Саме тому браузер інтерпретував тег <h3>. Отже, якщо потрібно надіслати чистий текст, краще повернути [ContentResult], а не [string]. А [ContentResult] дозволяє нам вказати тип MIME «text/plain», щоб позначити, що ми надсилаємо неформатований текст, який, отже, не підлягає інтерпретації з боку браузера.

3.8. Дія з результатом типу [EmptyResult]

Розглянемо таку нову дію:


    // Дія05
    public EmptyResult Action05()
    {
      return new EmptyResult();
}

Ця дія просто повертає тип [EmptyResult]. У цьому випадку сервер надсилає клієнту порожню відповідь, як показано в його відповіді 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
  • рядок 9: сервер повідомляє клієнту, що надсилає йому порожній документ.

3.9. Дія з результатом типу [RedirectResult] — 1

Розглянемо таку нову дію:


    // Дія06
    public RedirectResult Action06()
    {
      return new RedirectResult("/First/Action05");
}

Ця дія повертає тип [RedirectResult]. Цей тип дозволяє надіслати клієнту команду перенаправлення до параметра конструктора URL (рядок 4). Після цього клієнт надішле новий запит GET до [/First/Action05]. Отже, клієнт надсилає загалом два запити.

Браузер відображає результат другого запиту:

 

Розглянемо відповідь сервера HTTP у Chrome:

 

Вище наведено обидва запити браузера. Розглянемо перший запит [Action06]. Відповідь сервера 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?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNg==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:16:59 GMT
Content-Length: 132
  • рядок 1: сервер відповів кодом 302 Found. До цього моменту код був 200 OK, що означає, що запитуваний документ було знайдено. Код 302 вказує на те, що вимагається перенаправлення. Адреса перенаправлення вказана у рядку 4. Тут ми бачимо адресу перенаправлення, яку ми вказали в коді дії;
  • рядок 11: сервер вказує, що у своїй відповіді HTTP він надсилає документ типу text/html (рядок 3) розміром 132 байти (рядок 11). Якщо в Chrome переглянути відповідь на запит [Action06], вона, як і слід було очікувати, порожня. Ймовірно, цьому є пояснення, але я його не знаю.

Через перенаправлення браузер надсилає новий запит GET до URL, зазначеного у рядку 4 вище, як це видно в Chrome у рядку 1 нижче:

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. Дія з результатом типу [RedirectResult] - 2

Нехай нова дія виглядає так:


    // Дія07
    public RedirectResult Action07()
    {
      return new RedirectResult("/First/Action05",true);
}

У рядку 4 до конструктора типу [RedirectResult] додано другий параметр. Це логічне значення, яке за замовчуванням дорівнює false. Якщо його значення змінити на true, це змінить відповідь HTTP, що надсилається клієнту. Вона стане такою:

HTTP/1.1 301 Moved Permanently

Таким чином, код відповіді, що надсилається клієнту, тепер дорівнює 301 Moved Permanently. Перенаправлення відбувається так само, як і раніше, але вказується, що це перенаправлення є постійним. Це дозволяє пошуковим системам замінити у своїх результатах старий код URL на новий.

3.11. Дія з результатом типу [RedirectToRouteResult]

Розглянемо таку нову дію:


    // Дія08
    public RedirectToRouteResult Action08()
    {
      return new RedirectToRouteResult("Default",new RouteValueDictionary(new {controller="First",action="Action05"}));
}
  • рядок 2: дія повертає тип [RedirectToRouteResult]. Цей тип дозволяє перенаправити клієнта на URL, вказаний не за допомогою символьного рядка, як раніше, а за допомогою маршруту.

Маршрути визначені в [App_Start/RouteConfig]. Наразі існує лише один:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
  • у рядку 4 клієнту пропонується перенаправитися на маршрут із назвою [Default] із змінною controller=First та змінною action=Action05. Система маршрутизації згенерує маршрут перенаправлення URL до /First/Action05. Про це свідчить відповідь сервера 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
  • рядок 1: перенаправлення;
  • рядок 4: адреса перенаправлення, згенерована системою маршрутизації URL.

3.12. Дія з результатом типу [void]

Нехай це буде така нова дія:


    // Дія09
    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));
}
  • рядок 2: дія не повертає жодного результату. Вона сама записує дані у потік відповіді, що надсилається клієнту;
  • рядок 4: отримуємо можливий параметр із назвою [nom] у запиті. Доступ до нього здійснюється через властивість [Request] контролера [Controller], від якого успадковує контролер [First]. Параметр [nom], переданий у формі [/First/Action09?nom=quelquechose], доступний у Request.QueryString["nom"]. Синтаксис рядка 4 еквівалентний такому:
string nom=Request.QueryString["nom"];
if(nom==null){
    nom="inconnu";
}
  • рядок 5: відповідь, надіслана клієнту, доступна через властивість [Response] контролера [Controller], від якого успадковує контролер [First];
  • рядок 5: встановлюється заголовок HTTP [Content-Type], який вказує на тип документа, який сервер збирається надіслати клієнту. У даному випадку «text/plain» означає, що документ є простим текстом, який не повинен інтерпретуватися браузером;
  • рядок 6: у потік відповіді записується рядок символів. До нього включено теги HTML, які не повинні інтерпретуватися браузером, оскільки той раніше отримав заголовок HTTP [Content-Type : text/plain"]. Саме це ми й хочемо перевірити.

Скомпілюємо проєкт і надішлемо запит на URL [/First/Action09?nom=someone ][1], а потім наURL [/First/Action09 ] [2]:

Тепер подивимося в Chrome на відповідь HTTP від сервера:

1
2
3
4
5
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
...
Content-Length: 144
  • рядок 3: ми бачимо заголовок HTTP, який ми самі встановили в коді дії.

3.13. Другий контролер

Створимо в проєкті другий контролер. Дотримаємося методу, описаного в розділі 3.1, на сторінці 40. Назвемо його [Second].

  

Його згенерований код такий:


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

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

  }
}

Змінимо його наступним чином:


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

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

  }
}

Потім завантажимо URL [/Second/Action01] у браузері. Отримаємо таку відповідь:

 

Цей URL було запитано за допомогою команди HTTP GET, як показано в логах HTTP запиту в Chrome:

GET /Second/Action01 HTTP/1.1

URL також може бути запрошений за допомогою команд HTTP та POST. Щоб це продемонструвати, знову скористаємося додатком [Advanced Rest Client]:

  • у [1] запускаємо додаток (у вкладці [Applications] нової вкладки Chrome);
  • у [2] вибираємо опцію [Request];
  • у [3] вказуємо необхідну URL;
  • у [4] вказуємо, що URL має бути запитано разом із POST;

Увімкніть журнали Chrome за допомогою (CTRL-I), щоб отримати відповідь HTTP від сервера. При виконанні [Send], що є попереднім запитом, обмін даними HTTP відбувається наступним чином:

Браузер надсилає такий запит:

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
  • рядок 1: запит URL дійсно надсилається разом із POST;
  • рядок 4: розмір у байтах відправлених елементів. Тут їх немає.

Відповідь HTTP від сервера має такий вигляд:

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
  • рядок 3: сервер надсилає неформатований (plain) текстовий документ;
  • рядок 12: 148 символів.

Надісланий документ має такий вигляд:

 

Отримуємо той самий документ, що й у випадку з GET.

3.14. Дія, відфільтрована за атрибутом

Створимо таку нову дію:


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

Дія [Action02] аналогічна дії [Action01], але вказано, що вона доступна лише за допомогою команди HTTP POST (рядок 2). Можна використовувати й інші атрибути:

HttpGet
використовується лише з командою GET
HttpHead
використовується лише для команди HEAD
HttpOptions
використовується лише для команди OPTIONS
HttpPut
використовується лише для команди PUT
HttpDelete
працює лише з командою DELETE

Запросімо URL [/Second/Action02] безпосередньо у браузері. Тоді вона запитується за допомогою GET. Після цього браузер відображає таку відповідь:

 

Відповідь HTTP від сервера була такою:

1
2
3
HTTP/1.1 404 Not Found
...
Content-Length: 3807
  • рядок 1: код HTTP 404 Not Found вказує, що сервер не знайшов запитуваний документ. У цьому випадку дія [Action02] не змогла обробити запит GET, оскільки вона обробляє лише команди POST;
  • рядок 3: розмір повернутого документа. Це сторінка, яку відобразив браузер:
 

3.15. Отримання елементів маршруту

У двох описаних вище діях ми писали щось на кшталт:


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

Імена контролера та дії були жорстко вказані в коді. Якщо змінити ці імена, код перестане працювати. Отримати доступ до контролера та дії можна таким чином:


    // /Секунда/Дія03
    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);
}

Маршрут, визначений у [App_Start/RouteConfig], є таким:


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

У рядку 3 три елементи маршруту можна отримати за допомогою RouteData.Values["élément"] з елементом у [controller, action, id].

Запросимо URL та [http://localhost:49302/Second/Action03]:

 

Ми успішно отримали як ім’я контролера, так і назву дії.