Skip to content

3. Controladores, acciones, enrutamiento

Analicemos la arquitectura de una aplicación ASP.NET MVC:

En este capítulo, analizaremos el proceso que lleva la solicitud [1] al controlador y a la acción [2a] que la procesarán, un mecanismo que se denomina enrutamiento. Además, presentamos las diferentes respuestas [3] que una acción puede enviar al navegador. Puede tratarse de algo distinto a una vista V [4b].

3.1. La estructura de un proyecto ASP.NET MVC

Creemos un primer proyecto ASP.NET MVC con Visual Studio Express 2012. Lo añadiremos [1] a la solución utilizada en el capítulo anterior:

  • en [2], el nombre del nuevo proyecto;
  • en [3, 4], elegimos un proyecto base ASP.NET MVC. Esta plantilla nos proporciona una aplicación web vacía, pero con todos los recursos (DLL, bibliotecas de JavaScript, etc.) necesarios para trabajar.

El proyecto resultante se muestra en [5]. Lo convertiremos en [6], el proyecto inicial de la solución:

En [5] se observarán los siguientes puntos:

  • la arquitectura del proyecto refleja su modelo MVC:
  • los controladores C se colocarán en la carpeta [Controllers],
  • los modelos de datos M se colocarán en la carpeta [Models],
  • las vistas V se colocarán en la carpeta [Views],
  • en [1], el archivo [Site.css] será el archivo CSS por defecto de la aplicación;
  • en [2], se ponen a nuestra disposición varias bibliotecas de JavaScript;
  • en [3], encontramos tres vistas concretas: _ViewStart, _Layout y Error.

El archivo [_ViewStart] es el siguiente:


@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}
  • línea 1: el carácter @ indica una secuencia de C# en la vista. De hecho, se puede incluir código C# en una vista;
  • línea 2: define una variable Layout que establece la vista principal de todas las vistas. Corresponde a la página maestra del framework clásico ASP.NET.

El archivo [_Layout] es el siguiente:


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

Cuando se muestre una vista de la carpeta [Views], su cuerpo se generará mediante la línea 11 anterior. Esto significa que la vista no tiene que incluir las etiquetas <html>, <head> ni <body>. Estas las proporciona el archivo anterior. Por el momento, este es bastante hermético. Simplifiquémoslo:


<!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>
  • línea 6: el título común a todas las vistas;
  • línea 9: el encabezado común a todas las vistas;
  • línea 10: el contenido específico de la vista mostrada.
  
  • [Web.config] es el archivo de configuración de la aplicación web. Es complejo. Habrá que modificarlo cuando se utilice el marco [Spring.net] en una arquitectura multicapa.
  • [Global.asax] contiene el código que se ejecuta al iniciar la aplicación. Por lo general, este código utiliza los distintos archivos de configuración de la aplicación, entre ellos [Web.config].

3.2. El enrutamiento por defecto de URL

El código de [Global.asax] es, por el momento, el siguiente:


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()
    {
   ...
    }
  }
}
  • línea 6, el namespace de la clase. Proviene directamente del nombre del proyecto y aparece en las propiedades del proyecto:

Image

Establecemos en [1] el espacio de nombres por defecto. A partir de ese momento, se utilizará por defecto para todas las clases que se creen en el proyecto.

Volvamos al código de [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()
    {
   ...
    }
  }
}
  • línea 9: la clase [MvcApplication] deriva de la clase [HttpApplication]. El nombre [MvcApplication] se puede cambiar;
  • línea 11: el método [Application_Start] es el método que se ejecuta al iniciar la aplicación web. Solo se ejecuta una vez. Es aquí donde se inicializa la aplicación.

El código de [Application_Start] es actualmente el siguiente:


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

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

Por ahora, no es necesario comprender todo este código. Las líneas 5-8 definen las rutas aceptadas por la aplicación web. Volvamos a la arquitectura de una aplicación ASP.NET MVC:

