Skip to content

3. Controller, azioni, routing

Consideriamo l'architettura di un'applicazione ASP.NET MVC:

In questo capitolo, esaminiamo il processo che instrada la richiesta [1] al controller e all'azione [2a] che la elaborerà, un meccanismo noto come routing. Presentiamo inoltre le varie risposte [3] che un'azione può restituire al browser. Queste possono essere diverse da una vista V [4b].

3.1. La struttura di un progetto ASP.NET MVC

Creiamo il nostro primo progetto ASP.NET MVC utilizzando Visual Studio Express 2012. Lo aggiungeremo [1] alla soluzione utilizzata nel capitolo precedente:

  • in [2], il nome del nuovo progetto;
  • in [3, 4], scegliamo un progetto ASP.NET MVC di base. Questo modello ci fornisce un'applicazione web vuota che include comunque tutte le risorse (DLL, librerie JavaScript, ecc.) necessarie per iniziare.

Il progetto risultante è mostrato in [5]. Lo imposteremo [6] come progetto iniziale della soluzione ( ):

Si notino i seguenti punti in [5]:

  • l'architettura del progetto riflette il suo modello MVC:
  • i controller C saranno collocati nella cartella [Controllers],
  • i modelli di dati M saranno collocati nella cartella [Models],
  • le viste V saranno collocate nella cartella [Views],
  • in [1], il file [Site.css] sarà il file CSS predefinito dell'applicazione;
  • in [2] sono disponibili diverse librerie JavaScript;
  • in [3], ci sono tre viste specifiche: _ViewStart, _Layout ed Error.

Il file [_ViewStart] è il seguente:


@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

  • Riga 1: Il carattere @ indica l'inizio di un blocco C# all'interno della vista. Infatti, il codice C# può essere incluso in una vista;
  • riga 2: definisce una variabile Layout che specifica la vista padre per tutte le viste. Corrisponde alla pagina master nel framework ASP.NET classico.

Il file [ _Layout ] è il seguente:


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

Quando viene visualizzata una vista dalla cartella [Views], il suo corpo verrà visualizzato dalla riga 11 sopra. Ciò significa che la vista non deve includere i tag <html>, <head> e <body>. Questi sono forniti dal file sopra. Per ora, questo file è piuttosto criptico. Semplifichiamolo:


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

  • riga 6: il titolo comune a tutte le viste;
  • riga 9: l'intestazione comune a tutte le viste;
  • riga 10: il contenuto specifico della vista visualizzata.

Image

  • [Web.config] è il file di configurazione dell'applicazione web. È complesso. Dovrai modificarlo quando utilizzi il framework [Spring.net] in un'architettura multilivello.
  • [Global.asax] contiene il codice eseguito all'avvio dell'applicazione. Generalmente, questo codice utilizza i vari file di configurazione dell'applicazione, incluso [Web.config].

3.2. Indirizzamento URL predefinito

Il codice in [Global.asax] è attualmente il seguente:


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()
    {
   ...
    }
  }
}

  • Riga 6: lo spazio dei nomi della classe. Deriva direttamente dal nome del progetto ed è elencato nelle proprietà del progetto:

Image

Impostiamo [1] come spazio dei nomi predefinito. Verrà quindi utilizzato per impostazione predefinita per tutte le classi che verranno create nel progetto.

Torniamo al codice in [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()
    {
   ...
    }
  }
}

  • riga 9: la classe [MvcApplication] deriva dalla classe [HttpApplication]. Il nome [MvcApplication] può essere modificato;
  • riga 11: il metodo [Application_Start] è il metodo eseguito all'avvio dell'applicazione web. Viene eseguito una sola volta. È qui che l'applicazione viene inizializzata.

Il codice per [Application_Start] è attualmente il seguente:


    protected void Application_Start()
    {
      AreaRegistration.RegisterAllAreas();
 
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
}

Per ora non è necessario comprendere tutto questo codice. Le righe 5–8 definiscono i percorsi accettati dall'applicazione web. Torniamo all'architettura di un'applicazione ASP.NET MVC:

Abbiamo spiegato che il [Front Controller] deve instradare un URL all'azione responsabile della sua gestione. Un percorso viene utilizzato per collegare un modello di URL a un'azione. Questi percorsi sono definiti nella cartella [App_Start] del progetto dalle classi [WebApiConfig, FilterConfig, RouteConfig, BundleConfig]:

