Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 12 de abril de 2011
ASP.NET MVCYa me parecía que el equipo de MVC llevaba mucho tiempo en silencio ;-). Acaba de publicarse una actualización para las herramientas de ASP.NET MVC 3 en la que se incluyen algunas novedades interesantes,  y a las que ya he podido echar un vistazo. Os comento lo que he encontrado de momento en ellas.

Novedades a primera vista

El primer pequeño cambio que notamos al instalar esta actualización, es que algunos de los cuadros de diálogo y nombres de plantillas que antes aparecían en español ahora aparecen de nuevo en inglés, como antes de instalar el paquete de idioma correspondiente. La plantilla de aplicaciones MVC 3 vuelve a llamarse “MVC 3 Web Application”, y los cuadros de diálogo vuelven su idioma original. Pero bueno, nada grave.

Nuevo cuadro de diálogo "Crear proyecto"

Justo a continuación, ya creando el primer proyecto MVC 3, vemos que en el cuadro de diálogo inicial ha sido introducida una nueva plantilla llamada “Intranet Application”, y aparece un checkbox “use HTML5 semantic markup”.

El uso de esta nueva plantilla generará un proyecto prácticamente idéntico al habitualmente creado utilizando la plantilla “Aplicación de internet”, pero en lugar de utilizar el sistema de membresía de ASP.NET, utilizará autenticación Windows. Por tanto, ni rastro de controlador Account, ni de su Modelo, ni sus vistas; sólo encontraremos instrucciones para activar en IIS 7 e IIS Express este modelo de seguridad.

Por otra parte, marcando el nuevo checkbox podemos hacer que las vistas sean generadas utilizando marcado semántico HTML5. En la práctica, esto sólo implica que en el marcado del Layout encontraremos tags como HEADER, NAV, SECTION, o FOOTER.

Más scripts, y más actualizados

Si nos quejamos de la velocidad de los chicos de MVC a la hora de sacar releases, los de jQuery no se quedan nada atrás… incluso diría que los superan. Por ejemplo, la plantilla de ASP.NET MVC 3 incluía por defecto jQuery 1.4.4, y recientemente ha aparecido la 1.5.2… en tres meses, han aparecido al menos 4 revisiones.

Actualización de jQuery vía NugetLos nuevos proyectos ASP.NET MVC vienen ya de serie con la versión 1.5.1, pero lo que es más interesante, es que ésta se incorpora en el proyecto como un paquete predescargado de Nuget.  Por tanto, nada más abrir el proyecto recién creado, o en cualquier momento a partir de entonces, podemos utilizar Nuget para obtener las actualizaciones del mismo.

Lo mismo ocurre con jquery.Validation y con jQuery.UI. En ambos casos se ha incluido la versión 1.8, y de la misma forma, pueden ser actualizados a través de Nuget.

Eso sí, un detalle a tener en cuenta es que la inclusión de jquery desde nuestro código (el <script src..> que solemos encontrar en nuestro Layout o en algunas vistas) debemos modificarla de forma manual, puesto que el nombre de archivo incluye la versión específica, y Nuget no lo actualizará por nosotros.

Otra novedad al respecto es que se incluye de serie la biblioteca Modernizr, una interesante herramienta que nos facilita la detección de características de browsers, permitiéndonos el uso de tecnologías modernas como HTML5 o CSS3, mientras mantenemos la compatibilidad con navegadores anteriores.

Más andamiaje

El cuadro de diálogo de añadir controladores ha sido potenciado considerablemente. En este diálogo, hasta ahora sólo indicábamos el nombre del controlador y si queríamos generar código de acciones para las operaciones CRUD.

Ahora, tras instalar esta actualización, tendremos a nuestra disposición una herramienta mucho más potente y compleja, que nos permite crear controladores, vistas, e incluso clases de contexto de datos para Entity Framework 4.1 (que se instala de serie en nuestros proyectos) con métodos básicos de acceso a datos.

Nuevo cuadro de diálogo para añadir controladores
Disponemos de tres opciones para crear un controlador: vacío, con acciones de edición vacías, y con vistas de lectura y edición usando Entity Framework.

Las dos primeras son equivalentes a lo que podíamos conseguir anteriormente, un controlador únicamente con la acción Index(), o con las acciones habituales CRUD, pero sin demasiado código en su interior.