Ya hemos explicado que el [Front Controller] debe redirigir un URL hacia la acción encargada de procesarlo. Una ruta sirve para establecer el vínculo entre un modelo de URL y una acción. Estas rutas se definen en la carpeta [App_Start] del proyecto mediante las clases [WebApiConfig, FilterConfig, RouteConfig, BundleConfig]:

 

Por el momento, solo nos interesa la clase [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 }
      );
    }
  }
}

Las líneas 12-16 definen el formato de los URL aceptados por la aplicación. A esto se le denomina «ruta». Puede haber varias rutas posibles (= varios formatos de URL posibles). Se distinguen entre sí por su nombre (línea 13). El formato de los URL de la ruta se define en la línea 14. En este caso, el URL tendrá tres componentes:

  • {controller}: el nombre de una clase derivada de [Controller]. Se buscará en la carpeta [Controllers] del proyecto. Por convención, si el URL es /X/Y/Z, el controlador encargado de procesar este URL será la clase XController. El sufijo «Controller» se añade al nombre del controlador presente en el URL;
  • {action}: el nombre de un método del controlador indicado anteriormente. Es este método el que recibirá los parámetros que acompañan al URL y los procesará. Este método puede devolver diversos resultados:
    • void: la acción generará por sí misma la respuesta al navegador del cliente
    • String: la acción devuelve al cliente una cadena de caracteres;
    • ViewResult: devuelve una vista al cliente;
    • PartialViewResult: devuelve una vista parcial;
    • EmptyResult: se envía una respuesta vacía al cliente;
    • RedirectResult: solicita al cliente que se redirija a una URL
    • RedirectToRouteResult: lo mismo, pero la URL se construye a partir de las rutas de la aplicación;
    • JsonResult: envía una respuesta JSON
    • JavaScriptResult: devuelve un código JavaScript al cliente;
    • ContentResult: devuelve un flujo HTML al cliente sin pasar por una vista;
    • FileContentResult: devuelve un archivo al cliente;
    • FileStreamResult: lo mismo, pero por otra vía;
    • FilePathResult: ...
  • {id}: un parámetro que se transmitirá a la acción. Para ello, la acción deberá tener un parámetro denominado «id».

La línea 15 define valores por defecto cuando el URL no tiene el formato esperado /{controller}/{action}/{id}. También indica que el parámetro {id} en el URL es opcional. A continuación se muestra una lista de URL incompletas y la URL completada con los valores por defecto:

URL original
URL completado
/
/Inicio/Índice
/Do
/Do/Índice
/Do/Something
/Hacer/Algo
/Hacer/Algo/4
/Hacer/Algo/4
/Hacer/Algo/x/y/z
URL sin enrutar

3.3. Creación de un controlador y una primera acción

Creemos un primer controlador:

 
  • en [1], introduce el nombre del controlador con su sufijo [Controller];
  • en [2], crea un controlador MVC vacío;
  • en [3], ya se ha creado.

El código de [FirstController] es el siguiente:


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

    }
}
  • línea 7: se ha generado un namespace por defecto;
  • línea 9: la clase [FirstController] deriva de la clase [System.Web.Mvc.Controller];
  • líneas 14-17: se ha generado una acción [Index] por defecto. Es pública. Esto es importante, ya que, de lo contrario, no se encontrará. Devuelve un tipo [ActionResult], que es una clase abstracta de la que derivan la mayoría de los resultados que suele devolver una acción. Se trata de un tipo «genérico» que se puede especificar sustituyéndolo por el nombre real del tipo devuelto;
  • línea 16: el método no hace nada. Se limita a devolver una vista, es decir, un tipo [ViewResult]. No se especifica el nombre de la vista. En este caso, el framework busca en la carpeta [Views / First] una vista con el nombre de la acción, es decir, en este caso: [Index.cshtml].

Creemos la vista [Index.cshtml]:

  • en [1], hacemos clic con el botón derecho en el código de la acción y seleccionamos la opción [Ajouter une vue];
  • en [2], el asistente propone una vista con el nombre de la acción. Eso es lo que queremos aquí;
  • en [3], por defecto se propone el uso de la página maestra [_Layout.cshtml];
  • en [4], una vez validado, el asistente crea la vista en una subcarpeta de la carpeta [Views] con el nombre del controlador (First).