Image

Per ora, ci interessa solo la classe [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 }
      );
    }
  }
}

Le righe 12–16 definiscono il formato degli URL accettati dall'applicazione. Questo è chiamato percorso. Possono esserci più percorsi possibili (= più modelli di URL possibili). Si distinguono l'uno dall'altro per il loro nome (riga 13). Il formato degli URL del percorso è definito alla riga 14. Qui, l'URL avrà tre componenti:

  • {controller}: il nome di una classe derivata da [Controller]. Verrà cercata nella cartella [Controllers] del progetto. Per convenzione, se l'URL è /X/Y/Z, il controller responsabile della gestione di questo URL sarà la classe XController. Il suffisso Controller viene aggiunto al nome del controller presente nell'URL;
  • {action}: il nome di un metodo nel controller specificato sopra. Questo metodo riceverà i parametri che accompagnano l'URL e li elaborerà. Questo metodo può restituire vari risultati:
    • void: l'azione costruirà la risposta al browser del cliente
    • String: l'azione restituisce una stringa al client;
    • ViewResult: restituisce una vista al client;
    • PartialViewResult: restituisce una vista parziale;
    • EmptyResult: al client viene inviata una risposta vuota;
    • RedirectResult: indica al client di reindirizzarsi a un URL
    • RedirectToRouteResult: come sopra, ma l'URL viene costruito a partire dai percorsi dell'applicazione;
    • JsonResult: invia una risposta JSON
    • JavaScriptResult: restituisce codice JavaScript al client;
    • ContentResult: restituisce un flusso HTML al client senza passare attraverso una vista;
    • FileContentResult: restituisce un file al client;
    • FileStreamResult: come sopra, ma tramite un metodo diverso;
    • FilePathResult: ...
  • {id}: un parametro che verrà passato all'azione. Affinché funzioni, l'azione deve avere un parametro denominato id.

La riga 15 definisce i valori predefiniti quando l'URL non ha il formato previsto /{controller}/{action}/{id}. Indica inoltre che il parametro {id} nell'URL è facoltativo. Ecco un elenco di URL incompleti e l'URL completato con i valori predefiniti:

URL originale
URL completato
/
/Home/Index
/Fare
/Fare/Indice
/Fare/Qualcosa
/Fare/Qualcosa
/Fare/Qualcosa/4
/Fare/Qualcosa/4
/Fai/Qualcosa/x/y/z
URL non instradato

3.3. Creazione di un controller e di una prima azione

Creiamo un primo controller:

Image

  • In [1], inserisci il nome del controller con il suffisso [Controller];
  • in [2], creare un controller MVC vuoto;
  • in [3], è stato creato.

Il codice per [FirstController] è il seguente:


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();
        }
 
    }
}
  • riga 7: è stato generato uno spazio dei nomi predefinito;
  • riga 9: la classe [FirstController] deriva dalla classe [System.Web.Mvc.Controller];
  • righe 14–17: è stata generata un'azione [Index] per impostazione predefinita. È pubblica. Questo è importante; altrimenti, non verrà trovata. Restituisce un tipo [ActionResult], che è una classe astratta da cui derivano la maggior parte dei risultati tipicamente restituiti da un'azione. Si tratta di un tipo "catch-all" che può essere specificato sostituendolo con il nome effettivo del tipo restituito;
  • Riga 16: il metodo non fa nulla. Restituisce semplicemente una vista, ovvero un tipo [ViewResult]. Il nome della vista non è specificato. In questo caso, il framework cerca nella cartella [Views/First] una vista con lo stesso nome dell'azione, che in questo caso è [Index.cshtml].

Creiamo la vista [Index.cshtml]:

  • in [1], fare clic con il pulsante destro del mouse sul codice dell'azione e selezionare l'opzione [Aggiungi vista];
  • in [2], la procedura guidata suggerisce una vista con lo stesso nome dell'azione. È proprio quello che vogliamo in questo caso;
  • in [3], per impostazione predefinita, viene suggerito l'uso della pagina master [_Layout.cshtml];
  • in [4], una volta confermata, la procedura guidata crea la vista in una sottocartella della cartella [Views] che prende il nome dal controller (First).

