A raíz de dicho post, el amigo Héctor S. (¡gracias!) me envió una interesante pregunta: ¿por qué no utilizar la anotación
System.ComponentModel.DataAnnotations.
EnumDataType
para ello? Y la respuesta corta es: pues también se podría. Con sus particularidades, pero sería posible controlar los valores de entrada a un
enum
usando esta anotación.Empecemos por el principio.
EnumDataTypeAttribute
es una anotación introducida en .NET 4 que nos permite indicar que una propiedad (no necesariamente de tipo enum
) admite exclusivamente valores definidos en un enumerado. Mejor lo vemos con algo de código: public enum Sexo
{
Hombre = 1,
Mujer = 2
}
[EnumDataType(typeof(Sexo))]
public int Sexo { get; set; }
En el ejemplo anterior observamos una propiedad
Sexo
de tipo int
, lo que le permite virtualmente alojar cualquier valor entero posible. Sin embargo, el hecho de decorarla con el atributo EnumDataTypeAttribute
y asociarla a la enumeración Sexo
hará que en la validación de la entidad su valor sea considerado incorrecto si éste no pertenece a los permitidos en ella, en este caso, 1 y 2.Su uso es interesante, por ejemplo, como método para acercar representaciones de la información. Por ejemplo, si la propiedad
Sexo
está mapeada hacia un campo de base de datos, es bastante habitual que éste sea de tipo entero, por lo que el hecho de declararla en la entidad como int
facilita el movimiento de datos entre uno y otro. Al decorarla con EnumDataType
lo que hacemos simplemente es asegurar que contendrá un valor correcto conforme al enumerado, pero ojo, siempre que la entidad sea validada considerando sus anotaciones (como ocurre durante el binding).Y volviendo al tema del post, entonces, ¿por qué no utilizamos este método para asegurar la validez de los datos en el post al que hacía referencia, en lugar de montar un binder personalizado?
Pues el motivo principal sería la pereza. Realmente, me parece demasiado trabajoso el tener que decorar cada propiedad
enum
del modelo con una declaración tan redundante como la siguiente: public class Persona
{
public string Nombre { get; set; }
[EnumDataType(typeof(Sexo))]
public Sexo Sexo { get; set; }
...
}
A ver, si estoy declarando una propiedad de tipo
Sexo
, me resulta pesado tener que decirle algo más arriba que ésta es un tipo de datos enumerado de tipo Sexo
.Pero bueno, salvando este incómodo detalle,
EnumDataType
es una buena fórmula para controlar la corrección de los valores de entrada, y sobre todo muy cómoda si la propiedad parámetro a actualizar es de un tipo no enum
, como un int
:. [EnumDataType(typeof(Sexo))]
public int Sexo { get; set; }
Su uso aporta, además, otras posibilidades interesantes. Por ejemplo, sería posible crear editores personalizados sobre el tipo
int
que, basándose en este atributo, mostraran a nivel de vista desplegables u otros controles de selección de forma automática.Es importante tener en cuenta que, a diferencia de mi enfoque, la comprobación del atributo se realiza durante el proceso de validación, que es posterior al binding en sí. La ventaja de esto es que se integra de forma natural en dicho proceso (por ejemplo, ModelState.IsValid retornará falso si hemos intentado introducir un "3" en Sexo), pero también pueden producirse errores previos generados por el propio binder si no es capaz de realizar la transformación de forma automática (por ejemplo, al intentar un valor textual no presente en la enumeración, como 'Gato'), que de la otra forma sí sería posible controlar.
Publicado en: Variable not found.
Publicado por José M. Aguilar a las 10:15 a. m.
Etiquetas: asp.net, aspnetmvc, desarrollo, trucos
- Discriminar acciones según el nombre de los parámetros, por Pablo
Fecha: 09/09/2011 - OpenRa: clon open source del clásico Command&Conquer escrito en C#.
Fecha: 08/09/2011 - MonoDevelop 2.6 is out, by Miguel de Icaza.
Fecha: 08/09/2011 - Serie "Javascript OMGs!", by Mike Taulty.
Fecha: 08/09/2011 - How to write a small game using HTML5 and Javascript
Fecha: 08/09/2011 - Extending the ASP.NET error page (show me the SQL edition), vía Pablo Iglesias.
Fecha: 08/09/2011 - Creating Multiple Javascript Objects when using the Revealing Module Pattern, by Dan Wahlin.
Fecha: 08/09/2011 - Best Tools for Professional Javascript Development, by Joe Stagner
Fecha: 08/09/2011 - Entity Framework Code First Migrations Alpha 2 released.
Fecha: 07/09/2011 - TinyMCE HTML Text Editior & ASP.NET MVC - Setting It Up Has Become Easy With Nuget
Fecha: 07/09/2011 - NerdDinner 3.0 released with ASP.NET MVC 3.0, Razor, HTML5, jQuery Mobile, etc...
Fecha: 07/09/2011 - Adding Export Capabilities to the Razor WebGrid, by Mike Ceranski
Fecha: 06/09/2011 - SQL Server Compact Toolbox for Visual Studio 2010 now available, vía José Miguel Torres Fecha: 06/09/2011
- Model Binding: Obteniendo datos, por Luis Ruiz Pavón
Fecha: 06/09/2011 - ASP.NET vNext Series - Model Binding SelectMethods
Fecha: 06/09/2011 - ASP.NET MVC Mobile ViewEngines available on NuGet
Fecha: 05/09/2011 - Synchronisation in Parallel Loops.
Fecha: 05/09/2011 - Gestionando la memoria Cache en ASP.NET MVC, por Sebis.
Fecha: 05/09/2011 - CSS Tricks and the Alternate Universe of Graphics Primitives, by K. Scott Allen.
Fecha: 05/09/2011 - Using Lucene.NET search engine library in .NET applications.
Fecha: 05/09/2011 - Recibir resultados de una página en otra: Transferencias y Cross-page posting, por José Manuel Alarcón.
Fecha: 05/09/2011 - How to Navigate the HTML5 Video Maze.
Fecha: 05/09/2011 - Offline web applications: a working example.
Fecha: 05/09/2011 - Interesante comparación de herramientas de logging, vía Jorge Serrano.
Fecha: 05/09/2011
Publicado en: Variable not found
En este post vamos a ver un ejemplo ómo podemos utilizar este nuevo componente para montar en pocos segundos el sistema de membresía, roles y perfiles de una aplicación web utilizando SQL Compact Edition, algo que por otra parte no es una idea nada descabellada para sitios de pequeño calibre. Recordad que esta edición de SQL Server no requiere un servicio funcionando en el servidor (se ejecuta en el mismo proceso que la aplicación web), por lo que puede resultar muy apropiada para su uso en pequeños sitios donde no se justifica la implantación de sus hermanas mayores (express o superiores).
1. Instalamos el tooling en Visual Studio 2010 SP1
Lo primero que vamos a hacer es instalar las herramientas de SQL Compact Edition para el Visual Studio. Éstas las podéis descargar desde este enlace, aunque antes debéis aseguraros de tener instalado el Service Pack 1 de Visual Studio.Esto no es estrictamente necesario si no pensamos crear o acceder a la base de datos desde Visual Studio, aunque en cualquier caso es recomendable para poder echar un vistazo a la estructura y datos más adelante.
2. Creamos una aplicación web
Creamos ahora una aplicación Web desde Visual Studio. No importa si es ASP.NET MVC o WebForms: en la pila de tecnologías estamos un nivel por abajo, directamente jugando con características de ASP.NET, que, como sabéis, son comunes a ambos frameworks.3. Descargamos de Nuget los componentes que necesitamos
- el proveedor de datos SQL CE, que nos permitirá conectarnos a esta base de datos y acceder a su información ya sea utilizando directamente las clases de ADO.NET como Entity Framework,
- el paquete de Proveedores Universales para ASP.NET, que creará la abstracción sobre el origen de datos concreto y nos permitirá trabajar virtualmente con cualquier motor relacional (aunque, como comentaba en el post anterior, por las pruebas que he hecho de momento no sea así).
PM> Install-Package System.Web.Providers [...] Successfully installed 'System.Web.Providers 1.0.1'. PM> Install-Package SqlServerCompact [...] Successfully installed 'SqlServerCompact 4.0.8482.1'.
Si en cambio sois más de utilizar el GUI, podéis buscar en la galería los paquetes “ASP.NET Universal Providers” y “SqlServerCompact”:
4. Modificamos la cadena de conexión
El proceso de instalación del paquete de proveedores habrá modificado nuestro web.config, dejándolo listo para su utilización con SQL Express. Como no es esto lo que queremos, acudimos a este archivo y modificamos la cadena de conexión usada por los proveedores:<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=|DataDirectory|\Datos.sdf;" providerName="System.Data.SqlServerCe.4.0" /> </connectionStrings>
Observad que estamos indicando que el proveedor de datos es SqlServerCe (lo hemos descargado previamente usando Nuget), y que la base de datos se llamará datos.sdf y estará ubicada en la carpeta App_Data del proyecto.
De forma opcional, podemos aprovechar el momento para limpiar un poco el web.config, eliminando líneas que vienen en la plantilla de proyectos por defecto, pero que ya no vamos a necesitar.
5. ¡Y esto es todo!
Pues sí, eso es todo lo que necesitamos hacer. En este momento ya tenemos configurados los proveedores de membresía, roles y perfiles. De hecho, dado que las plantillas de proyecto tanto para MVC como para Webforms ya incluyen un sistema de autenticación y registro básico, podemos directamente ejecutarla y utilizar sus funcionalidades para registrarnos y autenticarnos en el sistema:Eso sí, para comenzar a definir roles y asignarlos a usuarios es necesario activar previamente el proveedor de roles (
DefaultRoleProvider
), ya sea desde la herramienta de administración, bien añadiendo en el nodo <RoleProvider>
del web.config el atributo enabled="true"
.De la misma forma, podemos activar la persistencia del estado de sesión sobre la misma base de datos estableciendo el atributo
mode="Custom"
en el nodo <sessionState>
(por defecto está configurado como "InProc"
).En cualquier caso, una vez accedamos a alguno de estos proveedores, podremos observar que se ha creado de forma automática la base de datos llamada Datos.sdf en la carpeta App_Data. Por supuesto, también podríamos haber indicado el nombre de un base de datos existente (creada por ejemplo desde el mismo VS) y el sistema habría creado únicamente las tablas e índices usados por los proveedores sin afectar al resto de objetos.
Recapitulando: si ya tenemos el tooling y el proveedor de datos para SQLCE en un proyecto, lo único que tenemos que hacer es, en primer lugar, descargar a través de Nuget el paquete de Proveedores Universales de ASP.NET, y a continuación ajustar la cadena de conexión en el web.config. Más sencillo, imposible.
He dejado en Skydrive una solución de ejemplo con un proyecto Webforms y otro MVC para que podáis probarlo de forma sencilla. En la base de datos hay un usuario creado (jmaguilar con clave jmaguilar, aunque podéis registraros desde las propias aplicaciones); para que podáis ver cómo se comporta el sistema de autenticación y membresía, no se permite el acceso a las páginas “Acerca de” a usuarios no identificados previamente.
Publicado en: Variable not found.
Lo interesante que tiene el ensamblado
System.Web.Providers
, que así es como se distribuye el componente, es que podemos modificar el motor de persistencia sobre el que trabajan simplemente tocando la cadena de conexión en el web.config, evitándonos el tener que crear proveedores en cuanto nos salíamos de los provistos por defecto en la plataforma (SQL Server). O en otras palabras, tenemos aquí un proveedor para gobernarlos a todos ;-) Como no podría ser de otra forma, la distribución del paquete de proveedores se realiza a través de Nuget, por lo que su instalación y puesta en marcha es sencillísima.
Una vez descargado e instalado el paquete “ASP.NET Universal Providers”, además de añadirse el ensamblado
System.Web.Providers
y sus correspondientes referencias al proyecto, veremos que en el web.config se ha incluido una cadena de conexión, llamada “DefaultConnection
”, que es la que será utilizada por estos nuevos proveedores. Por defecto podremos comprobar que esta conexión está configurada para trabajar sobre una instancia de usuario de SQL Express, pero simplemente modificándola podremos hacer que funcione sobre ediciones superiores de SQL Server, SQL Azure o SQL Compact.Un detalle importante es que, para evitar daños colaterales, el proceso de instalación no elimina la cadena de conexión previa (esa que vemos con la denominación “
ApplicationServices
”), ni la configuración de proveedores previa: simplemente añade el nuevo proveedor y lo establece por defecto, por lo que podemos eliminar tranquilamente esas configuraciones que ya no utilizamos.DefaultConnection
”) ya no será necesario tocar nada más; el sistema se encargará de crear las tablas necesarias para almacenar la información de usuarios, roles, perfiles, e incluso para hacer persistente el estado de sesión (si utilizamos el proveedor incluido en este componente); además, si no existe la base de datos, será capaz de crearla automáticamente si tenemos permisos para ello. Como nota negativa, seguro que habéis pensado que ya que es posible modificar el proveedor de datos a nivel de archivo de configuración, parece lógico que podamos utilizar cualquier origen de datos disponible, ¿verdad? Pues no necesariamente. He probado con el conector MySQL y no ha habido forma de echar a andar estos proveedores debido a un problema de incompatibilidad del tipo “image” en los metadatos. No sé si se debe a limitaciones del Universal Provider, o bien se trata de algún problema en la implementación del conector MySQL.
Sin duda, es un avance interesante que puede ahorrarnos bastante tiempo en esa tarea tan molesta que es montar la infraestructura de membresía y seguridad de nuestras aplicaciones. Y además, como comentaba Hanselman en su momento, estos proveedores es posible que formen parte de la próxima versión del framework, por lo que habrá que estar atentos a ellos.
En un próximo post os mostraré un ejemplo práctico, cómo utilizar este componente para poner en marcha el sistema de membership, roles y perfiles sobre una base de datos SQLCE.
Publicado en: Variable not found.
Publicado por José M. Aguilar a las 9:43 a. m.
Etiquetas: asp.net, aspnetmvc, desarrollo, novedades, webforms
Espero que te resulten interesantes. :-)
- Strongly Typed Data Controls, No más Eval y Bind!, por Luis Ruiz Pavón.
Fecha: 02/09/2011 - Esto lo haces tú en dos días, por Jorge Serrano
Fecha: 01/09/2011 - Moving Beyond Enumerations.
Fecha: 01/09/2011 - T4 Metadata and Data Annotations Template. ASP.NET MVC
Fecha: 01/09/2011 - The 4 Characteristics of Highly Effective Developers (vía Lluis Franco)
Fecha: 01/09/2011 - Detectar cambios en los datos en SQL Server, por Rodrigo Corral
Fecha: 1/09/2011 - Asynchronous scalable web applications with real-time persistent long-running connections with SignalR, por Scott Hanselman.
- Etiquetas autocerradas y tipo de contenido en XHTML y HTML5.
Fecha: 01/09/2011 - Smart Tasks y generación de eventos de controles desde la vista HTML, por Luis Ruiz Pavón
Fecha: 01/09/2011 - Sensacionales las novedades para trabajar con ISOs y VHDs en Windows 8 (vía CampusMVP)
Fecha: 01/09/2011 - HTML Editing Smart Tasks and Event Handlers generation, por ScottGu
Fecha: 01/09/2011 - Rules to Better Interfaces.
Fecha: 31/08/2011 - Nuget 1.5 Released, por Phil Haack
Fecha: 31/08/2011 - JavaScript básico en 10 minutos (vía Marc Rubiño)
Fecha: 31/08/2011 - JavaScript no es el demonio (creo)!, por Josep Maria Camps.Fecha: 29/08/2011
Publicado en: Variable not found
El caso es que ya estamos aquí de nuevo. Descansados, oxigenados, ilusionados y listos para enfrentarnos a otro emocionante año que seguro nos traerá grandes alegrías :-)
Por empezar con novedades, habréis notado le he dado un lavado de cara a la plantilla del blog, que ya se le iban notando los años. He sustituido el layout líquido por uno de ancho fijo, más apropiado para facilitar la lectura en las ya extendidas pantallas panorámicas, he simplificado algo el código pasando a usar características de CSS3 (lo siento por los que aún navegáis en galeones ;-)), y he reestructurado la columna derecha, pasando a segundo plano la publicidad de los laterales a pesar de los vastos ingresos que generaban, y potenciando la vertiente social y de comunidad del blog.
Y dicho esto, ¡queda inaugurada la temporada 2011-2012 de Variable not found!
Como siempre, nos vemos por aquí.
Publicado por José M. Aguilar a las 9:32 p. m.
Etiquetas: blogging, vacaciones, variablenotfound, variablenotfound.com
¿Y qué planes tenemos? Pues inmejorables. :-)
Este año vamos a tener la fortuna de poder pasar unos días en Mallorca, una isla que tengo muchas ganas de visitar, y donde seguro que vamos a disfrutar de lo lindo. Todo el mundo que la conoce no duda en calificarla como una auténtica maravilla, así que estamos ya locos por hacer las maletas y montarnos en el avión :-)
También, al igual que en ocasiones anteriores, pasaremos unos días en Sanlúcar de Barrameda, un pueblo que ya nos cautivó hace años y al que seguimos acudiendo cada verano a disfrutar de sus playas, sus calles, su gastronomía y todo lo que le rodea. Y por último, probablemente nos dejemos caer algunos días por El Portil, otro enclave costero al que solemos ir a descansar cada verano y algunos fines de semana (como el del viaje con sorpresa ;-)).
Así pues, dejo el blog con el piloto automático hasta el inicio de la nueva temporada, allá por finales de agosto o principios de septiembre, cuando volveremos con las pilas recargadas, los ánimos a tope y totalmente listos para afrontar el año con ganas e ilusión.
¡Felices vacaciones, amigos! :-)
PD: Como de costumbre, cierro los comentarios anónimos para que los spammers no se ceben en ellos y conviertan variablenotfound.com en un festival de enlaces a sitios inapropiados ;-)
Y aunque suene a chiste, esta vez viene con retraso; hasta ahora, cada lanzamiento de la plataforma iba acompañado por la actualización del roadmap de la siguiente revisión del producto, es decir, nada más aparecer la versión X ya sabíamos hacia dónde se dirigirían los esfuerzos para la versión X+1. En esta ocasión hemos tenido seis meses (!) para asentar la v3 antes de empezar a pensar en la 4.
Ya hemos comentado en otras ocasiones que este ritmo no es fácil de digerir ni por la comunidad de desarrolladores ni por el mercado, e incluso supone un hándicap para la difusión y generalización del uso de la plataforma, pero bueno, es lo que hay.
Otro tema también bastante criticado es la facilidad con que MVC incrementa sus números de versión, cuando realmente las novedades ofrecidas en cada lanzamiento no parecen merecer tal distinción. Días atrás comentaba el amigo Eduard Tomás vía Twitter, y no puedo estar más de acuerdo, que MVC 3 probablemente podría haber pasado perfectamente por MVC 2.1. De hecho, si os fijáis, bastantes de sus novedades más destacables (como Nuget, Razor o los Helpers) poco o nada tienen que ver con el framework MVC en sí. Y quizás con la llegada de las tools update podríamos haberlo estirado hasta la 2.2, aunque en realidad se trataba de una mejora sobre las herramientas de Visual Studio.
No sé, a veces parece que se trata simplemente de una carrera para poder igualar la numeración de versiones con la plataforma, es decir, llegar a hacer coincidir el lanzamiento de ASP.NET MVC 5 con la propia plataforma ASP.NET 5 y la entrada por la puerta grande de HTML 5. Desde luego no sería mala cosa, al menos se relajaría un poco la actual fiebre de lanzamientos de releases que sufrimos ;-)
Pero bueno, disertaciones filosóficas aparte, la verdad es que la aparición de pistas sobre lo que incluirá una nueva revisión del producto siempre es interesante. En esta ocasión, bajo el ambicioso objetivo general de “hacer de ASP.NET MVC la mejor plataforma para la creación de aplicaciones ricas y modernas para la web”, el nuevo roadmap incluye las líneas maestras y características que, a día de hoy, están siendo consideradas para MVC 4 y vale la pena echarle un vistazo.
Aunque como bien se indica se trata de un documento muy preliminar y con toda seguridad sufrirá muchas modificaciones, ya se dejan entrever las herramientas que tendremos a nuestra disposición en unos meses, y que paso a comentar.
Y empezamos por recipes (¿recetas?), una de las novedades especialmente destacadas en el roadmap. Según se describe en éste, los recipes son cuadros de diálogo distribuidos a través de Nuget, que contienen tanto el interfaz de usuario como la lógica de generación de código que automatice determinadas tareas frecuentes y que actualmente no pueden realizarse de forma sencilla.
Un escenario descrito en el mapa de rutas podría ser la inserción de un grid Ajax. Actualmente debemos implementar la vista, acciones en el controlador que retornen datos de forma asíncrona, y sus correspondientes clases en el Modelo. El uso de un “recipe” en este caso podría ayudarnos a generar estos elementos de forma automatizada, atendiendo a los parámetros indicados a través de un cuadro de diálogo.
Otro aspecto muy interesante de las recipes es que serán fáciles de crear gracias a su API simplificado. Obviamente, el objetivo no es otro que hacer que sea la propia comunidad de desarrolladores la que amplíe el “recetario” que vendrá de serie con el producto, de la misma forma que ocurrió con Nuget.
Como idea para incrementar la productividad, me parece muy acertada; de hecho, seguro ya que habíais echado en falta algo similar para simplificar la generación de código en vuestros escenarios habituales. Habrá que ver cómo se plasma en el producto para ver si realmente nos ayuda en nuestro trabajo, y cómo se enfocan algunas cuestiones como el soporte para las versiones Express (que supongo que existirá, pero habrá que verlo), las capacidades reales de generación de código de la herramienta, o la disponibilidad de utilizar las mismas funciones desde línea de comandos para los virtuosos del teclado.
En cualquier caso, sí es curioso que una de las principales novedades de MVC 4 vaya a ser algo prácticamente ajeno a la plataforma, como en su momento fue Nuget. Esto supongo que indica que, como en aquel caso, se trata de algo que probaremos primero los desarrolladores de MVC y que más adelante, si prospera la idea, podrán disfrutar desde otras plataformas.
Cambiando de tercio, también entra en escena de forma muy destacable el soporte para dispositivos móviles. Están previstos cambios simples en la plantilla inicial de proyectos MVC, la creación de nuevas plantillas específicas para proyectos de aplicaciones móviles con layouts, vistas y scripts (jQuery Mobile), o la creación de vistas específicas para estos dispositivos.
Por otra parte, el soporte para HTML5, ya tímidamente introducido en la reciente tools update, tomará ahora mayor fuerza, al incorporarse a nivel de controles de edición. De esta forma, se podrá aprovechar la potencia de la nueva versión del lenguaje de marcado para la creación de formularios con controles específicos para la introducción de fechas, valores numéricos, etc. No es nada que no pueda hacerse a día de hoy, pero sin duda es una buena noticia el traerlo ya de fábrica.
También los controladores asíncronos se van a ver beneficiados de una importante simplificación gracias al uso de las clases de procesamiento en paralelo y de las futuras mejoras de C# 5 (¡vaya, otra versión 5! ¿coincidencia?). Hasta ahora, el uso de la asincronía en las acciones, aunque eficaz, resultaba algo engorrosa; en MVC 4, el código de un controlador asíncrono podría ser tan limpio como el mostrado en el siguiente ejemplo:
public async Task<ActionResult> Index(string city) { var newsService = new NewsService(); var sportsService = new SportsService(); return View("Common", new PortalViewModel { NewsHeadlines = await newsService.GetHeadlinesAsync(), SportsScores = await sportsService.GetScoresAsync() }); }
MVC 4 también integrará mecanismos para comprimir y empaquetar archivos externos de scripts y hojas de estilo con objeto de minimizar el ancho de banda y, por tanto, los tiempos de descarga de los mismos. De nuevo, no es algo revolucionario porque hoy en día existen soluciones para ello.
Se pretende también mejorar la construcción de helpers Razor. En la versión actual, los helpers que introducimos en la carpeta
App_Code
son funcionalmente válidos, pero les falta integración con la plataforma. Por ejemplo, desde ellos no es posible acceder al contexto de la vista, y esto puede resultar bastante molesto.Y por cierto, respecto a Razor, comenta Phil Haack en su blog que de forma paralela se introducirán novedades en Razor y Webpages que podremos aprovechar desde MVC, como ocurre ahora, aunque no ha desvelado ninguna de ellas.
Por último, el roadmap recoge más novedades agrupadas como las siguientes, de las que se aportan únicamente los titulares:
- Soporte para la evolución de esquemas en Code First, de forma que las modificaciones en la estructura no afecten a los datos ya almacenados (EF Data Migrations).
- Mejoras en el soporte para pruebas funcionales y de integración. No se indica en qué dirección.
- Soporte para las WCF Web API.
- Mejoras en Ajax orientadas a reducir puntos de fricción actuales, aunque tampoco se indica en qué sentido.
- Mejoras en el cacheado de vistas y soporte para App Fabric Cache.
- Un nuevo atributo llamado
AreaAttribute
para mejorar la seguridad.
En cualquier caso, ya estoy deseando tener a mano alguna preview sobre la que poder ver estas ideas materializadas y poder opinar con mayor conocimiento de causa.
Enlaces:
- ASP.NET MVC 4 Roadmap
- Participar en la definición de MVC 4 (aportar sugerencias, votar propuestas, comentar…)
Publicado en: Variable not found.
- Using Moq to mock ASP.NET MVC HttpContextBase.
Fecha: 18/07/2011 - Building MVC Select Lists The Easy Way by Michael Ceranski
Fecha: 18/07/2011 - EF 4.1 y la pre-generación de vistas.
Fecha: 18/07/2011 - Binding de colecciones en ASP.NET MVC (ii), por Eduard Tomàs.
Fecha: 18/07/2011 - Model Metadata and Validation Localization using Conventions, by Phil Haack.
Fecha: 18/07/2011 - ¡¡Ya tenemos aquí el Roadmap de ASP.NET MVC 4!! /vía Phil Haack
Fecha: 13/07/2011 - Boris Armenta: El cuarto reto primeros pasos con HTML5 #retosmsdn
Fecha: 13/07/2011 - José Manuel Alarcón: Un problema peliagudo: "Mi aplicación Web tarda muchísimo en arrancar: ¿Usas ensamblados firmados quizá?"
Fecha: 13/07/2011 - Working with ASP.NET MVC 3 Razor Helpers & Templates ASP.NET MVC
Fecha: 13/07/2011 - Paquetes de idioma para EF 4.1.
Fecha: 13/07/2011 - What is a Closure?
Fecha: 13/07/2011 - Overriding Unobtrusive Client Side Validation Settings in ASP.NET MVC 3.
Fecha: 13/07/2011
Publicado en: Variable not found
Lorempixum permite dos vías para generar imágenes. La primera de ellas se basa en acudir al sitio web (http://www.lorempixum.com/) y utilizar el pequeño formulario que aparece para indicar los detalles de la imagen que deseamos generar.
Simplemente introduciendo o seleccionando el ancho, alto, la temática (a elegir entre las once disponibles, tales como gente, tecnología, transporte, naturaleza, etc.), y si deseamos que sea en color o escala de grises, el sistema seleccionará y generará automáticamente una imagen totalmente ajustada a nuestras pretensiones :-)
Otra posibilidad es invocar directamente al generador de Lorempixum utilizando URLs en las que introduciremos los parámetros de la imagen a generar. A continuación se muestran algunos ejemplos de llamadas, y el resultado obtenido:
Imagen aleatoria en color de 200px de ancho por 100px de alto Categoría “Sports” http://lorempixum.com/200/100/sports/ | |
Imagen aleatoria en grises de 200px de ancho por 100px de alto Categoría “Sports” http://lorempixum.com/g/200/100/sports/ (La “g” indica que queremos la imagen en grises) | |
Quinta imagen en color de 200px x 100px Categoría “Fashion” http://lorempixum.com/200/100/fashion/5/ | |
Imagen aleatoria en color de 200px de ancho por 180px de alto Categoría “City” Texto sobreimpreso “Variable not found” http://lorempixum.com/200/180/city/Variable not found/ |
Obviamente, estas direcciones podemos teclearlas directamente en el navegador para obtener las imágenes, o bien introducirlas como origen de los
<img>
de nuestras páginas, por ejemplo así: <img src="http://lorempixum.com/200/180/city" alt=”Ciudad” />
En definitiva, un servicio interesante para no tener que andar por ahí
Publicado en: Variable not found.
enum
en C#), a pesar de su comodidad e idoneidad en multitud de ocasiones, suele siempre una tarea pesada debido a la falta de soporte directo existente en algunas tecnologías.Por ejemplo, hasta la fecha, Entity Framework los ha ignorado por completo (aunque que esto va a cambiar y la próxima versión de EF sí soportará enums :-)), lo cual resulta muy incómodo en el momento de crear componentes de acceso a datos, viéndonos obligados a realizar demasiadas conversiones, o incluso a veces el plantearnos otras soluciones, como el uso de constantes numéricas en su lugar para simplificar las operaciones.
public enum Color
{
Rojo = 1,
Verde = 2,
Azul = 3
}
Ya centrados en ASP.NET MVC, un aspecto muy interesante del mecanismo de binding es su capacidad para trabajar de forma muy natural con estos tipos de datos, aunque esto pueda acarrear algunos problemillas. Veámoslo con un ejemplo partiendo del siguiente controlador:public class EnumController : Controller
{
public ActionResult Test(Color color)
{
return Content("Color: " + color);
}
}
Suponiendo que utilizamos la ruta por defecto, una petición dirigida a la dirección URL /enum/test?color=1
retornará por pantalla el texto “Color: Rojo”. Durante el binding, el framework ha recuperado el parámetro “color” presente en la query string (un “1”) y lo ha transformado en el elemento de la enumeración correspondiente con este valor.Y no sólo eso, también podemos hacer referencia al identificador asignado en la enumeración; así, una petición como
/enum/test?color=Verde
será capaz de interpretarlo de forma correcta, asignando al parámetro color
el valor apropiado.Pero en realidad no se trata de un mérito del binder, ni tan siquiera del framework MVC. Internamente, el proceso de conversión de los tipos enumerados se delega a la clase
EnumConverter
(presente en System.ComponentModel
), que a su vez llamará a Enum.Parse()
para obtener el valor apropiado desde el string
obtenido por el value provider desde la query string. Este método examina la cadena y en función de su contenido buscará de una u otra forma el miembro de la enumeración a retornar:- Si la cadena comienza por un dígito o los signos “-“ o “+”, interpreta que el contenido del
string
es el valor asignado al elemento, por lo que intentará obtenerlo partiendo de éste. Por eso el ejemplo anterior, cuando en la petición asignábamos acolor
el valor “1” funcionaba correctamente. - En caso contrario, se asume que la cadena contiene directamente el identificador (“Rojo”, “Verde”…), y se intenta localizar en la enumeración el elemento que coincida con el suministrado. Por esta razón el segundo ejemplo, cuando en la petición asignábamos a
color
el valor “verde” también funcionaba correctamente.
/enum/test/?color=Rojo,Verde
será también interpretada de forma correcta, introduciendo en el parámetro color la combinación (un "or" binario) de ambos valores, de la misma forma que lo sería /enum/test/?color=1,2
.En resumen, esta flexibilidad a la hora de interpretar los valores y obtener el elemento correspondiente del enumerado es realmente potente y da mucho juego a la hora de crear en nuestras aplicaciones acciones muy expresivas y respetuosas con el protocolo HTTP sobre el que trabajamos. Sin embargo, esta potencia tiene también unas contraindicaciones que debemos conocer.
¿Y qué ocurre cuando los valores que se envían a una acción no son correctos?
Pues aquí llegan los problemas, claro ;-). Si a la acción anterior enviamos una petición incorrecta introduciendo en el navegador una URL como/enum/test?color=Amarillo
veremos que se produce una excepción:Observad que, a diferencia de lo que podríamos esperar, la excepción no hace referencia al hecho de que el miembro
Amarillo
no pertenece al enum
, sino a que el parámetro color
no puede contener un nulo. Es decir, el framework utiliza los componentes descritos anteriormente para intentar la conversión, pero éstos retornan un nulo y, dada la firma de la acción, éste es un valor que no puede aceptarse.Para confirmarlo, sólo tenemos que utilizar un tipo anulable en el parámetro de la acción, así:
public ActionResult Test(Color? color)
...
Simplemente con esa línea ya aseguramos que la aplicación no se parará en ejecución cuando llegue un nombre incorrecto del miembro de la enumeración, simplemente deberemos controlar lo que queremos hacer en caso de que color sea nulo.
Pero un problema aún más grave tenemos cuando el valor incorrecto viene expresado en forma numérica, indicando un valor inexistente en el tipo enumerado, por ejemplo en
/enum/test?color=24
. Fijaos que el valor está fuera de los permitidos, sería equivalente a hacer lo siguiente desde código: Color color = (Color) 24;
Seguro que ya sabéis lo que ocurre en este caso: nada. A la hora de asignar valores a una variable de tipo enum
no se realiza de forma automática ningún tipo de comprobación que permita detectar que el valor no forma parte de los permitidos, por lo que la variable color
contendrá un bonito 24
que en el contexto de nuestro sistema no significa absolutamente nada :-(Y precisamente por eso decía el problema es más grave que antes, que al menos la excepción o el valor nulo en el parámetro de entrada indicaba que el valor recibido no es correcto. Si lo que nos llega es un número fuera de rango no nos enteraremos y “se colará” silenciosamente en nuestra lógica, lo que podría liar un desaguisado tremendo.
A continuación se muestra cómo podríamos gestionar en la acción la entrada de un dato incorrecto, bien sea por tratarse de un identificador inválido (que entraría un nulo) o bien un valor numérico fuera de los permitidos en la enumeración. Observad que utilizamos el método
IsDefined()
de la clase Enum
para comprobar si el valor suministrado es válido: public ActionResult Test(Color? color)
{
if (!color.HasValue || !Enum.IsDefined(typeof(Color), color))
throw new ArgumentOutOfRangeException("color");
return Content("Color: " + color);
}
Supongo que estaréis pensando, y con razón, que sería demasiado trabajo repetirlo estas comprobaciones en todas las acciones que acepten parámetros de tipo enum
, ¿verdad?Controlando los valores de entrada mediante un model binder personalizado
Sin duda, sería mejor idea aprovechar las posibilidades de extensión del framework y crear rápidamente un binder específico que sea capaz de liberar a nuestro controlador de realizar las tareas de comprobación de los parámetros de entrada de tipoenum
, como el siguiente:public class EnumBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
var type = bindingContext.ModelType;
if (value!=null && Enum.IsDefined(type, value))
return value;
throw new ArgumentOutOfRangeException(bindingContext.ModelName);
}
}
Como se observa en el código, se comprueba que el valor no sea nulo y que se encuentre entre los definidos en la enumeración, lanzando una excepción en caso contrario. Os dejo como deberes la implementación del binder que acepte enumeraciones de tipo flag, y parámetros de tipo anulable ;-)A continuación tenemos que indicar al framework que debe utilizar este model binder para las enumeraciones. Una posibilidad sería registrar en la colección
ModelBinders
una asociación para cada tipo de enumeración, algo así: ModelBinders.Binders.Add(typeof(Color), new EnumBinder());
Desafortunadamente, esta fórmula de registro de binders no funciona para jerarquías de clases; es decir, no podemos registrar un binder para la clase Enum
y que se aplique automáticamente para todas sus descendientes, por lo que si usamos muchas enumeraciones sería bastante tedioso registrar uno por uno los binders.Otra posibilidad sería utilizar los nuevos
ModelBinderProviders
, una característica introducida en ASP.NET MVC 3, que permiten introducir lógica en el momento de obtención de los binders y, por tanto, decidir qué binder queremos asignar a cada clase. El siguiente código podría ser un proveedor simple que conseguiría el efecto deseado:class MyModelBinderProvider: IModelBinderProvider
{
public IModelBinder GetBinder(Type modelType)
{
if (modelType.IsEnum)
return new EnumBinder();
return null;
}
}
Y su registro en el global.asax.cs sería así:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
ModelBinderProviders.BinderProviders.Add(new MyModelBinderProvider());
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
En cualquier caso, sea cual sea la vía utilizada, una vez asociado el binder en la inicialización de la aplicación, ya controlará por nosotros la entrada de valores incorrectos para la enumeración:
public ActionResult Test(Color color)
{
// Podemos asegurar que "color" es un color válido
return Content("Color: " + color);
}
Generación de rutas hacia acciones en cuyos parámetros hay enums
Ya hemos visto cómo funciona el mecanismo de binding en acciones entre cuyos parámetros se encuentran enums, pero, ¿cómo tenemos en cuenta esta particularidad a la hora de generar enlaces o rutas hacia estas acciones?Pues la respuesta es bien simple: como si se tratara de cualquier otro tipo de datos. El siguiente código muestra la generación de enlaces hacia la acción vista anteriormente utilizando el helper
Html.ActionLink()
, primero de forma directa y a continuación utilizando T4MVC: @Html.ActionLink("Enlace a azul", "Test", "Enum", new {color = Color.Azul}, null)
@Html.ActionLink("Enlace a verde", MVC.Enum.Test(Color.Verde))
Como se puede observar, no hay que realizar conversiones ni ningún otro tipo de malabarismos, simplemente podemos utilizar elementos de la enumeración de forma directa cuando sean necesarios :-)Publicado en: Variable not found.
- Genial!!! Eduard Tomàs: Binding de colecciones en ASP.NET MVC (y algunos comentarios) Fecha: 09/07/2011
- Gran post de Bruno Capuano: Pregunta “Porqué” como un niño, y mejorarás la gestión de tus proyectos.
Fecha: 09/07/2011 - Periodic table of HTML5 elements.
Fecha: 08/07/2011 - Object hierarchy of NULL
Fecha: 08/07/2011 - Óscar Sotorrío: Aportando un poco de coherencia en los controladores
Fecha: 08/07/2011 - jQuery UI MultiSelect Widget.
Fecha: 08/07/2011 - Repository, Code first & IoC in MVC3 (Vía Omar del Valle)
Fecha: 07/07/2011 - jQuery Proven Performance Tips And Tricks (Slides).
Fecha: 07/07/2011 - Gran post de Unai Zorrilla: Testing y EF 4.1.
Fecha: 07/07/2011 - CRUD Operation with ASP.NET MVC and EFCodeFirst Part-2.
Fecha: 06/07/2011 - Managing data in web applications with HTML5 Web Storage, by Rachel Appel.
Fecha: 06/07/2011 - Elijah Manor: "ASP.NET WebGrid - Get the Most out of WebGrid in ASP.NET MVC" ASP.NET MVC
Fecha: 06/07/2011 - Elijah Manor: "MVC Filters - Easily Add Performance Counters to Your MVC Application" ASP.NET MVC
Fecha: 06/07/2011 - jQuery 1.6.2 syntax error? You may be the victim of SEO.
Fecha: 06/07/2011 - Eduard Tomàs: Los materiales de la charla inaugural de #CatDotNet ya disponibles! ;-)
Fecha: 06/07/2011 - Real World Accessibility: HTML5, ARIA and the Modern Web.
Fecha: 06/07/2011 - Tangle: explorable explanations made easy. Vía Andrés Nieto
Fecha: 06/07/2011 - ¿Cargar jQuery desde un CDN o desde servidor? por Carlos Benítez .
Fecha: 06/07/2011 - Bin Deploy ASP.NET MVC 3 Application With SQLCE 4.0 & Entity Framework.
Fecha: 05/07/2011 - Custom Errors in ASP.NET MVC: It couldn't be simpler, right?
Fecha: 05/07/2011 - Second Nug: Evento "La revolución de las consultas... LINQ. "
Fecha: 05/07/2011 - JavaScript language advanced Tips & Tricks.
Fecha: 05/07/2011
Publicado en: Variable not found
ActionResult
que podemos utilizar como retorno de nuestras acciones (FileResult
, ContentResult
, ViewResult
, RedirectResult
, etc…) y que cubren la mayoría de escenarios de uso frecuente al desarrollar aplicaciones para este framework.Pero sin duda, lo mejor de todo es lo fácilmente que podemos extender este conjunto para lograr resultados muy potentes, reutilizables y respetuosos con el patrón.
En este post vamos a ver cómo crear en unos minutos un nuevo tipo de resultado para nuestras acciones llamado
ZipResult
, que nos permitirá generar al vuelo archivos en formato comprimido .ZIP, en cuyo interior podremos añadir los ficheros que queramos.Así, nuestro
ZipResult
recibirá una serie nombres de archivo, los comprimirá, y los retornará al usuario empaquetados en un único fichero .ZIP. A nivel de código, su uso será así de simple:public ActionResult DescargarArchivos()
{
return new ZipResult("c:\\archivo1.dat", "c:\\archivo2.dat");
}
¡Vamos allá!1. Creación de zips desde .NET
Desde la llegada de Nuget, nada ha vuelto a ser lo mismo. En unos segundos, sólo abriendo la herramienta de gestión de paquetes, seleccionando la opción “online” y haciendo una búsqueda sobre el término “zip” tenemos acceso a la oferta de paquetes relacionados con el mismo:Como podemos ver, existen muchas opciones para tratar con archivos .zip que tenemos al alcance de un clic. En este caso vamos a usar DotNetZip, una potente biblioteca open source que nos ofrece todo lo que necesitamos en este proyecto y mucho más ;-), pero a diferencia de otras, es bastante más sencilla y cómoda de utilizar.
Y como muestra el siguiente método, que recibe una lista de rutas de archivo y los comprime sobre un fichero .zip:
public void Comprime(IEnumerable<string> files)
{
using (ZipFile zf = new ZipFile())
{
zf.AddFiles(_files, false, "");
zf.Save(@"d:\prueba.zip");
}
}
El código es bastante conciso y fácil de comprender; creamos un nuevo archivo zip, representado por la instacia del tipo ZipFile
, llamamos a su método AddFiles()
suministrándole la colección de rutas de los ficheros a comprimir, y salvamos el resultado al disco. El segundo parámetro de AddFiles()
se usa para indicar si se respetan las rutas originales de los archivos y el tercero especifica el nombre de carpeta donde se almacenarán dentro del fichero .zip.2. Creación de ActionResults personalizados
Aunque estrictamente hablando no tendría por qué ser así, la práctica totalidad de las acciones en ASP.NET MVC retornan un objeto de tipoActionResult
, como en el siguiente ejemplo:public ActionResult About()
{
return View();
}
La clase abstracta ActionResult
se define en el espacio de nombres System.Web.Mvc
de la siguiente forma:public abstract class ActionResult
{
public abstract void ExecuteResult(ControllerContext context);
}
Cuando una acción retorna un subtipo de ActionResult
, el framework se encarga de invocar a su método ExecuteResult()
para que envíe el resultado al cliente. Por ejemplo, en el caso de un ViewResult
(el retorno generado por el método View()
del controlador), su método ExecuteResult()
es el responsable de ponerse en contacto con el motor de vistas para generar el HTML, y retornar el marcado al cliente; un RedirectResult
, en cambio, sólo se encargará de retornar una redirección (temporal o permanente).Por tanto, lo único que necesitamos para crear nuestro tipo de resultado personalizado es crear una clase que herede de
ActionResult
e implementar en ella el método ExecuteResult()
. En la práctica, normalmente encontraremos en este método la lógica de generación del resultado, establecimiento de encabezados de la respuesta (content-type, content-disposition, status code, etc.), y el envío a través del canal de salida de la información deseada.3. ZipResult, el ActionResult que retorna archivos .zip
A continuación se muestra el código de la claseZipResult
, que se encarga de retornar al cliente un archivo comprimido en formato .zip en cuyo interior se encontrarán todos los archivos indicados en el momento de su instanciación.Lo que vale la pena leer está prácticamente al final de la porción de código, el método
ExecuteResult()
, que es el que realmente realiza el trabajo de comprimir y enviar al cliente el archivo resultante:public class ZipResult : ActionResult
{
private IEnumerable<string> _files;
private string _fileName;
public string FileName
{
get
{
return _fileName ?? "archivo.zip";
}
set { _fileName = value; }
}
public ZipResult(params string[] files)
{
this._files = files;
}
public ZipResult(IEnumerable<string> files)
{
this._files = files;
}
public override void ExecuteResult(ControllerContext context)
{
using (ZipFile zf = new ZipFile())
{
zf.AddFiles(_files, false, "");
context.HttpContext
.Response.ContentType = "application/zip";
context.HttpContext
.Response.AppendHeader("content-disposition", "attachment; filename=" + FileName);
zf.Save(context.HttpContext.Response.OutputStream);
}
}
}
Observad que el método ExecuteResult()
es prácticamente idéntico al Comprime()
que mostrábamos más arriba para ver lo fácil que resultaba comprimir archivos con DotNetZip. Sólo le estamos añadiendo los encabezados para la respuesta HTTP, y estamos haciendo que el archivo sea salvado directamente sobre el stream de salida en lugar de hacerlo en disco.4. Uso desde el controlador
Y para utilizar nuestro flamanteActionResult
, lo único que debemos hacer es instanciarlo desde la acción, suministrarle las rutas hacia los archivos que deseamos comprimir y retornarlo como resultado:public ActionResult Descargar()
{
return new ZipResult(
Server.MapPath("~/Archivos/fich1.txt"),
Server.MapPath("~/Archivos/fich2.txt"),
Server.MapPath("~/Archivos/fich3.txt")
);
}
ZipResult
.Espero que os resulte interesante.
Publicado en: Variable not found.