La tercera opción, sin embargo, supone un gran avance respecto a las demás, puesto que permite, de un plumazo, generar todo el código que necesitamos para cubrir los habituales escenarios CRUD (consulta, altas, bajas, modificaciones y eliminación) basándose en el nuevo Entity Framework 4.1, que por cierto también acaba de salir del horno.

Obviamente, como es habitual, tendremos que retocar el código generado, pero como base no está nada mal la cantidad de trabajo que nos ahorra.

Por ejemplo, supongamos que creamos un proyecto MVC 3 vacío y añadimos a la carpeta Models una clase como la siguiente:

    public class Persona
    {
        [Key]
        public int IdPersona { get; set; }
 
        [Required]
        [StringLength(50)]
        public string Nombre { get; set; }
 
        [Required]
        [StringLength(50)]
        public string Apellidos { get; set; }
 
        public DateTime FechaNacimiento { get; set; }
    }

Como siempre, seleccionando la opción “Añadir controlador” sobre la carpeta Controllers del proyecto, podemos generar una clase controlador así:

Añadir controlador

Observad el desplegable donde se nos solicita la clase del contexto de datos. En ella podemos seleccionar el contexto de objetos (si usamos los tradicionales db-first o model-first de EF 4) o contexto de datos (usando code-first), desde donde se obtendrá la información. Además, si no existe, podemos incluso generar esta clase (para code-first) de forma automática desde este mismo punto.

A continuación, pulsando “Add”, se habrán generado automáticamente los siguientes elementos:

  • la clase PersonasController, dentro de la carpeta correspondiente, en cuyo interior encontraremos la implementación completa de las acciones Index, Details, Create, Edit y Delete,
  • vistas para cada una de las acciones anteriores, que tienen en cuenta algunas características definidas en el modelo de datos, como las anotaciones o relaciones entre entidades.
De hecho, el código generado es totalmente funcional. Si directamente ejecutamos la solución, gracias a la magia del code-first, tendremos funcionando un mantenimiento completo de la entidad Persona, sin ni siquiera necesidad de crear la base de datos de forma manual:

Mantenimiento generado

Bueno, y esto es todo de momento. Ya conforme vaya descubriendo más novedades o particularidades las iremos viendo por aquí.

Aquí tenéis el enlace para descargar la actualización de herramientas para MVC 3; en el mismo sitio encontraréis las notas de revisión, que describen los cambios introducidos.

Publicado en: Variable not found.
La ruta por defecto de ASP.NET MVC es válida para la gran mayoría de escenarios simples, permitiéndonos acceder a las acciones a través de URLs del tipo
http://{servidor:puerto}/{controlador}/{accion}
Así, dada una clase controlador con acciones como las siguientes:

public class InformacionCorporativaController : Controller
{
    public ActionResult QuienesSomos()
    {
        return View();
    }
    public ActionResult MisionVisionYValores()
    {
        return View();
    }
    public ActionResult UneteANuestroEquipo()
    {
        return View();
    }
}

… podríamos acceder a ellas utilizando direcciones tan naturales como /InformacionCorporativa/QuienesSomos, o /InformacionCorporativa/MisionVisionYValores. Sin duda, un gran avance vistas a ofrecer a nuestros usuarios un esquema de rutas limpio, intuitivo, y de paso, mejorar nuestro posicionamiento.

Sin embargo, ¿no os parece que sería mejor aún poder acceder a acciones y controladores utilizando guiones para separar los distintos términos? Me refiero a poder utilizar, por ejemplo, la URL /Informacion-corporativa/Quienes-Somos, o /Informacion-corporativa/Unete-a-nuestro-equipo.

Obviamente no podemos modificar el nombre de nuestros controladores y acciones, puesto que nuestros lenguajes de programación no permiten el uso de guiones en el nombre de los identificadores.

Ante este escenario, una posibilidad sería asignar mediante el atributo [ActionName] un nombre alternativo a cada una de estas acciones. Sin embargo, además del trabajo que esto implica, no habría una forma sencilla de conseguirlo también para los nombres de las clases controlador. Otra opción sería registrar en la tabla de rutas una entrada específica para cada una de las acciones. Aunque no tiene contraindicaciones y cubriría perfectamente nuestras necesidades, uuffff… sin duda es demasiado trabajo a estas alturas.