Il codice generato per la vista [Index] è il seguente:


@{
    ViewBag.Title = "Index";
}
 
<h2>Index</h2>
  • Righe 1–3: codice C# che definisce una variabile;
  • riga 5: un tag HTML.

Sostituisci tutto il codice precedente con questo:


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

Riassumendo:

  • abbiamo un controller C# chiamato [First];
  • abbiamo un'azione chiamata [Index] che richiede la visualizzazione di una vista chiamata [Index];
  • abbiamo la vista V [Index].

Possiamo chiamare l'azione [Index] in due modi:

  • /First/Index;
  • /First, poiché [Index] è anche l'azione predefinita nelle rotte.

Eseguiamo l'applicazione (CTRL-F5). Otteniamo la seguente pagina:

Image

In [1], l'URL richiesto era http://localhost:49302. Manca il percorso. Sappiamo che il nostro router si aspetta URL nella forma /{controller}/{action}/{id}. Poiché questi elementi mancano, vengono utilizzati i valori predefiniti. L'URL diventa http://localhost:49302/Home/Index. Il controller [Home] non esiste. Pertanto, l'URL viene rifiutato.

Ora proviamo l'URL http://localhost:49302/First/Index digitandolo direttamente nel browser:

La pagina sopra è stata generata dall'azione [Index] del controller [First]. La pagina generata da questa azione è la vista [Index], il cui codice era il seguente:


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

Genera la parte [1]. La parte [2], invece, proviene dalla pagina master [_Layout] che abbiamo definito in precedenza:


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

La riga 9 ha generato la sezione [2] della pagina. La vista [Index], tuttavia, appare solo alla riga 10.

Se visualizziamo il codice sorgente della pagina ricevuta, possiamo vedere che la pagina [Index] è effettivamente inclusa (riga 10 qui sotto) nella pagina [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>

Ora proviamo l'URL [/First]:

Image

L'URL [/First] era incompleto. È stato completato con i valori predefiniti del percorso ed è diventato [/First/Index]. Otteniamo quindi lo stesso risultato di prima.

3.4. Azione con un risultato di tipo [ContentResult] - 1

Creiamo una nuova azione nel controller [First]:


using System.Text;
using System.Web.Mvc;
 
namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
    // Index
    public ViewResult Index()
    {
      return View();
    }
    // Action01
    public ContentResult Action01()
    {
      return Content("<h1>Action [Action01]</h1>", "text/plain", Encoding.UTF8);
    }
  }
}

La nuova azione è definita nelle righe 14–18. Restituisce semplicemente una stringa utilizzando il metodo [Content] (riga 16) della classe [Controller] (riga 6). I parametri del metodo sono:

  1. la stringa di risposta;
  2. un indicatore del tipo di testo inviato: "text/plain", "text/html", "text/xml", ecc. Questo indicatore è chiamato tipo MIME (http://fr.wikipedia.org/wiki/Type_MIME);
  3. il terzo parametro specifica il tipo di codifica utilizzato per il testo.

Anziché utilizzare il tipo astratto [ActionResult], i nostri metodi specificano il tipo di ritorno effettivo (righe 9 e 14).

Richiediamo l'URL [/First/Action01]. Otteniamo la seguente pagina:

Image

Diamo un'occhiata al codice sorgente della pagina ricevuta:

<h1>Action [Action01]</h1>

Il browser ha ricevuto solo il testo inviato dall'azione e nient'altro. Questa modalità è utile quando si desidera richiedere dati grezzi dal server web senza il markup HTML circostante. Si noti che il browser non ha interpretato il tag HTML <h1>. Per capire perché, diamo un'occhiata agli scambi HTTP in Chrome:

Il browser ha inviato le seguenti intestazioni HTTP:

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

Il server ha risposto con le seguenti intestazioni:

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
  • Riga 3: imposta il tipo di documento. Vediamo gli attributi impostati nel metodo [Action01]. È perché al browser è stato comunicato che il documento era di tipo "text/plain" e non "text/html" che non ha interpretato il tag <h1> presente nel documento ricevuto.

3.5. Azione con un risultato di tipo [ContentResult] - 2

Consideriamo la seguente terza azione:


using System.Text;
using System.Web.Mvc;
 
namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
   ...
    // Action02
    public ContentResult Action02()
    {
      string data = "<action><name>Action02</name><description>renvoie un texte XML</description></action>";
      return Content(data, "text/xml", Encoding.UTF8);
    }
  }
}
  • riga 12: definiamo un testo XML;
  • riga 13: lo inviamo al browser, specificando che si tratta di XML con il tipo MIME "text/xml".