El código generado para la vista [Index] es el siguiente:


@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>
  • líneas 1-3: código C# que define una variable;
  • línea 5: una etiqueta HTML.

Se sustituye todo el código anterior por este:


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

Resumamos:

  • tenemos un controlador C llamado [First];
  • tenemos una acción llamada [Index] que solicita la visualización de una vista llamada [Index];
  • tenemos la vista V [Index].

Podemos llamar a la acción [Index] de dos maneras:

  • /First/Index;
  • /First, ya que [Index] es también la acción por defecto en las rutas.

Ejecutemos la aplicación (CTRL-F5). Obtenemos la siguiente página:

1

Image

En [1], la acción solicitada fue http://localhost:49302. No hay ruta. Sabemos que nuestro enrutador espera acciones de la forma /{controller}/{action}/{id}. Al faltar estos elementos, se utilizan los valores por defecto. La URL URL se convierte en http://localhost:49302/Home/Index. El controlador [Home] no existe. Por lo tanto, la URL URL es rechazada.

Probemos ahora con el URL http://localhost:49302/First/Index escribiéndolo directamente en el navegador:

La página anterior ha sido generada por la acción [Index] del controlador [First]. La página generada por esta acción es la vista [Index], cuyo código era el siguiente:


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

Esta genera la parte [1]. Por su parte, la parte [2] procede de la página maestra [_Layout] que hemos definido anteriormente:


<!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 línea 9 ha generado la parte [2] de la página. La vista [Index], por su parte, solo interviene en la línea 10.

Si mostramos el código fuente de la página recibida, vemos que la página [Index] está incluida (línea 10 a continuación) en la página [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>

Probemos ahora con URL y [/First]:

  

El archivo URL [/First] estaba incompleto. Se ha completado con los valores por defecto de la ruta y ha pasado a ser [/First/Index]. Por lo tanto, se obtiene el mismo resultado que anteriormente.

3.4. Acción con un resultado de tipo [ContentResult] - 1

Creemos una nueva acción en el controlador [First]:


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

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

La nueva acción se define en las líneas 14-18. Se limita a devolver una cadena de caracteres mediante el método [Content] (línea 16) de la clase [Controller] (línea 6). Los parámetros del método son:

  1. la cadena de caracteres de la respuesta;
  2. un indicador del tipo de texto enviado: «text/plain», «text/html», «text/xml», etc. Este indicador se denomina tipo MIME (http://fr.wikipedia.org/wiki/Type_MIME);
  3. el tercer parámetro permite especificar el tipo de codificación utilizado para el texto.

En lugar de utilizar el tipo abstracto [ActionResult], nuestros métodos especifican el tipo real resultante (líneas 9 y 14).

Solicitemos el URL [/First/Action01]. Obtenemos la siguiente página:

  

Echemos un vistazo al código fuente de la página recibida:

<h1>Action [Action01]</h1>

El navegador solo ha recibido el texto enviado por la acción y nada más. Este modo resulta interesante cuando se desea solicitar al servidor web datos sin formato, sin el envoltorio HTML que los rodea. Cabe destacar que, en el ejemplo anterior, el navegador no ha interpretado la etiqueta <h1>. Para entender por qué, echemos un vistazo en Chrome a los intercambios:

El navegador ha enviado los siguientes encabezados:

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

El servidor le respondió con los siguientes encabezados:

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
  • línea 3: establece el tipo de documento. Encontramos los atributos definidos en el método [Action01]. Como se le indicó que el documento era de tipo «text/plain» y no «text/html», el navegador no interpretó la etiqueta <h1> que figuraba en el documento recibido.

3.5. Acción con un resultado de tipo [ContentResult] - 2

Consideremos la siguiente tercera acción:


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

namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
   ...
    // Acción02
    public ContentResult Action02()
    {
      string data = "<action><name>Action02</name><description>renvoie un texte XML</description></action>";
      return Content(data, "text/xml", Encoding.UTF8);
    }
  }
}
  • línea 12: se define un texto XML;
  • línea 13: se envía al navegador especificando que se trata de XML con el tipo MIME «text/xml».