Afortunadamente, existen en el framework bastantes puntos de extensión donde podemos “enganchar” nuestra propia lógica al proceso de las peticiones, y emplearlos para conseguir solucionar de forma global problemas como este. Y uno de ellos es el sistema de routing.

1. ¡Convenciones al poder!

A continuación vamos a ver cómo conseguirlo de forma global utilizando el sistema de routing, y basándonos en una convención muy simple que vamos a establecer: los identificadores de controladores y acciones que contengan un guión bajo (“_”) serán convertidos a nivel de rutas en guiones.

Dado que el guión bajo es perfectamente válido en los nombre de clases y métodos, sólo tendremos que introducir en ellos este carácter en el lugar donde queremos que aparezcan los guiones, y dejar que el sistema de rutado se encargue de realizar la transformación de forma automática. Y, por supuesto, realizaremos esta transformación en sentido bidireccional, es decir, tanto cuando se analiza la URL para determinar el controlador y acción a ejecutar, como cuando se genera la dirección a partir de los parámetros de ruta.

2. La clase FriendlyRoute

A continuación, vamos a crear una nueva clase, a la que llamaremos FriendlyRoute, y que dotaremos de la lógica de transformación de guiones medios en guiones bajos, y viceversa. Nada que no podamos resolver en una escasa veintena de líneas de código:
public class FriendlyRoute : Route
{
    public FriendlyRoute(string url, object defaults) :
        base(url, new RouteValueDictionary(defaults), new MvcRouteHandler()) { }
 
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var routeData = base.GetRouteData(httpContext);
        RouteValueDictionary values = routeData.Values;
        values["controller"] = (values["controller"] as string).Replace("-", "_");
        values["action"] = (values["action"] as string).Replace("-", "_");
        return routeData;
    }
 
    public override VirtualPathData GetVirtualPath(RequestContext ctx, RouteValueDictionary values)
    {
        values["controller"] = (values["controller"] as string).Replace("_", "-").ToLower();
        values["action"] = (values["action"] as string).Replace("_", "-").ToLower();
        return base.GetVirtualPath(ctx, values);
    }
}

Del código anterior, destacar algunos aspectos:
  • sólo he creado un constructor, he de decirlo, por pura pereza. En realidad, si quisiéramos cubrir más casuística, como la introducción de restricciones o de espacios de nombre que sí permiten las distintas sobrecargas de la clase Route, deberíamos crear todas ellas para nuestra clase. Pero vaya, es trivial en cualquier caso.
  • el método GetRouteData() es utilizado por el framework para obtener los datos de ruta de una petición entrante. Como puede observarse en el código anterior, simplemente ejecutamos la lógica de la clase base, y aplicamos las oportunas transformaciones (de guiones medios a bajos).
  • el método GetVirtualPath() es utilizado para la transformación inversa, es decir, para generar una URL partiendo de valores de los parámetros de ruta. En este caso, primero retocamos ligeramente los valores del nombre de controlador y acción, transformando los guiones bajos en medios y pasándolos a minúsculas, y posteriormente invocamos a la lógica de la clase base para que genere la URL por nosotros.
A partir de este momento ya podríamos añadir en la tabla de rutas objetos de esta clase con un código como el siguiente:
routes.Add("default", 
    new FriendlyRoute(
        "{controller}/{action}/{id}", // URL con parámetros
        new { controller = "inicio", action = "portada_principal", id = UrlParameter.Optional }
    )
);

Observad que una de las ventajas de utilizar esta técnica es que a nivel de código utilizaremos siempre el nombre real de los controladores y acciones (con el guión bajo), es el routing el que realizará las transformaciones. Esto, entre otros beneficios, nos permite seguir utilizando el imprescindible T4MVC para evitar las “magic strings” en nuestro código.

3. Pero es queeee… yo estoy acostumbrado a usar routes.MapRoute()… O:-)