Nel browser, otteniamo la seguente pagina:

Image

Diamo un'occhiata alla risposta HTTP del server in Chrome:

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
  • Riga 3: specifica il tipo di documento. Qui sono inclusi gli attributi impostati nel metodo [Action02];
  • Riga 12: La dimensione in byte del documento inviato dal server.

Il documento inviato dal server è questo (screenshot di Chrome):

Image

3.6. Azione con un risultato di tipo [JsonResult]

Aggiungiamo la seguente azione al controller [First]:


    // Action03
    public JsonResult Action03()
    {
      dynamic personne = new ExpandoObject();
      personne.nom = "someone";
      personne.age = 20;
      return Json(personne,JsonRequestBehavior.AllowGet);
}
  • riga 4: una variabile di tipo dynamic. In fase di esecuzione, è possibile aggiungere liberamente proprietà a tale variabile. La proprietà viene creata nel momento stesso in cui viene inizializzata;
  • righe 5-6: inizializziamo due proprietà, nome ed età;
  • riga 7: restituisce la rappresentazione JSON (JavaScript Object Notation) dell'oggetto. JSON consente di serializzare un oggetto in una stringa e, viceversa, di deserializzare una stringa in un oggetto. È un'alternativa alla serializzazione/deserializzazione XML;
  • riga 2: l'azione restituisce un tipo [JsonResult]. Questo tipo può essere restituito solo per una richiesta POST. Se si desidera restituirlo per un metodo GET, è necessario impostare il secondo parametro del costruttore della classe Json (riga 7) su JsonRequestBehavior.AllowGet.

Quando si richiede l'URL [/First/Action03], il browser visualizza quanto segue:

Image

La risposta HTTP del server è la seguente:

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
  • Riga 3: specifica che il documento inviato è in formato JSON;
  • riga 10: il documento inviato è di 58 byte. È il seguente:
[{"Key":"nom","Value":"someone"},{"Key":"age","Value":20}]

L'elemento dinamico [person] viene interpretato da JSON come un array di dizionari in cui ogni dizionario:

  • corrisponde a un campo della variabile [person];
  • ha due chiavi, "Key" e "Value". "Key" ha come valore associato il nome del campo, mentre "Value" ha il valore del campo.

3.7. Azione con un risultato [string]

Aggiungiamo la seguente azione al controller [First]:


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

Quando richiediamo l'URL [/First/Action04] in Chrome, otteniamo la seguente risposta:

Image

Possiamo notare che il tag <h3> è stato interpretato. Diamo un'occhiata alla risposta HTTP del server:

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

e il seguente documento:

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

Come si vede alla riga 3, il server ha indicato che stava inviando testo in formato HTML. Questo è il motivo per cui il browser ha interpretato il tag <h3>. Quando si desidera inviare testo semplice, è quindi preferibile restituire un [ContentResult] piuttosto che una [string]. Il [ContentResult] ci permette di specificare un tipo MIME "text/plain" per indicare che stiamo inviando testo non formattato, che il browser non tenterà di interpretare.

3.8. Azione con un tipo [EmptyResult]

Consideriamo la seguente nuova azione:


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

L'azione restituisce semplicemente un tipo [EmptyResult]. In questo caso, il server invia una risposta vuota al client, come mostrato nella sua risposta 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
  • Riga 9: Il server informa il proprio client che sta inviando un documento vuoto.

3.9. Azione con un risultato di tipo [RedirectResult] - 1

Si consideri la seguente nuova azione:


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

L'azione restituisce un tipo [RedirectResult]. Questo tipo consente di inviare al client una richiesta di reindirizzamento all'URL specificato nel costruttore (riga 4). Il client invierà quindi una nuova richiesta GET a [/First/Action05]. Il client effettua quindi due richieste in totale.

