Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Manual Framework Asp Net PDF
Manual Framework Asp Net PDF
www.desarrolloweb.com
Lo de nuevo entre comillas, viene porque la primera versin sali, aproximadamente, a mediados del 2009 y ya
sabemos que en este mundo, casi dos aos son una eternidad. De hecho, actualmente el framework ya va por su tercera
versin y es esa tercera versin la que vamos a ver en esta serie de artculos.
ASP.NET MVC 3.0 permite el desarrollo de aplicaciones web usando .NET Framework 4.0 y Visual Studio 2010. No
puede usarse Visual Studio 2008 aunque s la versin Express (gratuita) de Visual Web Developer. ASP.NET MVC 3.0
no viene de serie, ni con Visual Studio 2010 ni con Visual Web Developer 2010. Puede instalarse usando Web Platform
Installer o bien descargndolo desde http://www.microsoft.com/downloads/en/details.aspx?FamilyID=d2928bc1-f48c-
4e95-a064-2a455a22c8f6&displaylang=en
Le damos el nombre que queramos (en este caso MvcHelloWorld) y el directorio donde se va a generar y listos.
En el siguiente paso nos preguntar si queremos una aplicacin "Emtpy" o "Internet Application", y seleccionamos
"Empty". La diferencia es que en el segundo caso ya se nos genera un conjunto de controladores y vistas por defecto, y
ahora este cdigo nos liara ms que ayudara as que vamos a obviarlo. Con "Empty" empezamos con una aplicacin
ASP.NET MVC vaca.
El desplegable "View Engine" tiene dos valores: Razor y ASPX. Esto hace referencia a la tecnologa con la cual se
implementan las vistas. Si seleccionamos ASPX nuestras vistas sern archivos .aspx, mientras que si usamos Razor
nuestras vistas sern archivos .cshtml (o .vbhtml si usamos Visual Basic). Razor es una sintaxis nueva mucho ms
compacta que ASPX y es, por tanto, la que vamos a usar nosotros.
Finalmente le damos a "Ok" y eso nos va a crear nuestro proyecto vaco.
ASP.NET MVC sigue lo que se conoce como convention over configuration, es decir: en lugar de usar archivos de
configuracin para ciertas tareas, se usan convenciones predefinidas. Y esas convenciones son reglas como las
siguientes:
1. Las vistas se ubican en la carpeta View
2. Los controladores son clases cuyo nombre termina en Controller
Las carpetas que nos crea Visual Studio por defecto son las siguientes:
1. Content: Para tener contenido esttico (imgenes, hojas de estilo, )
2. Controllers: Para ubicar nuestros controladores
3. Models: Para ubicar las clases del modelo.
4. Scripts: Para tener archivos con cdigo javascript
5. Views: Donde van las vistas de la aplicacin
Podemos ver que por defecto el nombre termina con Controller. Modificamos para que en lugar de Default1Controller
sea HomeController y le damos a Add. Eso nos crear una clase HomeController en la carpeta Controllers con el
cdigo:
public class HomeController : Controller
{
//
// GET: /Home/
Le ponemos "Index" como nombre y le damos a Add. Con eso Visual Studio nos habr generado un archivo
Index.cshtml (situado en /Views/Home) con el cdigo:
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<h2>Index</h2>
Un ltimo detalle: Si os fijis la URL es simplemente http://localhost, sin nada ms y se est mostrando nuestra vista.
Qu ha ocurrido? Pues que, por defecto si no se incluye controlador se asume que es "Home" y si no se entra accin se
asume que es Index. Pero si entramos la URL completa vemos que tambin funciona:
Por otra parte si entramos un nombre de controlador o de accin que no existe, recibimos un 404 (pgina no
encontrada):
Un saludo a todos!
Hay tres mtodos (que realmente son dos) principales para pasar datos de los controladores a las vistas.
Recordad que segn el patrn MVC, el controlador es quien accede al modelo para obtener los datos y mandrselos a la
vista. La vista debe limitarse a mostrar esos datos (o a pedir datos nuevos y mandrselos de vuelta al controlador, lo que
veremos ms adelante). Los datos que el modelo proporciona al controlador pueden venir de cualquier fuente
(usualmente una base de datos).
Aqu se define el controlador Home con la accin Index (recordad que las acciones son los mtodos pblicos que
reciben las peticiones del navegador). En esta accin establecemos las claves Nombre y Twitter del ViewData y luego
devolvemos la vista asociada a dicha accin.
Para mostrar los datos en la vista, simplemente usamos la propiedad ViewData que tienen las vistas y que funciona
exactamente igual. En este caso, en el cdigo de nuestra vista (archivo Home.cshtml que estara en la carpeta Home
dentro de la carpeta Views) tendramos algo como:
<h2>Index</h2>
Este valor es un DateTime, no obstante si desde la vista queremos llamar a los mtodos de DateTime (como
ToLongDateString()) deberemos hacer un casting:
Dado de alta en: @(((DateTime)ViewData["FechaAlta"]).ToLongDateString())
Si no hiciramos el casting a DateTime, la llamada a ToLongDateString() generara una excepcin (aun cuando, en
efecto, lo que hay en el ViewData["FechaAlta"] es un DateTime).
Fijaos que el cdigo es casi igual al uso de ViewData, solo que en lugar de hacer ViewData["clave"] hacemos
ViewBag.clave.
Y para recuperarlos desde una vista? Pues igual que antes, podemos usar la propiedad ViewBag:
<h2>Index2</h2>
Aqu podemos ver la gran ventaja de ViewBag respecto ViewData: no es necesario usar casting. Fijaos que para usar
ToLongDateString() no hemos tenido necesidad de convertir el resultado devuelto por ViewBag.FechaAlta a DateTime.
Esa es la gran ventaja de ViewBag respecto a ViewData y es por eso que, personalmente, os recomiendo usar ViewBag
en lugar de ViewData (de hecho ViewData se mantiene por compatibilidad con las versiones anteriores del framework
de ASP.NET MVC). Otra ventaja es que no usamos cadenas sino propiedades para acceder a los elementos, pero ojo,
que el compilador no puede ayudaros si accedis a una clave (propiedad) que no existe (al ser las propiedades
dinmicas).
Notemos las diferencias: El controlador crea un objeto de la clase Usuario y manda ese objeto a la vista, pasndolo
como parmetro a la funcin View.
Y desde la vista? Pues usamos la propiedad Model para acceder a dicho objeto:
@model MvcHelloWorld.Controllers.Usuario
<h2>Index3</h2>
Mi nombre es @Model.Nombre y puedes seguirme en
<a href="http://twitter.com/@Model.Twitter">Twitter</a>. <br />
Dado de alta en: @Model.Alta.ToLongDateString()
Fijaos que ahora usamos Model para acceder a los datos, en lugar de ViewData o ViewBag. Y una cosa importante:
fijmonos en la primera lnea, que empieza con @model. Esa lnea le indica al framework de que tipo es el objeto que la
vista recibe del controlador (es decir, de que tipo es la propiedad Model). Por supuesto este tipo debe coincidir con el
objeto que se pasa como parmetro a la funcin View en la accin del controlador. Una nota: El uso de @model es
opcional, si no lo ponemos, nuestra vista puede seguir usando la propiedad Model, pero en este caso es de tipo dynamic.
Eso tiene sus ventajas y sus inconvenientes (volveremos sobre ello en posteriores artculos). Las vistas que declaran
@model, se llaman vistas fuertemente tipadas. Usar vistas fuertemente tipadas tiene una ventaja que es que al saber
Visual Studio cual es el tipo de la propiedad Model, nos puede proporcionar soporte de Intellisense.
Usar este mecanismo es la manera preferida de pasar datos desde una accin a una vista (ya que en lugar de tener datos
desperdigados en n claves los podemos tener organizados en una clase). A las clases que se pasan desde las acciones a
las vistas (como nuestra clase Usuario) se les conoce tambin con el nombre de ViewModels (para distinguirlas de las
clases que conforman el Modelo del patrn MVC, ya que los ViewModels lo nico que hacen es contener datos que
mostrar una vista).
Desde su versin ASP.NET MVC ha tenido el concepto de motor de vistas (View Engine). A ver, recapitulemos: en
ASP.NET MVC las vistas realizan tareas slo de presentacin. No contienen ningn tipo de lgica de negocio y no
acceden a datos. Bsicamente se limitan a mostrar datos (en el artculo anterior vimos como pasar datos de los
controladores a las vistas) y a solicitar datos nuevos al usuario. Si vienes del mundo de webforms, olvdate del concepto
de Web Controls: no existen en ASP.NET MVC. No tenemos drag and drop, no configuramos propiedades. Las vistas
son bsicamente HTML. Y lo que no es HTML son pequeas porciones de cdigo de servidor destinadas a terminar
generando HTML para mostrar informacin.
El equipo que desarroll ASP.NET MVC tuvo la feliz idea de permitir separar la sintaxis de servidor usada, del
framework de ASP.NET MVC. El resultado? Lo que llamamos un motor de vistas de ASP.NET MVC. En las versiones
1 y 2, el framework viene con un nico motor de vistas, el llamado motor ASPX, que usa los clsicos archivos .aspx
como vistas de ASP.NET MVC. Pero ojo! Aunque estemos utilizando archivos .aspx, no son webforms (los
webcontrols no funcionan, no tenemos viewstate ni el ciclo de vida de webforms y adems es que son vistas de MVC,
por lo que slo deben hacer tareas de presentacin). La sintaxis que usa el motor ASPX es un poco eso que los
anglosajones llaman verbose, es decir que se debe escribir mucho para ciertas tareas. As, imaginad una pgina que
tuviese que mostrar una lista de elementos de la clase Usuario (la que usamos en el captulo anterior). El cdigo, usando
el motor de vistas ASPX queda as:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<IEnumerable<MvcHelloWorld.Controllers.Usuario>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
DemoAspx
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>DemoAspx</h2>
<table>
<% foreach (var item in Model) { %>
<tr>
<td>
<%: item.Nombre %>
</td>
<td>
<%: item.Twitter %>
</td>
<td>
<%: String.Format("{0:g}", item.Alta) %>
</td>
</tr>
<% } %>
</table>
</asp:Content>
Fijaos en la cantidad de veces que debe usarse el tag <% y su parejo %> para indicar dnde empieza y termina el cdigo
de servidor.
Rpidamente empezaron a surgir motores de vistas alternativos, realizados por la comunidad, con la intencin de tener
sintaxis ms claras para nuestras vistas. Algunos ejemplos son Nhaml y Spark.
Finalmente la versin 3 de ASP.NET MVC vino acompaada de un nuevo motor de vistas, llamado Razor. Eso s, el
motor ASPX puede seguir siendo usado en ASP.NET MVC3, pero honestamente no hay ninguna razn para hacerlo
(salvo en casos de migraciones, por supuesto): Razor es ms claro, sencillo e intuitivo.
Sintaxis de Razor
Lo que ms choca de Razor es que, a diferencia del motor ASPX donde tenemos el tag que inicia el cdigo de servidor
y el que lo termina, slo hay tag para iniciar cdigo de servidor. El motor Razor es lo suficientemente inteligente para
saber cundo termina el cdigo de servidor, sin necesidad de que lo explicitemos. Veamos la misma vista de antes, pero
ahora usando Razor:
@model IEnumerable<MvcHelloWorld.Controllers.Usuario>
@{
ViewBag.Title = "DemoRazor";
}
<h2>DemoRazor</h2>
@foreach (var item in Model) {
<tr>
<td>
@item.Nombre
</td>
<td>
@item.Twitter
</td>
<td>
@String.Format("{0:g}", item.Alta)
</td>
</tr>
}
</table>
Las diferencias saltan a la vista, no? En Razor el smbolo de la arroba (@) marca el inicio de cdigo de servidor. Y
como comentaba antes, no hay smbolo para indicar que se termina el cdigo de servidor: el motor Razor deduce
cuando termina en base al contexto.
Fijaos que para mostrar una variable de servidor (item.Nombre p.ej.) simplemente la precedemos de una @. Fijaos
tambin que la llave de cierre del foreach no debe ser precedida de ninguna arroba, Razor ya sabe que esa llave es de
servidor y cierra el foreach abierto.
El uso de la @ funciona de dos maneras bsicas:
1. @expresin: Renderiza la expresin en el navegador. As @item.Nombre muestra el valor de tem.Nombre. Es
decir @expresin equivale a <%: expresin %>
2. @{ cdigo }: Permite ejecutar un cdigo que no genera salida HTML. Es decir @{cdigo} equivale a <%
Cdigo %>
Consideraciones a la sintaxis
Expresiones compejas
Como hemos visto el motor Razor interpreta cuando empieza y cuando termina el cdigo de servidor. Pero no siempre
lo consigue adecuadamente. P.ej, el siguiente cdigo Razor:
@{ int a = 10; int b = 3; }
El valor de 10 - 3 es: @a-b
Genera el siguiente HTML:
El valor de 10 - 3 es: 10-b
Es decir Razor ha interpretado que el cdigo de servidor terminaba al encontrar el smbolo de resta. En este caso, esa
presuncin es totalmente errnea, pero por suerte podemos usar los parntesis para que haya slo una expresin detrs
de la arroba:
El valor de 10 - 3 es: @(a-b)
Con ese cdigo la vista mostrar el valor correcto (7). Recordad la clave: el motor Razor espera una y slo una
expresin detrs de la @. Por eso debemos usar los parntesis.
"Romper" el cdigo de servidor
A veces la problemtica es justo la contraria de la que hemos visto con las expresiones complejas: a veces hay cdigo
que Razor interpreta que es de servidor pero realmente parte de ese cdigo es HTML que debe enviarse al cliente.
Veamos un ejemplo:
@for (int i = 0; i < 10; i++)
{
El valor de i es: @i <br />
}
A priori podramos esperar que este cdigo generara 10 lneas de cdigo HTML. Pero el resultado es un error de
compilacin. La razn es que Razor interpreta que la lnea "El valor de i es: @i" es cdigo de servidor. Para "romper"
este cdigo de servidor y que Razor "sepa" que realmente esto es cdigo que debe enviarse tal cual al cliente, tenemos
dos opciones:
1. Intercalar una etiqueta HTML. Al intercalar una etiqueta HTML Razor "se da cuenta" que all empieza un cdigo de
cliente:
@for (int i = 0; i < 10; i++)
{
<span>El valor de i es:</span> @i <br />
2. Usar la construccin @: que indica explcitamente a Razor que lo que sigue es cdigo de cliente:
@for (int i = 0; i < 10; i++)
{
@:El valor de i es: @i <br />
}
La diferencia es que en el primer caso las etiquetas se envan al navegadaor, mientras que en el segundo caso no.
En este caso Razor interpreta que se trata de un correo electrnico, por lo que no trata la @ como inicio de cdigo de
servidor. Este comportamiento a veces tampoco se desa. P.ej, imaginad lo siguiente:
<div id="div_@Model.Index">Div usado</div>
Estoy asumiendo que el ViewModel que recibe la vista tiene una propiedad llamada Index. Supongamos que dicha
propiedad (Model.Index) vale 10. La verdad es que uno pensara que eso generara el cdigo HTML:
<div id="div_10">Div usado</div>
Es decir, Razor no ha interpretado la @ como inicio de cdigo de servidor, y eso es porque ha aplicado la excepcin de
correo electrnico. La solucin pasa por usar los parntesis:
<div id="div_@(Model.Index)">Div usado</div>
Ahora Razor sabe que Model.Index es cdigo de servidor y lo evaluar, generando el HTML que estbamos esperando.
A veces Razor falla incluso ms espectacularmente. Dado el siguiente cdigo:
@for (int i = 0; i <= 1; i++)
{
<div id="div_@i">Div @i</div>
}
En base a lo que hemos visto podramos esperar que generase el siguiente HTML:
<div id="div_@i">Div 0</div>
<div id="div_@i">Div 1</div>
Pues no! Este cdigo hace que el motor de Razor de un error (The for block is missing a closing "}" character. Make
sure you have a matching "}" character for all the "{" characters within this block, and that none of the "}" characters
are being interpreted as markup).
Por supuesto, la solucin para generar el HTML que queremos pasa por usar el parntesis igual que antes:
@for (int i = 0; i <= 1; i++)
{
<div id="div_@(i)">Div @i</div>
}
Escapar la arroba
A veces es necesario indicarle a Razor que una arroba es eso
una simple arroba y que no haga nada especfico. Que no
asuma nada, que no piense nada, que simplemente enve una @ al cliente. Un ejemplo? El siguiente cdigo:
<style>
@@media screen
{
body { font-size: 13px;}
}
</style>
Si no usramos la doble arroba (@@), Razor nos generara un error, ya que @media lo interpreta como "enviar el
contenido de la variable media al cliente". En este caso al usar @@ Razor simplemente sabe que debe enviar una @ al
cliente y lo que el navegador recibe es:
<style>
@media screen
{
body { font-size: 13px;}
}
</style>
Conclusiones
Hemos visto la sintxis bsica del motor de vistas Razor, y las principales consideraciones que debemos tener presentes.
No hemos visto las capacidades adicionales de dicho motor de vistas como layouts, templates y regiones que iremos
viendo en captulos posteriores.
Para finalizar una cosilla: Ambos motores de vistas (ASPX y Razor) se pueden usar en MVC3 de forma simultnea. El
motor de ASP.NET MVC intentar encontrar primero una vista .aspx y si no la encuentra buscar una vista Razor
(.cshtml, aunque tambin puede ser .vbhtml si usamos Visual Basic.NET en lugar de C#). Aunque por supuesto este
comportamiento puede ser modificado.
Un saludo a todos!
En estos primeros cuatro captulos de esta serie sobre ASP.NET MVC hemos visto los aspectos fundamentales del
framework: controladores, acciones y vistas. En el captulo dos, vimos que cada peticin del navegador debe ser
enrutada a una accin de un controlador. Dijimos tambin que por defecto se sigue la convencin de que las URLs estn
en forma http://servidor/controlador/accion.
Lo que mucha gente no sabe es que eso no es realmente una convencin del framework de ASP.NET MVC ni mucho
menos una obligacin. Que las URLs sigan por defecto esta forma se debe, ni ms ni menos, al cdigo que nos genera
por defecto Visual Studio cuando creamos un proyecto ASP.NET MVC. La responsable de decidir qu accin de qu
controlador se encarga de procesar cada peticin es la tabla de rutas y es de lo que vamos a hablar hoy.
Hablando rpidamente podramos decir que la tabla de rutas es la responsable de mapear cada URL a una accin de un
controlador. La frase puede parecer balad, pero no lo es en absoluto. Hay dos aspectos clave a tener en cuenta:
1. Cada URL debe ser mapeada a una accin de un controlador en concreto.
2. Para mapear una peticin del navegador a una accin de un controlador se usa slo la URL. Repito: slo la
URL.
Hablando en propiedad, la frase anterior no es del todo precisa. Lo que la tabla de rutas realmente hace es determinar
qu valores de ruta (route values) se rellenan a partir de la URL.
Este cdigo es el que configura la tabla de rutas. El parmetro routes que recibe este mtodo es la propia tabla de rutas,
que est en la propiedad esttica Routes de la clase RouteTable.
Analicemos este cdigo, y empecemos no por la primera lnea, sino por la segunda: la que llama al mtodo MapRoute.
Este mtodo (que es realmente un mtodo de extensin, aunque esto no sea relevante) nos permite aadir una nueva
entrada a la tabla de rutas de forma sumamente sencilla. Est sobrecargado pero en este cdigo los parmetros que
recibe son:
1. El nombre de la ruta (un identificador de la ruta). En este caso la ruta se llama Default
2. Las URLs que mapea esta ruta
3. Los valores por defecto de los valores de ruta, en caso de no ser encontrados en la URL.
Patrones de URLs
Centrmonos un poco en segundo parmetro. Su valor es {controller}/{action}/{id}.
Eso es simplemente el patrn que
deben cumplir las URLs para ser procesadas por esta ruta. Lo que est entre llaves es el nombre del valor de ruta que se
crea. As pues el patrn {controller}/{action}/{id} mapear cualquier URL que est en la forma
http://servidor/xxx/yyy/zzz. Y adems asignar los siguientes valores de ruta:
1. controller = xxx
2. action = yyy
3. id = zzz
Peroque pasa con una URL que no tenga el /zzz final? Que ocurre con una URL http://servidor/xxx/yyy? Pues en
principio una URL de este tipo no sera procesada por este patrn (ya que el patrn pide explcitamente que haya
{controller}/{action}/{id}. Pero, para evitar que la configuracin de la tabla de rutas costase horrores, existe el tercer
parmetro: los valores por defecto.
Mltiples patrones
La tabla de rutas se llama precisamente tabla porque puede contener varias entradas (es decir, varios patrones de URL,
con sus parmetros por defecto, etc). Para aadir ms entradas (rutas, cada entrada se conoce como ruta), lo ms
sencillo es aadir llamadas a MapRoute. P.ej. si quisiramos procesar la URL anterior http://servidor/Books/View/10/20
podramos aadir una entrada adicional:
routes.MapRoute(
"DosIds", // Route name
"{controller}/{action}/{id}/{id2}"
);
Ahora la URL http://servidor/Books/View/10/20 ya puede ser procesada, y ser procesada por esa entrada nueva en la
tabla de rutas. Los valores de ruta creados sern:
controller = Books
action = View
id = 10
id2 = 20
Un tema importante es que el orden de las rutaas en la tabla de rutas importa. Por cada URL, el framework evaluar las
distintas entradas de la tabla de rutas, una a una, en el orden en que estas se encuentren y tan buen punto una URL
pueda ser procesada, se eligir esa entrada de la tabla de rutas. P.ej. supongamos que queremos mapear las URLs de la
forma http://servidor/Ver/Edu a la accin View del controlador Profile con un valor de ruta llamado user cuyo valor sea
lo que hay despus de Ver (Edu en este caso). Eso lo podemos conseguir con una entrada en la tabla de rutas:
routes.MapRoute(
"ViewProfile", // Nombre de la ruta
"Ver/{author}", // URL with parameters
new { controller = "Profile", action = "View" }
);
Un detallito a tener en cuenta de esta nueva entrada es que dado que no hay lugar en el patrn de URL para los valores
de ruta de controller y action, al ser esos obligatorios, deben especificarse como valores por defecto.
Esta entrada mapea una URL del tipo: http://servidor/Ver/Edu con los valores de ruta:
controller = Profile (valor por defecto)
action = View (valor por defecto)
author = Edu (sacado del patrn de la URL)
Pero, podis comprobar que si aads esa lnea despus del routes.MapRoute que ya haba, si entris una URL del tipo
http://servidor/Ver/Edu el framework os devolver un 404, incluso aunque tengis el controlador Profile con una accin
View definida.
Por qu?
Pues simplemente porque la tabla de rutas se evala en orden. Y puede mapear la primera entrada (la ruta llamada
Default) una URL del tipo http://servidor/Ver/Edu? La respuesta es que s, y los valores de ruta quedan establecidos a:
controller = Ver
action = Edu
id = No hay valor de ruta id
(recordad que era opcional)
Por lo tanto, a no ser que tengis un controlador llamado Ver con una accin llamada Edu (cosa poco probable, no nos
vamos a engaar :p) el framework os devolver un 404. Para que todo funcione, la entrada Default
debe estar despus
de la nueva entrada:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"ViewProfile", // Nombre de la ruta
"Ver/{author}", // URL with parameters
new { controller = "Profile", action = "View" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new {controller = "Home", action = "Index", id = UrlParameter.Optional} // Parameter defaults
);
}
Para crear esas rutas especiales, se usa el mtodo IgnoreRoute. Se le suele pasar un solo argumento, que es el patrn de
URL a ignorar. Un ejemplo lo tenemos en el propio cdigo que genera Visual Studio:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
Este patrn de URL se mapear a todas aquellas URLs que tengan la forma http://servidor/xxx.axd/yyy
Dnde:
1. xxx es cualquier nombre
2. yyy es cualquier cosa, incluyendo barras separadoras (/). El hecho de que yyy pueda incluir barras separadoras
es porque se usa la forma {*nombre_valor_ruta} (con un asterisco) que es lo que se conoce como catch-all y
significa literalmente: captura todos los caracteres de la URL que vengan a partir de ahora.
Es decir la URL http://servidor/trace.axd/foo/bar/baz ser enrutada por esta ruta. Al ser declarada con IgnoreRoute, lo
que har es que dicha peticin sea ignorada por ASP.NET MVC y ser procesada por alguien ms (en este caso el
propio motor de ASP.NET, pero eso ya depende de cada caso).
En el siguiente artculo vamos a ver los valores de ruta y los controladores.
Nota: En este caso usamos el atributo [ActionName] que permite indicar el nombre de la accin que implementa un mtodo en concreto.
Si no se usa ese atributo el nombre de la accin es el mismo que el nombre del mtodo. En este caso debemos usarlo porque la accin se
llama View, pero el nombre de mtodo View ya est definido dentro de la clase Controller (y se usa para devolver vistas).
El error viene a decir que la accin espera un parmetro (id) pero que no hay valor de ruta del cual tomar ese parmetro.
Si el parmetro aceptase null (es decir, fuese un valor por referencia como string) el framework asignara null a ese
parmetro y no se quejara. Pero int no acepta null, as que el framework da ese error.
Es normal, al principio, pensar que para solucionar esto bastara con aadir, un mtodo Index sin parmetros al
controlador. Algo como:
public class HomeController : Controller
{
// URLs tipo /Home/Index/10
public ActionResult Index(int i)
{
return View();
}
Pero si probamos esto, nos damos cuenta de que ahora nos aparece otro error distinto:
Adems ese error aparece indistintamente, ya sea que entremos http://servidor/Home/Index/10 (con id) o
http://servidor/Home/Index (sin id).
La razn de este error es muy simple: una misma accin slo puede ser implementada por un solo mtodo (hay una
excepcin a este caso que es cuando se usan verbos http distintos, pero eso lo veremos en artculos posteriores). En este
caso tenemos dos mtodos (Index() y Index(int)) que ambos implementan la accin Index. Y eso no est permitido.
La solucin a todo ese
lopasa por hacer una de esas dos cosas:
1. Convertir el parmetro de la accin a algo que acepte nulls (p.ej. string o bien int?).
2. O modificar la tabla de rutas para que el parmetro en lugar de ser opcional, tenga un valor por defecto.
Si optamos por el primer caso, la accin puede quedar como:
public class HomeController : Controller
{
public ActionResult Index(int? id)
{
return View();
}
}
Ahora, la URL http://servidor/Home/Index se enruta a esta accin y como el valor de ruta id no existe, el mtodo de
accin recibir un null.
Por otro lado la URL http://servidor/Home/Index/10 se enruta a esta misma accin y el valor de ruta id valdr 10 (y ese
ser el valor del parmetro que reciba el controlador).
Si optamos por modificar la tabla de rutas, en lugar de declarar el valor de ruta id como opcional, dicho valor debe tener
un valor por defecto:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new {controller = "Home", action = "Index", id = 0} // Parameter defaults
);
Fijaos ahora que el valor por defecto del valor de ruta id es 0. Ahora la URL http://servidor/Home/Index se enruta a la
accin con el valor de ruta id con valor 0. Y por su lado la URL http://servidor/Home/Index/20 se enruta a la misma
accin pero con el valor de ruta id a 20.
Debe notarse que en este caso no puede diferenciarse entre la url http://servidor/Home/Index y la url
http://servidor/Home/Index/0 (ambas URLs tienen el valor de ruta id con valor 0).
As terminamos este artculo dedicado a la tabla de rutas, uno de los componentes bsicos de ASP.NET MVC pero que
muy poca gente le presta atencin al principio!
ASP.NET MVC tiene un soporte directo para usar la querystring: los parmetros que se pongan a la URL sern enviados
como parmetros de la accin correspondiente.
Es decir, si yo tengo la siguiente url: http://host/home/Index?p1=10&p2=no (y suponiendo la tabla de rutas por defecto),
se invocar la accin Index de HomeController con dos parmetros p1 (con valor 10) y p2 con valor no.
As en el
controlador podramos tener definida la accin de la siguiente forma:
public ActionResult Index(int p1, string p2)
{
// Codigo...
}
Los nombres de los parmetros deben coincidir con los nombres de los parmetros de la querystring. Bien, fijaos que
dado que hemos declarado el parmetro p1 como int slo podemos pasar valores enteros, mientras que en el parmetro
p2, podemos pasar cualquier cadena. Si pasamos una cadena en el parmetro p1, p.ej. la url http://host/home/index?
p1=texto&p2=otrotexto el error que recibimos es el siguiente:
Ahora si invocamos la url y el valor de p1 no es numrico, nos llegar null, mientras que si el valor de p1 es numrico
recibiremos su valor.
La regla es realmente muy simple: Si quieres que un parmetro de querystring sea opcional debes usar un tipo por
referencia (es decir una clase como string o Nullable). Si usas un tipo por valor (como int o double) el parmetro no
puede ser opcional y adems el valor que se entre en la URL debe ser convertible de cadena al tipo concreto que pongas
en el controlador.
Si ejecutamos eso y miramos el cdigo HTML veremos que es exactamente lo que habamos tecleado antes (debido a
que usamos la tabla de rutas estndar). Pero si aado una entrada a la tabla de rutas, dejndola as:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Ver",
"VerProducto",
new {controller = "Home", action = "View"});
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
As pues, siempre que necesitis obtener una URL desde una vista, usad Url.Actionrecordad que el formato real de las
URLs depende de la tabla de rutas. Asumir que siempre estarn en la forma /controlador/accin es una muy mala
prctica (y como deca antes, un error comn al principio).
Al final eso nos implica que no deberamos nunca generar las URLs con parmetros querystring aadidos a mano. Por
suerte para nosotros el helper Url.Action que hemos visto antes viene de nuevo a nuestra ayuda. En una de sus
sobrecargas Url.Action acepta un objeto annimo cuyas propiedades son los valores a mandar al controlador.
Url.Action es lo suficientemente inteligente como para usar valores de ruta si estn definidos y querystring en caso de
que no!
As pues si tenemos la tabla de rutas estndar y tenemos las siguientes llamadas a Url.Action:
Url 1: @Url.Action("Index", "Home", new { id = 20 })
Fijaos como desde el controlador recibimos de igual manera parmetros de ruta que parmetros que vengan por
querystring
:)
En el siguiente artculo del tutorial vamos a ver como mandar datos de formularios a las vistas, es decir cmo usar
POST.
Dicho rpido y mal: el uso de POST equivale al uso de formularios HTML. Digo lo de mal porque ni los formularios
son la nica manera de enviar datos va POST, ni todos los formularios se envan por POST, pero no nos vamos a
centrar en estos detalles, ya que la mayora de formularios hoy en da se envan via POST. La principal diferencia entre
enviar datos via POST o via GET (es decir usando la URL, ya sea a travs de querystring que vimos en el artculo
anterior, o en valores de ruta) es que con POST los datos circulan en el cuerpo de la peticin y no son visibles en la
URL.
controlador Usuarios:
<h2>Nuevo usuario</h2>
<form method="POST">
<label for="login">login:</label>
<input type="text" name="login" />
<br />
<label for="password">clave:</label>
<input type="text" name="password" />
<br />
<input type="submit" value="enviar"/>
</form>
Esta vista crea un formulario que ser enviado por POST. El formulario contiene:
Dos etiquetas con texto ()
Dos campos de texto (<input type=text>),
uno que se llama login
y otro llamado password
(el valor de sus
atributos name).
Un botn para enviar el formulario
Si os fijis no hemos indicado a que URL debe enviarse el formulario. Eso se hace a travs del atributo action de la
etiqueta <form>. Si no aparece, el formulario se mandar de vuelta a la misma URL a la que estamos.
En el controlador (UsuariosController) metemos simplemente una accin que muestre la vista:
public ActionResult Nuevo()
{
return View();
}
Bien, si ahora con el navegador nos dirigimos a /Usuarios/Nuevo nos aparecer la vista con el formulario. Podemos
rellenar datos y pulsar enviar. Al pulsar enviar simplemente se nos mostrar la vista (vaca) de nuevo. Esto ocurre
porque al pulsar el botn de enviar datos se enva el formulario a la misma URL (/Usuarios/Nuevo) de la que venimos.
Por lo tanto se invoca de nuevo la accin Nuevo del controlador Usuarios que lo nico que hace es mostrar la vista otra
vez.
Ahora bien, lo que nosotros queremos es que cuando se enve el formulario va POST podamos obtener los datos y
hacer algo con ellos. En definitiva queremos hacer otra cosa que no sea mostrar la vista. La verdad es que suena un
poco como si quisiramos otra accin distinta. Pero, si recordis en los inicios de este manual, dijimos que una accin
slo poda estar implementada por un solo mtodo en el controlador. Bueno, la verdad es que... mentimos un poquillo.
La realidad es que una accin puede estar implementada por un solo mtodo por cada verbo HTTP. Si no sabes lo que
son los verbos HTTP no te preocupes mucho: es la manera tcnica de referirnos a GET y POST. As GET es un verbo
HTTP y POST es otro. Hay ms, como HEAD, PUT y DELETE pero dado que no hay soporte en HTML para estos
verbos no nos vamos a preocupar de ellos (eso no significa que ASP.NET MVC no los soporte, slo que no vamos a
verlo aqu). Para nosotros slo van a existir GET y POST. Y volviendo a lo que decamos, eso significa que para la
misma accin (por lo tanto, la misma URL) puedo tener dos mtodos en el controlador: uno que se invoque a travs de
GET y otro que se invoque a travs de POST. As pues podemos aadir el siguiente mtodo a nuestro controlador:
[HttpPost]
public ActionResult Nuevo(string login, string password)
{
// Codigo...
}
Observad como el mtodo est decorado con [HttpPost]. Al aplicar este atributo al mtodo le estamos indicando a
ASP.NET MVC que cuando se deba invocar la accin Nuevo del controlador Usuarios use este mtodo si la invocacin
es va POST. Si la invocacin es va GET (p.ej. tecleando la URL en la barra de direcciones del navegador) se invocar
el mtodo Nuevo que ya tenamos. Fijaos pues que tenemos una manera simple y elegante de separar nuestro cdigo en
funcin del verbo HTTP que se use.
Fijmonos ahora en los parmetros del mtodo Nuevo: dos parmetros cuyo nombre es el mismo que los nombres de
los campos del formulario. Slo con esto le basta a ASP.NET MVC para enlazar los valores del formulario con los
parmetros de la accin del controlador. Ahora bien, imagina que nuestro formulario en lugar de tener dos campos, tiene
veinte... Te imaginas tener que poner veinte parmetros en la accin del controlador? Pues para evitar esto existe
precisamente el model binding.
Model Binding
Llamamos model binding a la capacidad de ASP.NET MVC de crear objetos (de clases nuestras) a partir de los
parmetros que vengan en la peticin. En nuestro caso a partir de los campos del formulario que enviamos.
As podramos tener una clase Usuario tal y como sigue:
public class Usuario
{
public string login { get; set; }
public string password { get; set; }
}
Y sustituir los dos parmetros que tenamos en la accin Nuevo por un solo parmetro de tipo Usuario:
[HttpPost]
public ActionResult Nuevo(Usuario usuario)
{
// Codigo...
}
Y gracias al poder del model binding recibiremos un objeto usuario rellenado a partir de los datos del formulario. La
nica condicin es que las propiedades del objeto se llamen igual que los campos del formulario.
Llegados a este punto podramos validar los datos y si hay algn error, los podemos mandar de vuelta a la vista (junto
con un mensaje explicativo del error):
[HttpPost]
public ActionResult Nuevo(Usuario usuario)
{
if (string.IsNullOrEmpty(usuario.login) ||
string.IsNullOrEmpty(usuario.password))
{
ViewBag.Error = "Login o password no pueden estar vacos";
return View(usuario);
}
// Damos de alta el usuario en la BBDD y redireccionamos
return RedirectToAction("Home", "Index");
}
Si el campo de login o password se deja vacio, entonces aadimos un campo llamado Error en el ViewBag y
devolvemos la vista, pasndole como datos el objeto usuario que hemos recibido. Si por otro lado la validacin es
correcta redirigimos el usuario a la accin Index del controlador Home.
Bien, ahora vayamos a por la vista: la idea es que si la vista recibe un objeto de tipo Usuario rellene los campos de texto
con el valor de los campos de dicho usuario. De este modo al mandarle de vuelta el objeto desde el controlador, el
usuario ver exactamente lo mismo que l ha enviado y slo deber corregir los errores que se le indiquen. El nuevo
cdigo de la vista es:
@model MvcDatosPost.Models.Usuario
<h2>Nuevo usuario</h2>
@if (!string.IsNullOrEmpty(ViewBag.Error))
{
<div class="error">@ViewBag.Error</div>
}
<form method="POST">
<label for="login" >login:</label>
<input type="text" name="login" value="@(Model!=null ? Model.login : string.Empty)"/>
<br />
<label for="password">clave:</label>
<input type="text" name="password" />
<br />
<input type="submit" value="enviar"/>
</form>
Lo que hemos aadido respecto a la vista original es que muestre un <div> con el error en caso de que este exista y
establecer el valor del atributo value del campo login al valor del elemento recibido si existe. El valor del campo
password no lo enlazamos porque, por norma general, cuando hay un error se obliga siempre a volver entrar el
password.
Y listos! Si el usuario enva un formulario con el campo login o password vacos, se le mostrar de nuevo los datos que
haba entrada (salvo el password) junto con el mensaje de error. Sencillo, verdad? Pues bien, esa manera en que hemos
hecho la validacin, y la forma en como hemos modificado la vista para mostrar los datos devueltos por el controlador,
aunque funcionan no son las ideales. En los prximos artculos veremos dos maneras ms elegantes de hacerlo: por un
lado la validacin mediante Data Annotations y por otro el uso de los helpers en las vistas...
Un saludo!
En el artculo anterior vimos el uso de POST en ASP.NET MVC junto con las bases del Model Binding. En concreto la
norma ms importante que debemos recordad es que el atributo name del campo debe llamarse igual que la propiedad
que queremos enlazar. Por supuesto que el Model Binder admite enlazar parmetros complejos (una clase que tenga una
propiedad que sea una lista de objetos que a su vez tengan ms propiedades simples o complejas) y en este caso basta
que los atributos name de los campos del formulario sigan unas determinadas reglas. No vamos a entrar ms en detalle
porque, por suerte el framework nos ofrece una ayuda para no tener que recordar exactamente todas esas reglas: Los
helpers para formularios.
Los helpers nos van a permitir crear campos para crear un formulario de una manera muy cmoda y son sin duda alguna
la manera preferida de hacerlo.
Ahora vamos a crear una vista que muestre cuatro campos de texto (para nombre, apellido, fecha de nacimiento y
sueldo) y una casilla de verificacin para indicar si es fijo o no. Pero en lugar de crear los controles a mano (<input
type="xxx">) vamos a usar los helpers:
@using MvchelpersForms.Models
@model Trabajador
<form method="post">
</form>
El parmetro en forma x=>x.Nombre es una expresin lambda. No es objetivo de este artculo entrar en las expresiones
lambda, as que nos vamos a limitar a indicar que en este caso se usan para indicar la propiedad. La ventaja de usar una
expresin lambda antes que una cadena (algo como TextBoxFor("Nombre")) es que las expresiones lambda son
evaluadas en tiempo de compilacin y no de ejecucin (si nos equivocamos en el nombre de la propiedad el cdigo no
compila). Esto requiere que la vista sea tipada con el tipo de ViewModel (fijaos en el uso de la directiva @model).
Bien, ahora veamos que cdigo fuente nos ha generado esta vista:
<form method="post">
</form>
La verdad es que no se diferencia mucho del cdigo que nosotros pondramos a mano (que vimos en el artculo
anterior). Lo que s os puede llamar la curiosidad es el campo hidden cuyo name es "EsFijo". Este campo existe para
bueno, para lidiar con el hecho de que una checkbox no marcada, no se enva. Es decir si tenemos el campo:
<input id="EsFijo" name="EsFijo" type="checkbox" value="true" />
Eso significa que si la checkbox est marcada se enviar el valor indicado en value (true), mientras que si la checkbox
no est marcada el navegador no enviar nada. Una checkbox no marcada es como si no existiese. Pero el Model
Binder de ASP.NET MVC esperar que haya un campo EsFijo para poder enlazarlo a la propiedad, de ah que se aada
este campo hidden con el mismo nombre y valor false. De este modo si la checkbox no est marcada el campo hidden se
enva y tiene el valor de false. Habras pensado t en eso? Esas son las ventajas de usar los helpers :)
Exacto! Nos aparecen errores. En concreto el sistema nos muestra un error si FechaNacimiento o Sueldo estn vacos.
Y por qu esos dos campos y no los otros? Muy sencillo: porque esos dos campos estn declarados como DateTime y
double que son tipos por valor y por lo tanto nunca pueden valer null.
Ahora
probad de meter una cadena en el Sueldo:
Tenemos que realizar manualmente las dos tareas que el helper hace por nosotros:
Establecer la propiedad value al valor del ViewModel recibido si lo hubiese
Establecer la clase a input-validation-error en caso de haber un error vinculado en el campo que estamos
mostrando.
As pues, los helpers no son imprescindibles. Pero s que son muy cmodos y la manera recomendable de construir
formularios en ASP.NET MVC.
Hemos visto TextBoxFor y CheckBoxFor pero hay un helper por cada control HTML que habitualmente usamos en
formularios, as tenemos Html.TextAreaFor, Html.LabelFor, Html.DropDownListFor, etc
Os muestro el uso de Html.LabelFor porque es otro que se usa muchsimo. Ese genera un campo vinculado a una
propiedad. As si tenemos:
@Html.LabelFor(x=>x.Nombre) @Html.TextBoxFor(x=>x.Nombre) <br />
La etiqueta <label> se renderiza como texto plano. La ventaja de usarla es que al pulsar sobre la label el navegador da el
foco al control indicado en el atributo for (en ese caso el textbox de al lado).
Helpers "inteligentes"
Con los helpers que hemos visto hasta ahora es nuestra responsabilidad la de decidir qu control HTML mapeamos a
cada propiedad (un textbox, una checkbox, etc). Pero ASP.NET MVC incorpora dos helpers, llammosles "inteligentes"
que son capaces de determinar cul es el mejor control para visualizar o editar una propiedad concreta.
Esos dos helpers son:
HtmlDisplayFor: Renderiza el HTML necesario para visualizar una propiedad (sin poder ser editada)
HtmlEditorFor: Renderiza el HTML necesario para poder editar una propiedad.
Esos helpers son extraordinariamente potentes (tanto que van a tener un artculo para ellos solos) as que por ahora
vamos a ver solo su uso:
@using MvcHelpersForms.Models
@model Trabajador
<form method="post">
@Html.LabelFor(x => x.Nombre) @Html.EditorFor(x => x.Nombre)
<br />
Apellido: @Html.EditorFor(x => x.Apellido)
<br />
Fecha Nacimiento: @Html.EditorFor(x => x.FechaNacimiento)
<br />
Sueldo: @Html.EditorFor(x => x.Sueldo) <br />
Es Fijo: @Html.EditorFor(x => x.EsFijo)
<br />
<input type="submit" value="Enviar" />
</form>
Fijaos que ahora usamos solamente Html.EditorFor. El cdigo HTML generado ahora es:
<form method="post">
<label for="Nombre">Nombre</label> <input class="text-box single-line" id="Nombre" name="Nombre"
type="text" value="" />
<br />
Apellido: <input class="text-box single-line" id="Apellido" name="Apellido" type="text" value="" />
<br />
Fecha Nacimiento: <input class="text-box single-line" id="FechaNacimiento" name="FechaNacimiento"
type="text" value="" />
<br />
Sueldo: <input class="text-box single-line" id="Sueldo" name="Sueldo" type="text" value="" /> <br />
Es Fijo: <input class="check-box" id="EsFijo" name="EsFijo" type="checkbox" value="true" /><input
name="EsFijo" type="hidden" value="false" />
<br />
<input type="submit" value="Enviar" />
</form>
Este cdigo es casi idntico al que tenamos antes. Html.EditorFor renderiza un cuadro de texto para cada propiedad a
excepcin de la booleana para la cual renderiza una casilla de verificacin.
La pregunta evidente es: Si existe EditorFor para que usar el resto de helpers? Pues bien, cuando queris vosotros
decidir cul es el control HTML se usa para visualizar o mostrar una propiedad podis usar los helpers que vimos antes.
Cuando queris que sea el sistema usad EditorFor (o DisplayFor si estis realizando una vista que no permita editar).
Como ya os he avanzado antes EditorFor y DisplayFor son extraordinariamente potentes y con ellos se pueden hacer
autnticas maravillas, pero eso lo veremos en un artculo prximo.
El helper BeginForm
Bueno, ya que hablamos de helpers para generar formularios este es un buen momento para introducir el helper
BeginForm. Este helper lo que genera es el tag <form>. Bueno, de hecho el tag <form> y su parejo </form>. Es por ello
que ese helper se "usa" de una forma un poco distinta:
@using (Html.BeginForm()) {
<!-- Codigo del formulario -->
}
El cdigo que genera el formulario se incluye dentro de las llaves de apertura y cierre. Cuando se encuentre la llave de
cierra se generar el tag </form>. Al usar BeginForm() debemos tener un poco ms de cuidado con Razor. El siguiente
cdigo da error:
@Html.LabelFor(x => x.Nombre) @Html.EditorFor(x => x.Nombre)
<br />
Apellido: @Html.EditorFor(x => x.Apellido)
<br />
Fecha Nacimiento: @Html.EditorFor(x => x.FechaNacimiento)
<br />
Sueldo: @Html.EditorFor(x => x.Sueldo) <br />
Es Fijo: @Html.EditorFor(x => x.EsFijo)
<br />
<input type="submit" value="Enviar" />
}
La razn es que Razor interpreta que debe ejecutar el cdigo que se encuentra entre las llaves. Por supuesto, como ya
vimos, Razor es lo suficientemente inteligente para "ignorar" el cdigo HTML (como <br />) pero no el texto plano. Es
decir, Razor intentar ejecutar el cdigo Apellido: (o Es Fijo:) que un cdigo totalmente invlido en C#, de ah el error.
La solucin? La que vimos en el artculo que dedicamos a Razor, o bien usamos @: o bien usamos la etiqueta ficticia
<text> para indicarle a Razor que este texto plano es eso
texto plano, que debe mandar a la salida HTML sin ms:
@using (Html.BeginForm()) {
@Html.LabelFor(x => x.Nombre) @Html.EditorFor(x => x.Nombre)
<br />
@:Apellido: @Html.EditorFor(x => x.Apellido)
<br />
@:Fecha Nacimiento: @Html.EditorFor(x => x.FechaNacimiento)
<br />
<text>Sueldo:</text> @Html.EditorFor(x => x.Sueldo) <br />
<text>Es Fijo:</text> @Html.EditorFor(x => x.EsFijo)
<br />
<input type="submit" value="Enviar" />
}
Bueno! Suficiente por hoy, no? En este artculo hemos visto que son los helpers para formulario, como se usan y
cules son sus ventajas. En el siguiente artculo vamos a hablar de un tema importantsimo: las validaciones
En los dos artculos anteriores hemos visto como enviar datos a un controlador usando POST y como generar los
formularios usando los helpers. Introdujimos tambin el concepto de Model Binding (como ASP.NET MVC era capaz
de construir un objeto que reciba el controlador a partir de los datos de la peticin) y vimos como al usar los helpers
obtenamos gratuitamente algunas validaciones, p.ej. si declarbamos un campo como int, ASP.NET MVC comprobaba
que no entrsemos caracteres alfanumricos.
De hecho lo que vemos automticamente al usar los helpers no son validaciones, son errores. Si introducimos una
cadena alfanumrica en un campo que se ha declarado como int, cuando el Model Binder debe enlazar los datos genera
un error. Los errores que el Model Binder se encuentra al intentar enlazar los valores de la request con las propiedades
del viewmodel son guardados en el ModelState y pueden ser consultados desde el propio controlador y tambin desde la
vista. De hecho, como vimos en el artculo anterior, los helpers consultan el ModelState para usar una clase CSS
especfica en caso de que haya algn error asociado con el campo.
Intentar convertir una cadena alfanumrica en un int es pues un error, pero hay muchos ms casos en los que nos puede
interesar decirle al usuario que los datos entrados son incorrectos: campos obligatorios, cadenas con un determinado
formato (p.ej. un email) o dos campos que deben tener el mismo valor (p.ej. contrasea y comprobar contrasea). Son
esos casos cuando nos referimos a validaciones y es lo que vamos a tratar en nuestro artculo. Pero aunque nosotros
diferenciemos entre errores y validaciones ASP.NET MVC no lo hace: ambos son tratados igual, es decir ambos son
guardados en el ModelState. Pero antes de profundizar ms, veamos exactamente que es el ModelState.
if (!ModelState.IsValid)
{
return View(data);
}
else
{
// Codigo normal
return View("ok");
}
Si el ModelState no es vlido significa que alguna entrada del usuario no es correcta, por lo tanto devolvemos de nuevo
la vista que contiene el formulario con los datos. Como vimos en el artculo anterior si usamos los helpers para crear el
formulario, esos mostrarn los errores (en color rojo con la CSS por defecto). Si el ModelState es vlido eso significa
que las entradas del usuario son correctas por lo que procedemos a realizar la accin que querramos.
Y listos! Solo con aadir el atributo Required, el framework sabe que el Nombre es requerido y si el usuario no lo entra
se mostrar un error:
<div class="editor-label">
@Html.LabelFor(model => model.Nombre)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Nombre)
@Html.ValidationMessageFor(model => model.Nombre)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Edad)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Edad)
@Html.ValidationMessageFor(model => model.Edad)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
La cadena "Hay varios errores:" se mostrar antes de mostrar la lista. El primer parmetro por su parte indica si la lista
debe mostrar todos los errores (true) o solo aquellos que no estn vinculados a ninguna propiedad (false), es decir que
sean globales a todo el viewmodel (en efecto, es posible que haya errores en el ModelState que no estn asociados a
ninguna propiedad en concreto).
Validacin en cliente
Si usamos los atributos estndar (no creamos atributos propios) ya tenemos la validacin en cliente automtica. Es decir
al mismo tiempo que el usuario vaya tecleando los valores o cambie el foco ya ser irn mostrando los distintos errores
que haya. Si hay errores no podr enviar el formulario. Por supuesto la validacin en cliente no tiene nada que ver con
la seguridad, es un tema de usabilidad (darle feedback al usuario de forma ms rpida) as que el uso de validacin en
cliente no inhibe de realizarla en servidor. ASP.NET MVC la realiza siempre en servidor (y nosotros por nuestra parte
debemos comprobar siempre el valor de la propiedad IsValid del ModelState).
Si por alguna razn se desea desactivar la validacin en cliente en alguna vista, basta con llamar al mtodo
Html.EnableClientValidation con el parmetro a false:
@{ Html.EnableClientValidation(false); }
@using (Html.BeginForm())
{
// Codigo del form
}
Si creas atributos propios para validaciones personalizadas entontes es responsabilidad tuya asegurarte de que sean
compatibles para validar en cliente (tranquilo, veremos en artculos posteriores como hacerlo), pero los que vienen, lo
incluyen de serie.
if (ModelState.IsValid)
{
if (Manager.ExistePersona(data))
{
ModelState.AddModelError("", "Ya existe una persona con este nombre");
}
}
if (!ModelState.IsValid)
{
return View(data);
}
else
{
// Codigo normal
return View("ok");
}
En esta accin, si no hay errores en el modelo (es decir los datos introducidos por el usuario son correctos) el
controlador comprueba si ya existe una persona con esos datos (usando una clase inventada Manager), y si este es el
caso aade un error en el ModelState. En este momento ModelState.IsValid deja de ser true (puesto que ahora hay un
error). ModelState.AddModelError tiene dos parmetros:
1. A que propiedad se asigna el error. Si vale "" no se asigna a ninguna propiedad (se entiende que es un error
global que afecta a todo el viewmodel).
2. El mensaje de error asociado a dicho error.
Como podemos observar el que exista el error de "Ya existe una persona con este nombre" es algo que depende de los
datos introducidos pero sobretodo del estado del sistema (que exista o no ya una persona con este nombre). Estos tipos
En el artculo anterior vimos cmo usar Data Annotations para crear validaciones y como usar los helpers para mostrar
los mensajes de errores de las validaciones fallidas. Pero nos qued en el tintero ver cmo podemos crear nuestras
propias validaciones, si las que vienen de serie no nos sirven. Eso es lo que vamos a ver en este artculo.
El atributo AtributeUsage se usa para indicarle a .NET donde es vlido aplicar este atributo (esto es siempre que se
creen atributos, ya sean para validaciones o para cualquier otra tarea). Aqu estamos indicando que este atributo se
aplica a propiedades, no a mtodos o a parmetros.
Una vez creado el atributo su uso es como cualquiera de los que vienen de serie: basta con aplicarlo a las propiedades
que deseemos validar.
public class DemoModel
{
[Range(1, 100, ErrorMessage = "Positivo menor que 100")]
[NumeroPar(ErrorMessage = "El nmero debe ser par.")]
public int ValorPar { get; set; }
}
Al derivar de la clase ValidationAttribute ya obtenemos la propiedad ErrorMessage que vimos en el artculo anterior y
que nos permite especificar un mensaje de error a mostrar en caso de que la validacin sea fallida. Tambin podemos
observar como a pesar de que la clase que hemos creado se llama NumeroParAttribute para aplicarla basta con decorar
<div class="editor-label">
@Html.LabelFor(model => model.ValorPar)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ValorPar)
@Html.ValidationMessageFor(model => model.ValorPar)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Y podramos comprobar como en efecto tan solo podemos entrar nmeros pares, comprendidos entre 1 y 100:
Hemos aadido un validador llamado "numeropar" con la funcin de validacin asociada. Con esto ahora jQuery
Validate sabe que debe llamar a este mtodo javascript cuando se requiera usar el validador "numeropar".
El siguiente paso es informar a jQuery Validate cuando debe llamar a este validador "numeropar" que hemos aadido.
Para ello necesitaremos cdigo en cliente y en servidor.
La interfaz IClientValidatable
Empecemos por el cdigo de servidor: nuestro atributo de validacin debe implementar una interfaz llamada
IClientValidatable. Esta interfaz requiere que implementemos un solo mtodo llamado GetClientValidationRules, que
debe devolver una coleccin de objetos de la clase ModelClientValidationRule.
La clase ModelClientValidationRule contiene el nombre de la regla de validacin en cliente a aplicar y el mensaje de
error en caso de que dicha validacin falle. En nuestro caso una posible implementacin es:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage,
ValidationType = "numeropar"
};
}
Estamos devolviendo una coleccin con un solo elemento ModelClientValidationRule. El nombre de la regla de
validacin en cliente es numeropar y el mensaje de error es el mismo mensaje que se usa para la validacin en servidor.
Nota: Conoces la palabra clave yield? Esa palabra clave de C# permite devolver colecciones sin necesidad de crear una clase que
implemente la coleccin (usualmente una List<T>). Para ms informacin te remito al blog de Jos Manuel Alarcn donde lo cuenta de
forma fenomenal: http://www.jasoft.org/Blog/post/PermaLinkaspxguid=8dfbbe0c-7851-4cb8-8a49-150be21.aspx
Vale, hemos creado en cliente un validador llamado "numeropar" y hemos modificado nuestro atributo para indicar que
debe usarse la regla de validacin en cliente llamada "numeropar". Parece que todo debera funcionar
pero todava nos
queda un ltimo detalle.
Todos los atributos data-val que contiene el <input> son para las validaciones usando javascript no obtrusivo. De hecho
puedes observar que existe un atributo llamado data-val-numeropar. Ese atributo se ha generado porque precisamente
hemos implementado IClientValidatable en nuestro atributo de servidor. Bien, por un lado tenemos un validador
llamado "numeropar" que hemos dado de alta en jQuery Validate. Por otro tenemos el atributo data-val-numeropar que
se ha generado al implementar IClientValidatable en nuestro atributo. Tan solo nos falta indicar a jQuery Validate que
debe usar el validador llamado "numeropar" en todos aquellos campos que tengan el atributo "data-val-numeropar".
Para ello debemos usar el siguiente cdigo javascript:
$.validator.unobtrusive.adapters.addBool("numeropar");
Con este cdigo se enlaza el validador numeropar de jQuery Validate, con el atributo data-val-numeropar.
De hecho en el ejemplo el mismo nombre (numeropar)
para todo, cosa que yo os recomiendo, pero realmente tenemos
dos conceptos:
1. El nombre del validador que damos de alta en jQuery Validate
2. El nombre de la regla de validacin, es decir el nombre del atributo data-val-xx (donde xx se sustituye por el
nombre de la regla de validacin).
No es obligatorio usar el mismo nombre. Con el siguiente cdigo, dais de alta un validador llamado "numeropar" y lo
vinculis a la regla de validacin "np":
$.validator.addMethod("numeropar", function (value, element, param) {
return value % 2 == 0;
});
$.validator.unobtrusive.adapters.addBool("np", "numeropar");
Fijaos en el segundo parmetro del mtodo addBool: indica que la regla "np" debe validarse usando el validador
"numeropar" (si no se pone el parmetro se asume que el nombre es el mismo).
Por supuesto ahora la regla se llama "np", y no "numeropar", por lo que cuando se implemente IClientValidatable debe
usarse "np":
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage,
ValidationType = "np"
};
Ahora s! Hemos creado una validacin propia y que se valida no solo en servidor sino tambin en cliente!
En resumen
Hemos visto cmo funciona el sistema de validaciones basadas en atributos en ASP.NET MVC3. Tambin hemos visto
lo sencillo que es crear nuestras propias validaciones usando atributos propios y como aadir validacin en cliente en
javascript. Por supuesto os animo a que entendis como funciona jQuery Validate (hemos visto el tipo de validadores
ms sencillos que existen, los que slo validan un valor, pero los hay que pueden recibir parmetros para validar rangos,
o hacer comparaciones). No entraremos ms en detalle en jQuery Validate porque cae fuera del mbito de este manual.
Y sobre las validaciones? Pues no hemos terminado todava
hay un par de cosillas ms que creo interesantes y que
veremos en el siguiente artculo!
Validaciones cruzadas
En este artculo del manual de ASP.NET MVC vamos a hablar sobre las validaciones cruzadas y las
validaciones remotas, dos mecanismos adicionales a los ya vistos para casos ms especficos.
En los dos artculos anteriores del manual de ASP.NET MVC hemos visto como usar los atributos de Data Annotations
para realizar validaciones y como crearnos nuestras propias validaciones tanto en servidor como en cliente.
Pero hay un escenario en el que el uso de Data Annotations no termina de encajar del todo bien: las validaciones
cruzadas. Es decir cuando la validacin de un campo depende del valor de otro campo. As, un campo puede ser
obligatorio solo si en otro campo se ha entrado un valor especfico, o bien podemos tener dos campos mutuamente
excluyentes pero que uno de los dos deba ser informado s o s. Seguro que se te ocurren mil ejemplos!
Tcnicamente usando Data Annotations (en ASP.NET MVC 3) es posible crear este tipo de validaciones, p.ej. el
atributo Compare compara el valor de dos propiedades y falla si no son iguales (se usa para el caso tpico de entrar
password y comprobar password en formularios de registro). Pero para escenarios ms complejos que puedan
involucrar varias propiedades, en casos en que sea necesario tratar al viewmodel como un todo, ASP.NET MVC
proporciona un mecanismo muy sencillo y eficaz: la interfaz IValidatableObject.
IValidatableObject
Esta interfaz, que forma parte del .NET Framework 4, es usada por el runtime de ASP.NET MVC para realizar las
validaciones cruzadas. Tiene un solo mtodo llamado Validate que debe devolver una coleccin de resultados de
validacin. Su implementacin es trivial:
public class UsuarioViewModel : IValidatableObject
{
public string Nombre { get; set; }
public string Password { get; set; }
public string CompararPassword { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrEmpty(Nombre))
{
yield return new ValidationResult("No puede estar vaco.", new List<string> { "Nombre" });
}
if (Password != CompararPassword)
{
yield return new ValidationResult("Deben ser iguales.", new List<string> { "Password",
"CompararPassword" });
}
}
}
Listado : Implementacin de IValidatableObject
La clave de este mtodo es que slo devuelve errores. No devuelve true o false para indicar si todo ha ido bien o no.
Simplemente devuelve una coleccin de todos los errores encontrados. Cada ValidationResult devuelto contiene el
mensaje de error y (opcionalmente) una lista con los nombres de las propiedades que se han visto involucradas en este
error (a nivel tcnico de ASP.NET MVC podemos decir que el valor que devolvamos aqu se aade a la coleccin Error
de todas aquellas entradas del ModelState cuya clave est contenida en esta lista de nombres de propiedades).
Dada esta implementacin de IValidatableObject si el usuario deja el nombre vaco y entra dos passwords que no
coinciden, el ModelState que recibe el controlador es:
Fjate como el primer error devuelto (que el nombre no puede estar vaco) est asociado a la coleccin Errors del
elemento 0 del ModelState (cuya clave es Nombre). Por otro lado el error de que las passwords deben ser iguales est
asociado tanto al elemento 1 (Password) y 2 (CompararPassword) del ModelState, porque en el mtodo Validate lo
hemos devuelto asociado a esas dos propiedades. Si se usan los helpers para crear formularios (que ya hemos visto en
este manual) la pantalla mostrar todos los errores.
En resumen, IValidatableObject proporciona un mecanismo rpido y sencillo para realizar validaciones cruzadas en
nuestros viewmodels.
Una ltima observacin a tener presente es que ASP.NET MVC tan solo invoca el mtodo Validate si las validaciones
por atributos han pasado. Es decir, si hay un solo atributo de Data Annotations que falla, el mtodo Validate() no es
invocado.
Validaciones remotas
El ltimo tipo de validacin que nos queda por ver, es la validacin remota. Esta validacin es validacin en cliente (por
lo que como digo siempre es un tema de usabilidad y no de seguridad) y consiste en llamar usando Ajax a una funcin
de un controlador que nos indique si los valores actuales son correctos o no. Es pues una manera efectiva y rpida de
realizar en cliente validaciones que requieren acceder a recursos del servidor. P.ej. se podra validar que un nombre de
un usuario no est dado de alta. Esta validacin se realizara en cliente pero implica ejecutar un mtodo del servidor (ya
que es donde se pueden consultar todos los usuarios de la aplicacin). El mecanismo de validaciones remotas de
ASP.NET MVC pone muy fcil el realizar estas validaciones al encargarse automticamente de realizar todas las
llamadas Ajax.
Quizs los lectores de este artculo conozcan ya a Eduard Toms, el ilustre colaborador que est creando para
DesarrolloWeb.com el Manual sobre el framework ASP.NET MVC. Pues bien, se conozca o no a este experto gestor de
proyectos en .NET, os informamos que el otro da estuvo con todos nosotros para ofrecernos dos horas de formacin
intensiva sobre tecnologas Microsoft y cmo ASP.NET MVC nos puede ayudar en nuestro da a da como
desarrolladores de sitios web.
El evento fue realizado en colaboracin con Microsoft, que nos cedi la plataforma Live Meeting para retransmitirlo a
todo el mundo. Nos sentimos orgullosos de afirmar que fue todo un xito! Seguro que, adems de nosotros mismos, los
asistentes se quedaron impactados de todas las cosas que pudimos aprender en poco tiempo sobre la plataforma .NET,
pero a decir verdad fueron comunicados muchos conocimientos en poco tiempo. Por ello, tanto las personas que
asistieron al evento como las personas que no pudieron estar presentes, nos han pedido publicar el vdeo de la charla de
Eduard, para verlo con ms calma desde sus ordenadores y en cualquier momento.
Dicho y hecho, en este artculo os presentamos justamente el webcast de Eduard, con toda su extensin, incluida la
intensa serie de preguntas con las que los asistentes literalmente alquilaron a nuestro ilustre ponente y que, a decir
verdad, muy gentilmente respondi.
Antes de dejaros con el vdeo, quiero sealar varios asuntos que pueden ser importantes para conocer por vuestra parte:
Grabacin del evento:
El evento se grab de dos maneras. La primera y ms lgica, fue la que la propia plataforma Live Meeting ofrece. Esa
grabacin la disponibilizar Microsoft en su propio sitio de Eventos Web, pero todava no es encuentra online en el
momento de escribir estas lneas.
La segunda opcin fue realizada por Alvaro Martnez @amargua, del equipo de DesarrolloWeb.com, que asisti a la
conferencia y grab el escritorio con la entrada de audio. Esa es la grabacin que ahora mismo estamos disponibilizando
para todos los lectores de DesarrolloWeb.com.
Tener en cuenta que, al ser la grabacin de escritorio de un asistente, pueden producirse pequeos fallos en el audio y la
imagen, que esperamos sean lo menores posibles. Nosotros hemos apreciado que la produccin final tiene la suficiente
calidad como para ser asistida sin mayores problemas.
Ejemplos realizados con el framework ASP.NET MVC:
El ponente, Eduard, nos ha comentado que en breve har disponibles a los asisntentes a la conferencia y a los que se
suscribieron a travs de DesarrolloWeb.com, los cdigos realizados en el webcast. Esperamos enlazarlos de aqu mismo
en un par de das ms.
Quieres aprender ms ASP.NET MVC?
Como se coment a lo largo de la charla, si quieres aprender mucho ms sobre este interesante framework y de la mano
de Eduard hacerte todo un experto en ASP.NET MVC, tenemos un curso que sin duda te va a interesar.
Se trata de una oportunidad excelente para aprender todo lo que has visto en el Webcast y mucho mucho ms, de una
manera detallada, pausada y con innmeros ejemplos y prcticas. Todos los que estn deseando aprender las mejores
tecnologas Microsoft para el desarrollo de aplicaciones web encontrarn en este curso todo lo que necesitan para hacer
realidad sus objetivos.
Para obtener ms informacin de este curso organizado por DesarrolloWeb.com y que tiene como tutor al propio Eduard
Toms, os recomendamos acceder a la pgina del curso: http://www.desarrolloweb.com/curso-aspnet-mvc/