No pasa nada, también podemos ponértelo así de fácil. Los métodos MapRoute() que solemos usar en el método RegisterRoutes del global.asax no son más que extensores de la clase RouteCollection, por lo que podemos basarnos en esta misma idea y crear extensiones personalizadas que nos pongan más a mano el mapeo de nuestras amigables rutas:
public static class RouteCollectionExtensions
{
    public static Route MapFriendlyRoute(this RouteCollection routes, string name, string url, object defaults)
    {
        var route = new FriendlyRoute(url, defaults);
        routes.Add(name, route);
        return route;
    }
}

De esta forma, ahora bastaría con referenciar desde el global.asax el espacio de nombres donde hemos definido la clase anterior y ya podemos llenar la tabla de rutas utilizando sentencias como la siguiente:
routes.MapFriendlyRoute(
    "Default",                    // Nombre de ruta
    "{controller}/{action}/{id}", // URL con parámetros
    new { controller = "inicio", action = "portada_principal", id = UrlParameter.Optional } 
);

Así, una vez introducida la ruta anterior en lugar de la que viene por defecto en proyectos ASP.NET MVC, ya nuestro sistema podrá recibir una petición como GET /Nombre-Controlador/Nombre-Accion y mapearla hacia a la acción Nombre_Accion de la clase controlador Nombre_ControladorController.

Por si os interesa, he dejado código y demo en SkyDrive.


Friendly Route Demo


Publicado en: Variable not found.
domingo, 10 de abril de 2011
Bien, pues tras el parón de la gira Make Web Not War, volvemos a la carga con los enlaces que he ido recopilando durante este tiempo en Variable not found en Facebook y Twitter.
Y no olvides que puedes seguir esta información en vivo y en directo desde Variable not found en Facebook, o a través de Twitter.

Publicado en: Variable not found
lunes, 21 de marzo de 2011
Web Camps 2011: Make Web, not WarDurante las próximas semanas estaré de gira participando como ponente en los Web Camps que se celebrarán en cinco ciudades del país, bajo el lema “Make Web Not War”.

En estos eventos, como se puede ver en la agenda, se tratarán temas relacionados con tecnologías y desarrollo para la web:
  • Migración a HTML5
  • IE9 para desarrolladores
  • Buenas prácticas en la detección de navegadores
  • El poder de los CMS
  • ASP.NET MVC 3
El itinerario previsto es:
  • Barcelona, 22 de marzo
  • Valencia, 24 de marzo
  • Bilbao, 29 de marzo
  • Sevilla, 31 de marzo (hey, juego en casa! ;-))
  • Madrid, 5 de abril
Si estáis interesados en asistir, sólo tenéis que ir a la web oficial de la gira, e inscribiros en la ciudad más cercana. En la misma página encontraréis información sobre los horarios y ubicación exacta del evento.

¡Espero veros por allí! :-)

Publicado en: Variable not found.
domingo, 20 de marzo de 2011
Estos son los enlaces publicados en Variable not found en Facebook y Twitter desde el lunes, 14 de marzo de 2011 hasta el domingo, 20 de marzo de 2011. Espero que te resulten interesantes. :-)
Y no olvides que puedes seguir esta información en vivo y en directo desde Variable not found en Facebook, o a través de Twitter.

Publicado en: www.variablenotfound.com.
martes, 15 de marzo de 2011
ASP.NET MVCComo sabemos, en MVC 2 y MVC 3 el atributo [DisplayName] nos permite especificar en cada propiedad de las entidades del Modelo un nombre descriptivo que, entre otras cosas, es utilizado como etiqueta en formularios si empleamos el helper Html.LabelFor().

Así, dada una clase del Modelo como la siguiente:
public class Persona
{
    [DisplayName("Nombre completo")]
    public string Name { get; set; }
 
    [DisplayName("Edad actual")]
    public int Age { get; set; }
}
si implementamos una vista de edición sobre ella similar a la mostrada a continuación, vemos el resultado que obtendríamos en tiempo de ejecución:

Vista de edición y resultado en tiempo de ejecución
Este mecanismo resulta muy cómodo y útil la mayoría de las veces, sin embargo, cuando estamos desarrollando sitios multiidioma, rápidamente vamos a encontrarnos con que este enfoque no es suficiente, puesto que asocia una única descripción textual a la propiedad independientemente de la cultura activa.