Il browser visualizza il risultato della seconda richiesta:

Image

Esaminiamo la risposta HTTP del server in Chrome:

Image

Sopra vediamo le due richieste del browser. Esaminiamo la prima richiesta [Action06]. La risposta HTTP del server è la seguente:

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
  • Riga 1: Il server ha risposto con un codice di stato 302 Found. In precedenza era 200 OK, il che significa che il documento richiesto è stato trovato. Il codice 302 indica che è richiesto un reindirizzamento. L'URL di reindirizzamento è fornito alla riga 4. Questo corrisponde all'URL di reindirizzamento che abbiamo specificato nel codice dell'azione;
  • Riga 11: Il server indica che, con la sua risposta HTTP, sta inviando un documento text/html (riga 3) di 132 byte (riga 11). Quando esaminiamo la risposta alla richiesta [Action06] in Chrome, è vuota, come previsto. Probabilmente c'è una spiegazione, ma non so quale sia.

A causa del reindirizzamento, il browser effettua una nuova richiesta GET all'URL specificato nella riga 4 sopra, come si può vedere in Chrome alla riga 1 qui sotto:

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. Azione con un risultato di tipo [RedirectResult] - 2

Si consideri la seguente nuova azione:


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

Alla riga 4, abbiamo aggiunto un secondo parametro al costruttore [RedirectResult]. Si tratta di un valore booleano il cui valore predefinito è false. Se impostato su true, modifica la risposta HTTP inviata al client. Diventa:

HTTP/1.1 301 Moved Permanently

Pertanto, il codice di risposta inviato al client è ora 301 Moved Permanently. Il reindirizzamento avviene come prima, ma indichiamo che questo reindirizzamento è permanente. Ciò consente ai motori di ricerca di sostituire il vecchio URL con quello nuovo nei loro risultati.

3.11. Azione con un risultato di tipo [RedirectToRouteResult]

Consideriamo la seguente nuova azione:


    // Action08
    public RedirectToRouteResult Action08()
    {
      return new RedirectToRouteResult("Default",new RouteValueDictionary(new {controller="First",action="Action05"}));
}
  • Riga 2: L'azione restituisce un tipo [RedirectToRouteResult]. Questo tipo consente di reindirizzare un client a un URL specificato, non tramite una stringa come in precedenza, ma tramite un percorso.

I percorsi sono definiti in [App_Start/RouteConfig]. Attualmente ce n'è solo uno:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
  • Alla riga 4, al client viene indicato di reindirizzarsi alla route denominata [Default] con la variabile controller impostata su "First" e la variabile action impostata su "Action05". Il sistema di routing genererà quindi l'URL di reindirizzamento /First/Action05. Ciò è mostrato nella risposta HTTP del server:
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
  • riga 1: il reindirizzamento;
  • riga 4: l'URL di reindirizzamento generato dal sistema di routing degli URL.

3.12. Azione con tipo di ritorno [void]

Consideriamo la seguente nuova azione:


    // Action09
    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));
}
  • riga 2: l'azione non restituisce alcun risultato. Scrive direttamente nel flusso di risposta inviato al client;
  • riga 4: recuperiamo un potenziale parametro denominato [name] dalla richiesta. È accessibile tramite la proprietà [Request] della classe [Controller] da cui il controller [First] eredita. Il parametro [name] passato nel modulo [/First/Action09?name=something] è disponibile in Request.QueryString["name"]. La sintassi della riga 4 è equivalente a:
string nom=Request.QueryString["nom"];
if(nom==null){
    nom="inconnu";
}
  • riga 5: la risposta inviata al client è accessibile tramite la proprietà [Response] della classe [Controller] da cui il controller [First] eredita;
  • riga 5: impostiamo l'intestazione HTTP [Content-Type], che indica la natura del documento che il server sta per inviare al client. Qui, "text/plain" indica che il documento è testo semplice che non deve essere interpretato dal browser;
  • riga 6: inseriamo una stringa di caratteri nel corpo della risposta. Abbiamo incluso dei tag HTML che non dovrebbero essere interpretati dal browser, poiché quest'ultimo avrà già ricevuto l'intestazione HTTP [Content-Type: text/plain]. È proprio questo che vogliamo verificare.