En el navegador, se obtiene la siguiente página:

 

Veamos en Chrome la respuesta HTTP del servidor:

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
  • línea 3: establece el tipo de documento. Encontramos los atributos definidos en el método [Action02];
  • línea 12: el tamaño en bytes del documento enviado por el servidor.

El documento enviado por el servidor es este (copia de Chrome):

 

3.6. Acción con un resultado de tipo [JsonResult]

Añadamos la siguiente acción al controlador [First]:


    // Acción03
    public JsonResult Action03()
    {
      dynamic personne = new ExpandoObject();
      personne.nom = "someone";
      personne.age = 20;
      return Json(personne,JsonRequestBehavior.AllowGet);
}
  • línea 4: una variable de tipo dynamic. Durante la ejecución, se pueden añadir libremente propiedades a dicha variable. La propiedad se crea al mismo tiempo que se inicializa;
  • líneas 5-6: se inicializan dos propiedades, nom y age;
  • línea 7: se devuelve la representación JSON (notación de objetos de JavaScript) del objeto. La función JSON permite serializar un objeto en una cadena de caracteres y, a la inversa, deserializar una cadena en un objeto. Es una alternativa a la serialización/deserialización XML;
  • línea 2: la acción devuelve un tipo [JsonResult]. Este tipo solo puede devolverse para una solicitud POST. Si se desea devolverlo para un método GET, hay que asignar al segundo parámetro del constructor de la clase Json (línea 7) el valor JsonRequestBehavior.AllowGet.

Al solicitar el URL [/First/Action03], el navegador muestra lo siguiente:

 

La respuesta del servidor es la siguiente:

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
  • línea 3: indica que el documento enviado es JSON;
  • línea 10: el documento enviado tiene 58 bytes. Es el siguiente:
[{"Key":"nom","Value":"someone"},{"Key":"age","Value":20}]

El elemento dinámico [personne] es interpretado por JSON como una matriz de diccionarios en la que cada diccionario:

  • corresponde a un campo de la variable [personne];
  • tiene dos claves: «Key» y «Value». A «Key» se le asocia el nombre del campo y a «Value», el valor del campo.

3.7. Acción con un resultado de tipo [string]

Añadamos la siguiente acción al controlador [First]:


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

Cuando solicitamos URL [/First/Action04] con Chrome, obtenemos la siguiente respuesta:

 

Se observa que se ha interpretado la etiqueta <h3>. Veamos la respuesta HTTP del servidor:

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

y el siguiente documento:

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

En la línea 3 se ve que el servidor ha indicado que envía texto en formato HTML. Por eso el navegador ha interpretado la etiqueta <h3>. Cuando se quiere enviar texto sin formato, es preferible devolver un [ContentResult] en lugar de un [string]. De hecho, el [ContentResult] nos permite especificar un tipo MIME «text/plain» para indicar que se envía texto sin formato, por lo que el navegador no lo interpretará.

3.8. Acción con un resultado de tipo [EmptyResult]

Consideremos la siguiente acción nueva:


    // Acción05
    public EmptyResult Action05()
    {
      return new EmptyResult();
}

La acción se limita a devolver un tipo [EmptyResult]. En este caso, el servidor envía una respuesta vacía al cliente, tal y como muestra su respuesta 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
  • línea 9: el servidor indica a su cliente que le envía un documento vacío.

3.9. Acción con un resultado de tipo [RedirectResult] - 1

Consideremos la siguiente acción nueva:


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

La acción devuelve un tipo [RedirectResult]. Este tipo permite enviar al cliente una orden de redirección hacia el parámetro del constructor URL (línea 4). A continuación, el cliente enviará una nueva solicitud GET a [/First/Action05]. Por lo tanto, el cliente realiza dos solicitudes en total.

El navegador muestra el resultado de la segunda solicitud:

 