A diferencia de otros atributos como las anotaciones de validación, DisplayName no incorpora ninguna forma para indicar una referencia hacia el recurso localizado, es decir, identificar el tipo (la clase) que representa al archivo de recursos (.resx) donde se encuentra la traducción del término, así como la propiedad o clave en la que lo encontraremos. Por ejemplo, el data annotation [Required] permite hacerlo de la siguiente forma:
[Required(
    ErrorMessageResourceName="NombreObligatorio", 
    ErrorMessageResourceType=typeof(Resources.Textos)
)]
public string Name { get; set; }
Ahora bastaría con crear la carpeta App_GlobalResources un archivo de recursos llamado “Textos.resx” (obviamente el nombre puede ser cualquiera, siempre que coincida con el referenciado en el parámetro ErrorMessageResourceType anterior), e introducir en él la clave “NombreObligatorio” con su correspondiente valor. Para añadir la traducción al inglés, bastaría con hacer lo mismo sobre el archivo Textos.en.resx, y así sucesivamente.

Archivos de recursos

Afortunadamente, a partir de ASP.NET MVC 3 podemos utilizar la anotación Display, disponible en System.ComponentModel.DataAnnotations a partir de la versión 4 del framework, de la siguiente forma:
[Display(Name="Nombre", ResourceType=typeof(Resources.Textos))]
public string Name { get; set; }
Como podréis intuir, el parámetro Name indica la clave del recurso incluido en la clase especificada en ResourceType.

Sin embargo, el uso de este atributo requiere que los elementos del archivo de recursos sean de acceso público, y no interno, como son por defecto cuando creamos los .resx en App_GlobalResources. Si no tenemos en cuenta este aspecto, podemos encontrarnos con un error como el siguiente:

Cannot retrieve property 'Name' because localization failed.  Type 'Resources.Textos' is not public or does not contain a public static string property with the name 'Nombre'.

Ante este error, tenemos varias fórmulas para conseguir acceso a los recursos localizados:
  • la primera de ellos a lo bestia, accediendo a la clase generada desde el archivo de recursos (por ejemplo Textos.Designer.cs) y cambiando los modificadores de la clase y sus miembros de “internal” a “public”. Obviamente, este cambio sólo nos vale hasta que se vuelva a generar el código , cosa que ocurrirá al modificar o añadir nuevas cadenas de texto, por lo que no es nada recomendable.
  • otra, sacar los recursos de la carpeta App_GlobalResources y moverlos a cualquier otra carpeta del proyecto (por ejemplo, /Recursos). Tras ello, en las propiedades del archivo .resx podemos modificar la “Herramienta personalizada” y establecerla a “PublicResXFileCodeGenerator” para asegurar su acceso público.
  • una última sería crear ensamblados satélite, lo cual requiere la añadir un nuevo proyecto de biblioteca de clases en la solución, en el que introduciremos exclusivamente los archivos de recursos localizados.
Pero si ninguna de ellas nos convence, o bien estamos todavía trabajando con MVC 2 y .NET 3.5SP1, todavía tenemos otra posibilidad:

[LocalizedDisplayNameAtribute]

Es realmente sencillo implementar un atributo que nos permita conseguir algo parecido a lo descrito anteriormente. Simplemente heredando de DisplayNameAttribute y sobrescribiendo su propiedad DisplayName podemos hacer que su valor sea tomado desde los recursos de la aplicación:
public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
    private readonly string _resourceName;
    public LocalizedDisplayNameAttribute(string resourceName)
    {
        _resourceName = resourceName;
    }
 
    public override string DisplayName
    {
        get { return Resources.Textos.ResourceManager.GetString(_resourceName); }
    }
}
Archivos de recursosObservad que para simplificar la sintaxis de uso y su implementación, estamos asumiendo que los recursos se encuentran en Resources.Textos, la clase generada automáticamente a partir del archivo Textos.resx.

De esta forma, ahora decoraremos las propiedades del modelo no con el texto que queremos que aparezca en pantalla, sino con la clave del recurso, y dado que la clase de recursos está definida en el atributo, no será necesario indicarla:
public class Persona
{
    [LocalizedDisplayName("Nombre")]
    public string Name { get; set; }
 
    [LocalizedDisplayName("Edad")]
    public int Age { get; set; }
}
Y por si os resulta de interés, he dejado un proyecto de demostración de este atributo en Skydrive.

Publicado en: Variable not found.