SERVICIO DE INFORMÁTICA | UNIVERSIDAD DE ALICANTE ASP.NET MVC 3 y 4 PERSONALIZACIÓN Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es/ ÚLTIMA ACTUALIZACIÓN: 13 DE OCTUBRE DE 2012 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 3º DÍA – PERSONALIZACIÓN ENTITY FRAMEWORK BÁSICO Vamos a estudiar lo básico para hacer consultas con Entity Framework. Nos creamos en la aplicación de libros con Oracle, en el controlador Home, el método BD. Los pasos para hacer la consulta son los siguientes: 1. Preparar una variable con el listado de datos que vamos a pasar al modelo. Si queremos un listado de libros, usaremos IEnumerable<Tabla> para que desde la vista podamos recorrer los datos con las plantillas básicas que nos ofrece MVC. 2. Abrir la conexión a la Entidad. Dependerá del nombre que se le haya puesto a la Entidad cuando hayamos importado el modelo. Lo más cómodo es usar using([declaración conexión]) {} porque se elimina automáticamente al salir del using. 3. Hacer la consulta y almacenar los resultados en la variable del punto 1. Lo más sencillo es obtener todos los registros de una tabla [conexión].[nombretabla].ToList(). Las tablas pasan a ser unos objetos más con los que podemos trabajar. 4. Cerrar o liberar la conexión con la entidad. Si hemos usado using no será necesario, pero en cualquier otro caso habrá que usar bd.Dispose() 5. Llamar a la vista con el modelo de datos que hemos almacenado en el punto 1 y 3. Igual que hemos hecho en otras ocasiones View([Variable]). Podría ser algo así. public ActionResult BD() { IEnumerable<CSI_LIBRO> libros; using (var bd = new EntitiesBiblioteca()) { libros = bd.CSI_LIBRO.ToList(); } return View(libros); } La nomenclatura usada para esta consulta es lambda, que para es la más sencilla, pero la tradicional de consultas (Linq) sería: libros = (from l in bd.CSI_LIBRO select l).ToList(); Como son muy sencilla ambas, que cada uno seleccione la que le sea más sencilla. Ahora creamos una vista para este método y le indicamos el modelo CSI_LIBRO y la plantilla List. Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 2 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 Si lo ejecutamos vemos el listado de libros. También incluye la creación de nuevos libros, edición, borrado. Toda esta parte la podemos eliminar. FILTRADO Cuando hacemos consulta lo normal es mostrar aquellos que cumplen una condición. La más básica podría ser la consulta por id libros = bd.CSI_LIBRO.Where(l => l.ID == 1 ).ToList(); libros = (from l in bd.CSI_LIBRO where l.ID == 1 select l).ToList(); O que esté en un intervalo libros = bd.CSI_LIBRO.Where(l => l.ID >= 1 && l.ID < 3).ToList(); libros = (from l in bd.CSI_LIBRO where l.ID >= 1 && l.ID <= 3 select l).ToList(); Si queremos consultar aquello libros que tienen en el título “El Principito” libros = bd.CSI_LIBRO.Where(l => l.TITULO == "El Principito").ToList(); libros = (from l in bd.CSI_LIBRO where l.TITULO == "El Principito" select l).ToList(); O que contienen la palabra “Principito” libros = bd.CSI_LIBRO.Where(l => l.TITULO.Contains("Principito") ).ToList(); libros = (from l in bd.CSI_LIBRO where l.TITULO.Contains("Principito") select l).ToList(); Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 3 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 Podemos hacer conversiones de mayúsculas o minúsculas de cualquier campo en la propia consulta libros = bd.CSI_LIBRO.Where(l => l.TITULO.ToUpper().Contains("PRINCIPITO") ).ToList(); libros = (from l in bd.CSI_LIBRO where l.TITULO.ToUpper().Contains("PRINCIPITO") select l).ToList(); ORDENACIÓN Disponemos de dos funciones en lambda OrderBy y OrderByDescending al que debemos indicar el campo de ordenación libros = bd.CSI_LIBRO.OrderBy(l => l.TITULO).ToList(); libros = (from l in bd.CSI_LIBRO orderby l.TITULO select l).ToList(); libros = bd.CSI_LIBRO.OrderByDescending(l => l.TITULO).ToList(); libros = (from l in bd.CSI_LIBRO orderby l.TITULO descending select l).ToList(); Si queremos ordenar por más de un campo, en lambda debemos usar ThenBy o ThenByDescending, mientras que en las consultas tradicionales lo hacemos separando los campos por comas. libros = bd.CSI_LIBRO.OrderBy(l => l.TITULO).ThenBy(l => l.ISBN).ToList(); libros = (from l in bd.CSI_LIBRO orderby l.TITULO, l.ISBN select l).ToList(); PAGINACIÓN Uno de los problemas que nos encontramos cuando trabajamos con Oracle es la paginación. Obtener un número limitado de registros y saltar hasta un determinado registro es trivial en EF. Para obtener los 2 primeros registos de una consulta libros = bd.CSI_LIBRO.OrderBy(l => l.TITULO).Take(2).ToList(); libros = (from l in bd.CSI_LIBRO orderby l.TITULO, l.ISBN select l).Take(2).ToList(); Si queremos saltar los primeros 10 registros y obtener los 2 primeros registros libros = bd.CSI_LIBRO.OrderBy(l => l.TITULO).Skip(10).Take(2).ToList(); libros = (from l in bd.CSI_LIBRO orderby l.TITULO, l.ISBN select l).Skip(10).Take(2).ToList(); Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 4 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 HELPERS (BÁSICO) HTMLHELPERS Ayer vimos las funciones relacionadas con las direcciones. Hoy nos centramos en las que realmente nos van a ayudar en el trabajo diario, las que nos generan código HTML. Aunque el listado es mucho más amplio que el de URLHelpers, lo cierto es que todos son muy parecidos. Vamos a comenzar con el elemento básico que es el formulario que recoge todos los campos con los que trabajamos. Nos creamos una método / acción en nuestro controlador para ir jugando. Le podemos llamar helpers. No le vamos a pasar ningún modelo por el momento. Editamos su vista y lo básico para un formulario en HTML es <form …></form>. La idea es que no lo escribamos directamente sino que usemos los helpers. BeginForm(s:action, s:controller, o:values) Si escribimos @using (Html.BeginForm()) { } Nos generaría el siguiente código <form action="/Home/helpers" method="post"></form> Por defecto nos genera una acción a nosotros mismos y hace la llamada con método post Con los parámetros podemos indicar que controlador / acción del action y el método Html.BeginForm("Create", "Libro", FormMethod.Get) Y generaría <form action="/Libro/Create" method="get"></form> Todos los HTMLHelpers incluyen un último parámetro abierto a añadir cualquier atributo HTML, por ejemplo id, class, etc. En el caso de que queramos indicar el id Html.BeginForm("Create", "Libro", FormMethod.Get, new {id="fTest"})) Si además quisiéramos añadir el estilo con class al ser una palabra reservadas del sistema debemos escribir la propiedad con @class. Html.BeginForm("Create", "Libro", FormMethod.Get, new {id="fTest", @class="formulario-ua"}) El resultado final sería <form action="/Libro/Create" class="formulario-ua" id="fTest" method="get"></form> Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 5 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 En caso de que no usemos using en la declaración de beginform deberemos usar endform para indicar donde acaba. @{ Html.BeginForm("Create", "Libro", FormMethod.Get, new {id = "fTest", @class = "formulario-ua"}); } … @{ Html.EndForm();} Yo usaré en todos los ejemplos using porque queda el código más agrupado. Ahora es el momento de incluir elementos en el formulario Label(s:name, o:text) Un etiqueta con texto que hace referencia a un campo (name). En caso de que no se indique el texto pondrá por defecto el nombre del campo que hayamos indicado en el primer parámetro. @using(Html.BeginForm("Create", "Libro", FormMethod.Get, new {id = "fTest", @class = "formulario-ua"})) { @Html.Label("Nombre", "Nombre:") … } En este ejemplo mostrará una etiqueta “Nombre:” que hace referencia a un campo “Nombre”. <label for="Nombre">Nombre:</label> TextBox(s:name, o:value) Crea una caja de texto con el nombre que le indiquemos y con un valor por defecto en el segundo parámetro. @Html.TextBox("Nombre", "Alberto") genera <input id="Nombre" name="Nombre" type="text" value="Alberto" /> Todo el contenido que se asigne en el valor por defecto (en cualquier HTMLHelper) se codifica automáticamente para evitar que se produzcan ataques XSS injection. CheckBox(s:name, b:checked) Genera una elemento checkbox. DropDownList(s:name, list:selectlistitems) Genera una lista desplegable en la que podemos seleccionar un único elemento. @Html.Label("Sexo") @Html.DropDownList("Sexo", new MultiSelectList(new[] {"Hombre", "Mujer"})) Genera Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 6 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 Hidden(s:name, o:value) Genera un campo oculto. ListBox(s:name, list:selectlistitems) Genera una lista de valores en la que podemos seleccionar más de un elemento. @using(Html.BeginForm("Create", "Libro", FormMethod.Get, new {id = "fTest", @class = "formulario-ua"})) { @Html.Label("Unidad") @Html.ListBox("Unidad", new MultiSelectList(new[] {"Servicio de Informática", "Selección y Formación", "Servicio de Personal"})) } Genera RadioButton(s:name, o:value, b:checked) Genera una elemento radiobutton TextArea(s:name, s:value) Crea una caja de texto de tipo textarea. INCLUIR BUSCADOR EN NUESTRA APLICACIÓN Con lo visto anteriormente preparar una caja de búsqueda totalmente funcional que busque en el listado de libros. Se podrá buscar por título o por Isbn. En caso de que se deje en blanco el término de búsqueda mostrará todos los libros. Crearemos los modelos que necesitemos, un nuevo método para el controlador Home y una vista con la caja de búsqueda. Luego crearemos una 2º versión que incluiremos el buscador como una sección dentro de la plantilla _layout.cshtml. De esta manera podremos indicar en las vistas si queremos incluirlo o no. Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 7 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 VISTAS POR DEFECTO Como ya se comentó el primer día las plantillas que usa el Visual Studio son personalizables. Se almacena por defecto en Visual Studio 2010 en la carpeta C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\ CSharp\Web\MVC 3\CodeTemplates\AddView\CSHTML Si accedemos veremos 6 ficheros con la extensión tt. Lo primero que podemos pensar es en modificarlo directamente, pero esto afecta a todos los proyectos. Además está en una carpeta que es muchas ocasiones, y por medidas de seguridad, nos pide permisos de administrador. Lo mejor es coger la carpeta CodeTemplates y copiarla a la raíz de nuestra aplicación (podemos borrar todo lo que no sea AddView\CSHTML). El siguiente paso para poder hacer modificaciones a la plantilla es cambiar el parámetro Herramienta personalizada a las 6 plantillas. Por defecto tiene el valor TextTemplatingFileGenerator. Lo dejamos vacío. Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 8 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 ¿Podemos crear nuevas plantillas y que aparezcan al crear una vista? Por supuesto. Vamos a duplicar la plantilla para a vista de listado (list.tt) para que no incluya ni dar de alta, ni poder gestionar los registros. Aunque el código no es tan legible como en Razor, buscamos los bloques de HTML y vamos eliminando los contenidos que nos interesen. El objetivo es que se vea un listado con este formato. Un problema que nos podemos encontrar es que en los listados personalizados el tipo de libro aparezca como código y no como descripción. Debemos realizar unos cambios. En el controlador, hacer un include la tabla relacionada. public ActionResult Buscar(string palabra) { IEnumerable<CSI_LIBRO> libros; using (var bd = new EntitiesBiblioteca()) { libros = bd.CSI_LIBRO.Include("CSI_TIPOLIBRO"); if (!String.IsNullOrEmpty(palabra)) { libros = libros.Where(l => l.TITULO.ToUpper().Contains(palabra.ToUpper())); } libros = libros.ToList(); } return View(libros); } En la vista cambiar el campo que es clave ajena por la [tabla relación].[campo descripción] (sólo en el caso que hayamos realizado el include porque si no es inaccesible). <td> @Html.DisplayFor(modelItem => item.CSI_TIPOLIBRO.DESCRIPCION) </td> HELPERS HTMLHELPERS (CONTINUACIÓN) ValidationSummary([Exclude property-level error]) Muestra una lista no ordenada de todos los errores que se producen al validar el formulario. Se puede validar todo o excluir los errores a nivel de las propiedades del modelo Los errores se pueden lanzar en tiempo de ejecución con la propiedad AddModelError(campo, mensaje de error) del objeto ModelState. En caso de que el campo lo dejemos vacío estamos lanzando un error a nivel de modelo ModelState.AddModelError("", "Prueba de un error general"); y si indicamos el campo lo hacemos a nivel de propiedad. ModelState.AddModelError("Nombre", "El nombre no cumple con los requisitos"); Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 9 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 Añadimos a la vista un sumario de validación @using(Html.BeginForm()) { @Html.ValidationSummary(false) } El resultado Por defecto asigna el estilo "validation-summary-errors”. ValidationMessage (s:[Nombre del campo], o:[Mensaje de error]) En caso de que no queramos que sea un sumario el que recoja todos los mensajes, si no que cada mensaje aparezca en el punto que indiquemos (normalmente a la derecha del campo), usaremos este Helper. Si no se especifica el mensaje de error, todos aquellos errores que se produzcan (o provoquemos) se visualizarán en este punto. @Html.ValidationMessage("Nombre") Se visualizará de la siguiente manera Por defecto asigna el estilo “field-validation-error”. Action(s: [nombre acción]) Nos permite llamar a un método / acción de un controlador. Puede parecerse mucho a las vistas parciales que vimos ayer y que recordaremos luego, pero lo cierto es que mientras las vistas parciales están pensadas para escribir bloques de código, action está orientada a ejecutar el proceso completo de la acción de un controlador (que incluye la generación del código con la Vista). El resultado es una cadena de texto con todo el contenido generado. @Html.Action("Cabecera") Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 10 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 ActionLink(s: [descripción], s: [acción], s: [controlador]) Permite generar enlaces a acciones determinadas de un controlador. Por ejemplo si queremos poner un enlace a la acción Index del Home usaríamos @Html.ActionLink("Página principal", "Index", "Home") Se usa en las plantillas para realizar cualquier acción con el modelo del controlador, alta, baja, edición o borrado. Dispone de muchas sobrecargas este Helper, permitiendo desde indicar protocolor, servidor y ancla, hasta definir los atributos HTML. RouteLink(s: [descripción], d: [valores ruta]) Es parecido alterior, porque genera un enlace a una ruta o una acción de un controlador. Es algo más artesanal ya que no dispone de tantas sobrecargas y todos los valores se meten en un campo o se llama a la routa por su nombre (en caso que se haya definido previamente). Si queremos enlazar con la acción “Acerca de” usaríamos @Html.RouteLink("Acerca de", new { controller = "Home", action="About"}) RenderAction(s: [nombre acción]) Es idéntica a Action con la diferencia de que no almacena el contenido en una cadena de texto si no que la escribe directamente al objeto Response, con lo que se visualiza por el navegador. Si deseamos que una acción sea sólo llamada desde Action o RenderAction pero no directamente como una dirección más en el navegador podemos usar la anotación ChildActionOnly antes de la declaración [ChildActionOnly] public ActionResult Cabecera() { } HELPERS CON EL MODELO Lo normal no será generar todo el código HTML sino que se genere a partir de un modelo, por lo tanto MVC ofrece tdo lo que hemos visto anteriormente para trabajar con modelos. Analizamos un par, pero será lo mismo para el resto de compontes. Partimos de que la vista incluye referencia al modelo por ejemplo al Libro que contiene una propiedad título @model Proyecto.Libro Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 11 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 Todos los Helpers se llaman como antes pero postponiendo For al nombre, es decir TextBox pasa a ser TextBoxFor o Label pasa a ser LabelFor. Luego para hacer referencia a una propiedad del modelo se usa un alias por ejemplo m => m.propiedad o l => l.propiedad (lo que os sea más cómodo) Si necesitamos mostrar la etiqueta, la caja de texto y la validación @Html.LabelFor(l => l.Titulo) @Html.TextBoxFor(l => l.Titulo) @Html.ValidationMessageFor(l => l.Titulo) Si lo ejecutamos lo primero que vemos es que el LabelFor de un modelo no es muy útil porque es el nombre del campo. Muchas veces siglas de un campo de la base de datos, o todo en mayúsculas. MVC incluye data annotations en los campos lo que permite personalizar la información que luego se verá con estos Helpers. Por el momento sólo vamos a ver el de la descripción, pero mañana nos centraremos en todo el tema de validación, que es donde realmente se saca el potencial. Las anotaciones se ponen con corchetes antes de la definición de la propiedad. [Display(Name = "Título del libro")] public string Titulo { get; set; } Display permite personalizar aspectos de la visualización. Con Name le indicamos el nombre o label que tendrá esta propiedad. El resultado es: PERSONALIZADOS Como es lógico MVC nos permite crear nuestros propios Helpers para darle mayor potencia a éstos. Se comportan como una función a la que se pasan parámetros si los necesita, y dentro genera el código que queremos mostrar. El formato es @Helper [Nombre Función]( [parámetros] ) @helper BreadCumb(string[] elementos) { <div> for(int i=0; i < elementos.Count(); i++) { <span>@elementos[i]</span> if (i < elementos.Count() - 1) { <text>></text> } } </div> } Luego para llamarla @NombreFunción(parámetros) @BreadCumb(new[] {"Inicio", "Administración", "Secciones"}) Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 12 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 El resultado es el siguiente Es costumbre a la hora de poner los parámetros, anteponer el nombre de cada parámetro y luego : (dos puntos). @BreadCumb(elementos: new[] {"Inicio", "Administración", "Secciones"}) Si deseamos que es helper sea reutilizables desde cualquier vista, añadimos la carpeta App_Code a nuestro proyecto (no aparece como opción en las carpetas de ASP.NET) y creamos el fichero BreadCumbHelpers.cshtml. Copiamos el código del Helper y lo guardamos. Ahora para hacerle referencia desde la vista llamaremos al helper de la siguiente manera @[Nombre fichero (sin extensión)].[Nombre del helper o función]( [parámetros]) @BreadcumbHelpers.BreadCumb(elementos: new[] { "Inicio", "Administración", "Secciones" }) Aunque puede parecer que crear un Helper es lo mismo que llamar a vistas parciales (que vimos ayer) porque ambas se usan para reaprovechar código o dejarlo más estructurado, si que es cierto que cada uno tiene su uso. Helpers personalizado está pensado para pequeños trozos de código, que generan una programación sencilla y que se comparte con diferentes vistas de tu proyecto o incluso entre varios. Partial views están orientadas a secciones de código, con el objetivo de hacer más clara la estructura. Puede contener una programación tan complicada como la vista que les llama. Disponemos de dos HTMLHelpers dedicados a trabajar con Partial views (ayer usamos el comando RenderPage). Partial(s: nombrevista) Genera una cadena de texto con la ejecución de la vista parcial @Html.Partial("_Cabecera") RenderPartial(s: nombrevista) Es idéntica a Partial con la diferencia de que no almacena el contenido en una cadena de texto si no que la escribe directamente al objeto Response, con lo que se visualiza por el navegador. @{ Html.RenderPartial("_Cabecera"); } PERSONALIZADOS CON HTMLHELPER Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 13 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 Hay otra forma de crear Helpers personalizados en el que “construimos el contenido” que queremos generar. Será una clase, con métodos que no generan HTML directamente como una vista parcial, si no que devuelve un objeto de tipo IHtmlString. Las etiquetas se generan con TagBuildery los atributos se añaden con MergeAttribute. Un ejemplo muy útil, y que encontramos en muchas páginas, es crear nuestro propio ActionLink para trabajar con imágenes. using System.Web; using System.Web.Mvc; using System.Web.Mvc.Html; using System.Web.Routing; namespace _3_MVCHelpers.Helpers { public static class HtmlImageActionLinkHelper { public static IHtmlString ImageActionLink( this HtmlHelper helper, string imageUrl, string actionName, object routeValues, object htmlAttributes ) { var builder = new TagBuilder("img"); builder.MergeAttribute("src", imageUrl); builder.MergeAttributes(new RouteValueDictionary(htmlAttributes)); var link = helper.ActionLink("[replaceme]", actionName, routeValues); var html = link.ToHtmlString().Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing)); return new HtmlString(html); } } } Para referenciarlo en nuestra propia vista, usaremos @Html.[Método] ([Parámetros]). Si queremos añadir una imagen 012.jpg que al pulsar sobre ella vaya a la acción Index usaríamos: @Html.ImageActionLink( Url.Content("~/Fotos/012.jpg"), "Index", new { id = 5 }, new { id = "imgnb", width = "100px", height = "150px", alt = "Foto playa de Alicante" } ) LISTADOS Uno de las interacciones más comunes con el usuario es mostrar listados. Puede ser un catálogo de enlaces (por ejemplo en una tienda de libros, de música), un listado de preguntas (para poder consultar las respuestas), resultado de buscar por algún termino, etc. Por tanto hay que ser versátiles a la hora de mostrar los datos porque si no es operativa el usuario dejará de usar nuestra aplicación. PAGINACIÓN Y ORDENACIÓN Los listados que hemos generado con las plantillas de ASP.NET MVC son muy básicos si lo comparamos con los GridViews de los WebForms. Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 14 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 El 5º día usaremos componentes externos que nos permite generar listados muy potentes, pero hoy nos centramos en el helper WebGrid que está a medio camino entre uno y otro y que en la mayoría de los casos nos sobrará. Explicarlo con detalle es muy complicado, pero aplicarlo en ejemplo es muy sencillo. Vamos a crear una nueva acción Grid que se consulte como el listado de Index public ViewResult Grid() { return View(db.VCSI_LIBRO.ToList()); } Ahora creamos la vista (podemos usar la plantilla de listado para ver los campos o vacía) y hacemos una definición del Grid y luego lo mostramos. @model IEnumerable<CSI_BibliotecaBDOracle.VCSI_LIBRO> @{ ViewBag.Title = "Grid"; var grid = new WebGrid( source: Model, rowsPerPage: 4); } <h2>Grid</h2> @grid.GetHtml( columns: grid.Columns ( grid.Column("Isbn", "Isbn"), grid.Column("Titulo", "Título"), grid.Column("Descripcion_TipoLibro", "Tipo de libro") ) ) El resultado en pantalla es Lo primero que haremos será pulsar en la cabecera para ordenar. No obtendremos el resultado deseado, pero si que veremos que va cambiando la URL Grid?sort=Titulo&sortdir=ASC, sort=Titulo&sortdir=DESC&page=2, dependiendo de si pulsamos ordenar o paginar. Seremos nosotros desde la acción los que gestionemos la ordenación, porque la paginación si que la realiza el propio componente. Actualizar la acción para mostrar los resultados es bastante sencill public ViewResult Grid(string sort, string sortdir) { IEnumerable<VCSI_LIBRO> libros = db.VCSI_LIBRO; if (!String.IsNullOrEmpty(sort)) Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 15 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 { switch (sort.ToLower()) { case "titulo": libros = (sortdir == "DESC" ? libros.OrderByDescending(l => l.TITULO) : libros.OrderBy(l => l.TITULO)); break; } } else libros = libros.OrderBy(l => l.TITULO); return View(libros.ToList()); } Detectamos que nos llegue un campo por el que ordenar. Por cada campo miramos si la ordenación es ascendente (por defecto) o descendente y actualizamos los libros a mostrar. PERSONALIZACIÓN Para comenzar con listados o grids el formato que nos ofrece es bastante atractivo. Si deseamos modificar la apariencia debemos recurrir a los estilos CSS. Por ejemplo WebGrid permite asignar el estilo a todos los elementos tableStyle, headerStyle, rowStyle y footerStyle. Incluso permite alternar el estilo de cada fila con alternatingRowStyle. Cuando veamos la plantilla de las aplicaciones de la UA, los listados se quedarán con el siguiente formato Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 16 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 GENERACIÓN DE VERSIONES VALENCIANO E INGLÉS IDIOMÁTICAS, CASTELLANO, CONFIGURAR Y DETECTAR EL IDIOMA POR DEFECTO La forma más cómoda de gestionar el idioma es definir el método Application_AcquireRequestState en Golbal.asax.cs. Almacenamos en la variable Session[“idioma”] el idioma por defecto. Luego la asignamos a CurrentCulture del proceso actual. En cada llamada que hagamos se llamará a este método. protected void Application_AcquireRequestState(object sender, EventArgs e) { const string idiomaDefecto = "es"; if (HttpContext.Current.Session != null) { CultureInfo ci = (CultureInfo)this.Session["idioma"]; if (ci == null) { ci = new CultureInfo(idiomaDefecto); this.Session["idioma"] = ci; } Thread.CurrentThread.CurrentUICulture = ci; Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name); } else { CultureInfo ci = new CultureInfo(idiomaDefecto); Thread.CurrentThread.CurrentUICulture = ci; Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name); } } RECURSOS El elemento base para gestionar las traducciones son los recursos. Una buena práctica es crearnos una carpeta Resources y meter todos aquellos ficheros que necesitemos. Se pueden crear a nivel de controlador o modelo o a nivel de aplicación si nuestro proyecto es muy básico. Sobre el proyectos pulsamos botón derecho Agregar > Nueva carpeta y le ponemos el nombre . Ahora sobre esta nueva carpeta Agregar > Nuevo elemento … y seleccionamos (o filtramos) Archivo de recursos. Le llamamos modelos.resx Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 17 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 Añadimos las descripciones que queremos que estén en varios idiomas Es importante que marquemos el fichero como público para que se pueda acceder desde el resto de los elementos de MVC Para crear la versión de recursos para otro idioma, copiamos y pegamos el que usemos de base y luego le añadimo .[dos digitos del idioma] antes de la extensión del fichero. Es nuestro caso para crear el fichero de recursos en inglés renombraríamos copia de modelos.resx por modelo.en.resx y traduciríamos los valores de cada una de las etiquetas. VISTAS La manera más cómoda de pasar textos traducidos a las vistas, en caso de que sean pocos, es usar el controlador y el objeto ViewBag. Para acceder a un recurso escribimos el [nombre carpeta de recursos].[ nombre recurso (sin extensión].etiqueta. ViewBag.Titulo = Resources.modelos.tituloLabel; Luego en la propia vista lo referenciamos Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 18 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 Texto del fichero de recursos: @ViewBag.Titulo<br /> Si la vista contiene muchos elementos a traducir, lo más sencillo es crear dos versiones de la vista de la misma manera que lo hemos hecho con el fichero de recursos. Cogemos la vista base index.cshtml la copiamos y la pegamos y renombramos por index.en.cshml. Hacemos los cambios que corresponda Creamos una clase para gestionar los idiomas CultureHelper. La variable Cultures almacena los idiomas con los que trabajemos. El primero de ellos será el que se usará por defecto en caso de que detectemos otro que no se corresponda con nuestro listado. using System; using System.Collections.Generic; using System.Linq; namespace _3_MvcGlobalization.Helpers { public static class CultureHelper { // Include ONLY cultures you are implementing as views private static readonly Dictionary<String, bool> Cultures = new Dictionary<string, bool> { {"es", true}, // first culture is the DEFAULT {"en", true}, {"ca", true} }; /// <summary> /// Returns a valid culture name based on "name" parameter. If "name" is not valid, it returns the default culture "en-US" /// </summary> /// <param name="name">Culture's name (e.g. en-US)</param> public static string GetValidCulture(string name) { if (string.IsNullOrEmpty(name)) return GetDefaultCulture(); // return Default culture if (Cultures.ContainsKey(name)) return name; // Find a close match. For example, if you have "en-US" defined and the user requests "en-GB", // the function will return closes match that is "en-US" because at least the language is the same (ie English) foreach (var c in Cultures.Keys) if (c.StartsWith(name.Substring(0, 2))) return c; // else return GetDefaultCulture(); // return Default culture as no match found } /// <summary> /// Returns default culture name which is the first name decalared (e.g. en-US) /// </summary> /// <returns></returns> public static string GetDefaultCulture() { return Cultures.Keys.ElementAt(0); // return Default culture } /// <summary> /// Returns "true" if view is implemented separatley, and "false" if not. Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 19 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 /// For example, if "es-CL" is true, then separate views must exist e.g. Index.es-cl.cshtml, About.es-cl.cshtml /// </summary> /// <param name="name">Culture's name</param> /// <returns></returns> public static bool IsViewSeparate(string name) { if (Cultures.ContainsKey(name)) return Cultures[name]; return false; } } } El siguiente paso es crear nuestro propio controlador que detectará el idioma y personalizará la vista. Requerimos hacer uso de dos métodos de la clase Controller: ExecuteCore y OnActionExecuted. El primero normaliza el idioma, lo almacena en una variable sesión y establece CurrentCulture. En Session[“idioma”] vamos a almacenar el idioma que se haya detectado o el que se seleccione (lo veremos luego). De esa manera lo podremos usar en cualquier vista o controlador. El segundo establece la vista que debe abrir el controlador dependiendo del idioma que se haya seleccionado. using System.Globalization; using System.Threading; using System.Web.Mvc; using _3_MvcGlobalization.Helpers; namespace _3_MvcGlobalization.Controllers { public class UaController : Controller { protected override void OnActionExecuted(ActionExecutedContext filterContext) { // Detectamos si llamamos desde una vista var view = filterContext.Result as ViewResultBase; if (view == null) // En caso de que no sea, salims return; string cultureName = Thread.CurrentThread.CurrentCulture.Name; // Buscamos si if (cultureName == CultureHelper.GetDefaultCulture()) return; // Are views implemented separately for this culture? if not exit bool viewImplemented = CultureHelper.IsViewSeparate(cultureName); if (viewImplemented == false) return; string viewName = view.ViewName; int i; if (string.IsNullOrEmpty(viewName)) viewName = filterContext.RouteData.Values["action"] + "." + cultureName; // Index.en-US else if ((i = viewName.IndexOf('.')) > 0) { // contains . like "Index.cshtml" viewName = viewName.Substring(0, i + 1) + cultureName + viewName.Substring(i); } else viewName += "." + cultureName; // e.g. "Index" ==> "Index.en-Us" view.ViewName = viewName; Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 20 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 filterContext.Controller.ViewBag._culture = "." + cultureName; base.OnActionExecuted(filterContext); } protected override void ExecuteCore() { var idioma = Session["idioma"]; string cultureName; if (idioma != null) cultureName = idioma.ToString(); else { cultureName = (Request.UserLanguages == null ? CultureHelper.GetDefaultCulture() : Request.UserLanguages[0]); if (cultureName.IndexOf("-") > 0) cultureName = cultureName.Substring(0, cultureName.IndexOf("-")); Session["idioma"] = cultureName; } // Normalizamos var normalizedCultureName = CultureHelper.GetValidCulture(cultureName); if (normalizedCultureName != cultureName) { cultureName = normalizedCultureName; Session["idioma"] = cultureName; } // Actualizamos el idioma Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName); Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultureName); base.ExecuteCore(); } } } PLANTILLA Por defecto la plantilla que se usa es la que se configure en /Views/ _ViewStart.cshtml. Por seguir con este criterio, si modificamos este fichero y usamos la variable Session[“idioma”] tendremos la plantilla que se debe usar. @{ Layout = "~/Views/Shared/_Layout." + @Session["idioma"] + ".cshtml"; } En clase lo optimizaremos para que no se produzcan errores cuando no esté definida esta variable. CAMBIAR DE IDIOMA Nuestra plantilla permite cambiar el icioma public ActionResult ChangeLanguage(string language) { Session["idioma"] = language; if (Request.UrlReferrer != null) Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 21 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 return Redirect(Request.UrlReferrer.ToString()); return Redirect("Index"); } [ @Html.ActionLink("English", "ChangeLanguage", "Home", new { language = "en" }, null) ] [ @Html.ActionLink("Español", "ChangeLanguage", "Home", new { language = "es" }, null) ] [ @Html.ActionLink("Valencià", "ChangeLanguage", "Home", new { language = "ca" }, null) ] En caso de que nos envíen un idioma que no se corresponda con el listado admitido, no habrá problemas porque en la siguiente llamada a Index (o la página desde donde se llamó) se detectará que no se corresponde con uno de los admitidos y se asignará el que tengamos por defecto. MODELO Siguiendo el ejemplo del libro de antes, si queremos que la descripción del título salga del fichero de recursos remplazamos [Display(Name = "Título del libro")] public string Titulo { get; set; } por [Display(Name = "tituloLabel", ResourceType = typeof(Resources.modelos))] public string Titulo { get; set; } En caso de que nos de un error de que no se encuentra un recurso público con ese nombre, recordar lo de marcar Public en el fichero de recursos. POCO Cuando el modelo de los datos lo gestionamos desde EF se complica la posibilidad de incluir data annotations porque no disponemos de forma sencilla el modelo y los atributos. Lo vamos a hacer de forma manual para comprender el proceso. Creamos una clase partial donde indiquemos los campos y podamos añadir las anotaciones. public partial class CSI_LIBRO { .... } Todos los campos se declaran de tipo virtual para no interferir con la declaración de EF public partial class CSI_LIBRO { public virtual decimal ID { get; set; } public virtual string ISBN { get; set; } Ahora ya podemos incluir las anotaciones sin problemas. Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 22 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 Generar código automático El proceso anterior se puede gestionar cuando tenemos tablas básicas como con las que estamos trabajando estas semanas, pero es inviable con una aplicación con varias tablas y con muchos campos cada un de éstas. Por eso EF incluye una opción de generar este código de forma automática. SI pulsamos el botón derecho sobre una tabla veremos que disponemos de la opción “Agregar elemento de generación de código”. Nos abre una ventana que tiene seleccionado por defecto la plantilla de Datos. Seleccionamos Código y nos saldrá una lista de plantillas. Seleccionamos EF 4.X POCO Entity Generator. Le ponemos un nombre en la parte inferior, ModelBiblioteca.tt y pulsamos Agregar. Volveremos al EF y aparentemente no veremos nada nuevo. Ha añadido una referencias y nos ha creado el fichero ModelBiblioteca.Context.tt y ModelBiblioteca.tt. Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 23 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 Si pulsamos sobre la flecha que hay a la izquierda de ModelBiblioteca.tt veremos que aparecen las tablas y/o vistas que tengamos en nuestro EF. Si abrimos una de estas clases veremos que es de tipo parcial y con todos los campos definidos como virtual. Ahora le podemos añadir los atributos que veamos necesarios. Si por ejemplo a los tipos de libros le incluímos: [Required] [Display(Name = "Descripción")] public virtual string DESCRIPCION Veremos los resultados cuando creemos o modifiquemos un tipo de libro. No nos permite dejarlo en blanco y la decripción del campo pasa de DESCRIPCION a Descripción. Aunque hemos visto que el proceso es muy sencillo y que se crea de forma muy rápida, debemos tener mucho cuidado con las actualizaciones en la base de datos y en el modelo EF. Un cambio en el EF implicará un cambio en el código generado con POCO. Lo más común y viendo su sencillez es borrarlo y volverlo a crear. Todas nuestras anotaciones se perderán en este proceso. Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 24 ASP.NET MVC 3 Y 4 | DESARROLLO DE APLICACIONES CON EL FRAMEWORK MVC 3 Y 4 PROYECTO CREAR TABLAS BÁSICAS, CONTROLADORES BÁSICOS Y MASTER PAGE Andrés Vallés Botella | Analista | Desarrollos propios Servicio de Informática | Universidad de Alicante Campus de Sant Vicent del Raspeig | 03690 | España http://si.ua.es/es 25