Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Manual Framework ASP Net
Manual Framework ASP Net
www.desarrolloweb.com
Toda la lgica de negocio y el acceso a datos es el Modelo (en muchos casos el Modelo puede estar en uno o varios
assemblies referenciados).
Las vistas contienen, bsicamente, el cdigo que se enva al navegador, es decir el cdigo HTML (y cdigo de
servidor asociado, siempre y cuando este cdigo haga cosas de presentacin, no de lgica de negocio).
Los controladores reciben las peticiones del navegador y en base a esas, deciden que vista debe enviarse de vuelta al
navegador y con qu datos.
Eduard Toms
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.
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.
2.
3.
4.
5.
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
{
//
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>
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):
Eduard Toms
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>
Mi nombre es @ViewData["Nombre"] y puedes seguirme en
<a href="http://twitter.com/@ViewData["Twitter"]">Twitter</a>.
Este valor es un DateTime, no obstante si desde la vista queremos llamar a los mtodos de DateTime (como
10
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>
Mi nombre es @ViewBag.Nombre y puedes seguirme en
<a href="http://twitter.com/@ViewBag.Twitter">Twitter</a>. <br />
Dado de alta en: @ViewBag.FechaAlta.ToLongDateString()
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).
class Usuario
string Nombre { get; set; }
string Twitter { get; set; }
DateTime Alta { get; set; }
11
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).
Eduard Toms
12
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
13
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 />
}
14
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
15
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!
Artculo por
Eduard Toms
16
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
Pero que 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.
17
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.
18
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
);
}
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).
19
Eduard Toms
Podemos ver como la accin View recibe el parmetro author", cuyo valor ser valor de ruta con el mismo nombre (autor).
Es decir, en el caso de la URL http://servidor/Ver/Edu el valor del parmetro author ser precisamente Edu.
En el caso de parmetros opcionales, la cosa es un peln ms delicada. Supongamos que tenemos la siguiente accin:
public class HomeController : Controller
{
public ActionResult Index(int i)
{
return View();
}
}
Supongamos que esta accin se ha mapeado a partir de la entrada Default de la tabla de rutas. Si entramos una url del tipo
http://servidor/Home/Index/10 no hay problema porque el valor de id es 10. Pero, en este caso id es un parmetro
opcional, y que ocurre si entramos la URL http://servidor/Home/Index?
Recordad que un parmetro opcional, si no aparece simplemente no se crea. No es que valga 0 o null. Es que no se
crea. Por lo tanto si entramos una URL de tipo http://servidor/Home/Index recibiremos un error:
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.
20
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 lo pasa 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!
21
Artculo por
Eduard Toms
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:
Lo que ha ocurrido es que ASP.NET MVC ha intentado convertir el valor de p1 (texto) a un entero. Al no poder hacerlo,
internamente asigna null al valor del parmetro p1, pero luego cuando debe invocar el mtodo Index y pasarle un int se
encuentra que int no acepta valores null. De aqu el error que recibimos.
Como podramos evitar esto? Bueno una manera fcil y sencilla es usar int? (es decir Nullable) en lugar de int para
declarar el tipo de p1:
public ActionResult Index(int? p1, string p2)
{
// Codigo
}
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
22
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.Action recordad 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).
23
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.
Artculo por
Eduard Toms
24
Esta vista crea un formulario que ser enviado por POST. El formulario contiene:
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
25
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
26
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!
Artculo por
Eduard Toms
27