Analicemos la respuesta HTTP del servidor en Chrome:

 

Como se ve arriba, estas son las dos solicitudes del navegador. Analicemos la primera solicitud, [Action06]. La respuesta del servidor, HTTP, es la siguiente:

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
  • línea 1: el servidor ha respondido con un código 302 Found. Hasta ahora, era 200 OK, lo que significa que se ha encontrado el documento solicitado. El código 302 indica que se ha solicitado una redirección. La dirección de redirección se indica en la línea 4. Allí encontramos la dirección de redirección que habíamos especificado en el código de la acción;
  • línea 11: el servidor indica que, con su respuesta HTTP, envía un documento de tipo text/html (línea 3) de 132 bytes (línea 11). Al examinar en Chrome la respuesta a la solicitud [Action06], esta está vacía, como era de esperar. Probablemente haya una explicación, pero no la conozco.

Debido a la redirección, el navegador realiza una nueva solicitud GET dirigida a la URL indicada en la línea 4 anterior, tal y como se puede ver en Chrome, en la línea 1 a continuación:

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. Acción con un resultado de tipo [RedirectResult] - 2

O bien, la siguiente acción nueva:


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

En la línea 4, se ha añadido un segundo parámetro al constructor del tipo [RedirectResult]. Se trata de un valor booleano cuyo valor por defecto es false. Al establecerlo en true, se modifica la respuesta HTTP enviada al cliente. Queda así:

HTTP/1.1 301 Moved Permanently

De este modo, el código de respuesta enviado al cliente es ahora 301 Moved Permanently. La redirección se produce igual que antes, pero se indica que es permanente. Esto permite a los motores de búsqueda sustituir en sus resultados el antiguo URL por el nuevo.

3.11. Acción con un resultado del tipo [RedirectToRouteResult]

Supongamos la siguiente acción nueva:


    // Acción08
    public RedirectToRouteResult Action08()
    {
      return new RedirectToRouteResult("Default",new RouteValueDictionary(new {controller="First",action="Action05"}));
}
  • línea 2: la acción devuelve un tipo [RedirectToRouteResult]. Este tipo permite redirigir a un cliente a una URL especificada, no mediante una cadena de caracteres como anteriormente, sino mediante una ruta.

Las rutas se definen en [App_Start/RouteConfig]. Actualmente solo hay una:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
  • En la línea 4, se le pide al cliente que se redirija a la ruta denominada [Default] con la variable controller=First y la variable action=Action05. El sistema de enrutamiento generará entonces la ruta de redirección URL a partir de /First/Action05. Así lo muestra la respuesta HTTP del servidor:
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
  • línea 1: la redirección;
  • línea 4: la dirección de redirección generada por el sistema de enrutamiento de URL.

3.12. Acción con un resultado de tipo [void]

Sea la siguiente acción nueva:


    // Acción09
    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));
}
  • línea 2: la acción no devuelve ningún resultado. Escribe directamente en el flujo de la respuesta enviada al cliente;
  • línea 4: se recupera un posible parámetro denominado [nom] en la solicitud. Se puede acceder a este parámetro a través de la propiedad [Request] del controlador [Controller], del que hereda el controlador [First]. El parámetro [nom], pasado en forma de [/First/Action09?nom=quelquechose], está disponible en Request.QueryString["nom"]. La sintaxis de la línea 4 es equivalente a:
string nom=Request.QueryString["nom"];
if(nom==null){
    nom="inconnu";
}
  • línea 5: se puede acceder a la respuesta enviada al cliente a través de la propiedad [Response] del controlador [Controller], del que hereda el controlador [First];
  • línea 5: se establece el encabezado HTTP [Content-Type], que indica la naturaleza del documento que el servidor se dispone a enviar al cliente. En este caso, «text/plain» indica que el documento es texto sin formato que el navegador no debe interpretar;
  • línea 6: se escribe una cadena de caracteres en el flujo de la respuesta. En ella se han incluido las etiquetas HTML, que el navegador no debe interpretar, ya que este habrá recibido previamente el encabezado HTTP [Content-Type : text/plain"]. Esto es lo que queremos comprobar.

Compilemos el proyecto y solicitemos el URL [/First/Action09?nom=someone ][1] y, a continuación, elURL [/First/Action09 ] [2]:

Veamos ahora en Chrome la respuesta HTTP del servidor:

1
2
3
4
5
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
...
Content-Length: 144
  • línea 3: encontramos el encabezado HTTP que nosotros mismos habíamos establecido en el código de la acción.

3.13. Un segundo controlador

Creemos en el proyecto un segundo controlador. Seguiremos el método descrito en el apartado 3.1, página 40. Lo llamaremos [Second].

  

Su código generado es el siguiente:


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

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

  }
}

Modifiquémoslo de la siguiente manera:


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

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

  }
}

A continuación, solicitemos el URL [/Second/Action01] con un navegador. Obtenemos la siguiente respuesta:

 

Este URL se solicitó mediante un comando HTTP GET, tal y como muestran los registros HTTP de la solicitud en Chrome:

GET /Second/Action01 HTTP/1.1

El URL también se puede solicitar con un comando HTTP POST. Para demostrarlo, volvamos a utilizar la aplicación [Advanced Rest Client]:

  • en [1], se inicia la aplicación (en la pestaña [Applications] de una nueva pestaña de Chrome);
  • en [2], seleccionamos la opción [Request];
  • en [3], se especifica la URL solicitada;
  • en [4], se indica que el URL debe solicitarse con un POST;

Se activan los registros de Chrome mediante (CTRL-I) para obtener la respuesta HTTP del servidor. Al ejecutar [Send], la solicitud anterior, los intercambios HTTP son los siguientes:

El navegador envía la siguiente solicitud:

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
  • línea 1: se solicita correctamente URL con un POST;
  • línea 4: el tamaño en bytes de los elementos enviados. Aquí no hay ninguno.

La respuesta HTTP del servidor es la siguiente:

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
  • línea 3: el servidor envía un documento de texto sin formato (plain);
  • línea 12: de 148 caracteres.

El documento enviado es el siguiente:

 

Se obtiene el mismo documento que con el GET.

3.14. Acción filtrada por un atributo

Creemos la siguiente acción nueva:


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

La acción [Action02] es análoga a la acción [Action01], pero se indica que solo es accesible mediante el comando HTTP POST (línea 2). Se pueden utilizar otros atributos:

HttpGet
solo sirve para el comando GET
HttpHead
solo sirve para el comando HEAD
HttpOptions
solo sirve para el pedido OPTIONS
HttpPut
solo sirve para el pedido PUT
HttpDelete
solo sirve para el comando DELETE

Solicitemos directamente en el navegador el URL [/Second/Action02]. A continuación, se solicita mediante un GET. El navegador muestra entonces la siguiente respuesta:

 

La respuesta HTTP del servidor fue la siguiente:

1
2
3
HTTP/1.1 404 Not Found
...
Content-Length: 3807
  • línea 1: el código HTTP 404 Not Found indica que el servidor no ha encontrado el documento solicitado. En este caso, la acción [Action02] no ha podido atender la solicitud GET, ya que solo atiende los comandos POST;
  • línea 3: el tamaño del documento devuelto. Se trata de la página que ha mostrado el navegador:
 

3.15. Recuperar los elementos de una ruta

En las dos acciones descritas anteriormente, se escribía algo así:


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

Los nombres del controlador y de la acción estaban escritos de forma fija en el código. Si se cambian estos nombres, el código deja de ser válido. Se puede acceder al controlador y a la acción de la siguiente manera:


    // /Segundo/Acción03
    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);
}

La ruta definida en [App_Start/RouteConfig] es la siguiente:


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

En la línea 3, los tres elementos de la ruta se pueden obtener mediante RouteData.Values["élément"] con el elemento en [controller, action, id].

Solicitemos los datos de URL y [http://localhost:49302/Second/Action03]:

 

Hemos recuperado correctamente tanto el nombre del controlador como el de la acción.