Compiliamo il progetto e richiediamo l'URL [/First/Action09?name=someone ][1] e poi l'URL [/First/Action09 ] [2]:

Ora diamo un'occhiata alla risposta HTTP del server in Chrome:

1
2
3
4
5
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
...
Content-Length: 144
  • riga 3: vediamo l'intestazione HTTP che abbiamo impostato noi stessi nel codice dell'azione.

3.13. Un secondo controller

Creiamo un secondo controller nel progetto. Seguiremo il metodo descritto nella sezione 3.1, a pagina 36. Lo chiameremo [Second].

Image

Il codice generato è il seguente:


namespace Exemple_01.Controllers
{
  public class SecondController : Controller
  {
    //
    // GET: /Second/
 
    public ActionResult Index()
    {
      return View();
    }
 
  }
}

Modifichiamolo come segue:


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

Quindi richiediamo l'URL [/Second/Action01] utilizzando un browser. Otteniamo la seguente risposta:

Image

Questo URL è stato richiesto utilizzando una richiesta HTTP GET, come mostrato nei log HTTP relativi alla richiesta in Chrome:

GET /Second/Action01 HTTP/1.1

L'URL può essere richiesto anche tramite una richiesta HTTP POST. Per dimostrarlo, utilizziamo nuovamente l'app [Advanced Rest Client]:

  • In [1], avvia l'applicazione (nella scheda [Applicazioni] di una nuova scheda di Chrome);
  • in [2], seleziona l'opzione [Request];
  • in [3], specifica l'URL richiesto;
  • in [4], specificare che l'URL deve essere richiesto utilizzando una richiesta POST;

Attiviamo i log di Chrome (CTRL-I) per visualizzare la risposta HTTP del server. Quando clicchiamo su [Invia] per eseguire la richiesta precedente, lo scambio di dati HTTP è il seguente:

Il browser invia la seguente richiesta:

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
  • riga 1: l'URL viene effettivamente richiesto con un POST;
  • Riga 4: La dimensione in byte degli elementi inviati. Qui non ce ne sono.

La risposta HTTP del server è la seguente:

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
  • riga 3: il server invia un documento di testo non formattato (semplice);
  • riga 12: 148 caratteri.

Il documento inviato è il seguente:

Image

Otteniamo lo stesso documento della richiesta GET.

3.14. Azione filtrata da un attributo

Creiamo la seguente nuova azione:


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

L'azione [Action02] è simile all'azione [Action01], ma specifica che è accessibile solo tramite una richiesta HTTP POST (riga 2). È possibile utilizzare altri attributi:

HttpGet
gestisce solo la richiesta GET
HttpHead
gestisce solo la richiesta HEAD
HttpOptions
supporta solo il comando OPTIONS
HttpPut
supporta solo il comando PUT
HttpDelete
viene utilizzato solo per il comando DELETE

Richiediamo l'URL [/Second/Action02] direttamente nel browser. Viene quindi richiesto tramite un GET. Il browser visualizza quindi la seguente risposta:

Image

La risposta HTTP del server era la seguente:

1
2
3
HTTP/1.1 404 Not Found
...
Content-Length: 3807
  • Riga 1: Il codice HTTP 404 Not Found indica che il server non è riuscito a trovare il documento richiesto. In questo caso, l'azione [Action02] non è riuscita a gestire la richiesta GET perché gestisce solo richieste POST;
  • Riga 3: La dimensione del documento restituito. Questa è la pagina che è stata visualizzata dal browser:

Image

3.15. Recupero di elementi da una route

Nelle due azioni descritte sopra, abbiamo scritto qualcosa del tipo:


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

I nomi del controller e dell'azione sono stati hard-coded nel codice. Se modifichiamo questi nomi, il codice non sarà più corretto. Possiamo accedere al controller e all'azione come segue:


    // /Second/Action03
    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);
}

Il percorso definito in [App_Start/RouteConfig] è il seguente:


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

Riga 3: I tre elementi del percorso possono essere recuperati utilizzando RouteData.Values["element"], dove element è uno tra [controller, action, id].

Richiediamo l'URL [http://localhost:49302/Second/Action03]:

Image

Abbiamo recuperato con successo sia il nome del controller che quello dell'azione.