Como sabréis, hace poco ha finalizado la gira “Make Web Not War”, una serie de eventos organizados por Microsoft en cinco ciudades del país en los que se trataban distintos temas relacionados con el desarrollo para la web, y donde he tenido la oportunidad de participar como ponente, hablando sobre la última versión del framework ASP.NET MVC.
Aunque me ha tenido varias semanas fuera de juego laboral y blogosféricamente hablando (y lo que todavía tardaré en recuperar la normalidad…), he de decir que ha sido un experiencia realmente impresionante, de la que salgo con muy gratos recuerdos y un buen puñado de amigos con los que espero volver a coincidir en posteriores ocasiones.
A lo que iba, unos días atrás se han publicado los materiales y los vídeos de todas las ponencias del evento de Madrid, la última ciudad de la gira, y a los que podéis acceder desde los siguientes enlaces. En general, se trata de contenidos muy atractivos para los que trabajamos con la web, y seguro que vale la pena dedicar una tarde a que fenómenos como Martín, Jorge, Boris, Alex o Rafael nos cuenten cosas interesantes. Bueno, y si queréis conocer algo de MVC 3, pues también ;-)
Estándares Web W3C Martín Álvarez-Espinar (W3C) Descargar presentación Ver vídeo | Migrar a HTML 5 es fácil… si sabes cómo Jorge Casar (Frontend Spain) Descargar presentación Ver vídeo | ||
IE9 para desarrolladores web Boris Armenta (Microsoft) Descargar presentación Ver vídeo | Una web más interoperable Alejandro Hidalgo (Plain Concepts) Descargar presentación Ver vídeo | ||
El poder de los CMS Rafael Gerardo (Vector SF) Descargar presentación Ver vídeo | ASP.NET MVC3 José María Aguilar Descargar presentación Ver vídeo |
Publicado en: Variable not found.
- Solucionar error al instalar helpers MVC de Facebook, por Lluis Franco
Fecha: 21/04/2011 - Lluis Franco: Sesiones del MIX categorizadas
Fecha: 15/04/2011 - Top 13 JavaScript Mistakes.
Fecha: 14/04/2011 - ASP.NET MVC 3 and EF4.1 Code First: Here are my classes, now you do the rest.
Fecha: 14/04/2011 - Gran lista de bibliotecas gratuitas para .net.
Fecha: 14/04/2011 - EF 4.1. version stamping, por Unai Zorrilla.
Fecha: 13/04/2011 - Phil Haack: Announcing ASP.NET MVC 3 Tools Update
Fecha: 13/04/2011 - Cartujadotnet: grupo de usuarios .net en Sevilla.
Fecha: 13/04/2011 - Ugo Lattanzi: ASP.NET MVC Tools update to support EF4.1, jQuery update and fixes
Fecha: 12/04/2011 - Nuget needs your input, by Phil Haack.
Fecha: 12/04/2011 - Using NonActionAttribute to restrict access to public methods of controller in ASP.NET MVC, by Gunnar Peipman.
Fecha: 12/04/2011 - Ado.net Entity Framework 4.1 RTM is out! Cool!
Fecha: 12/04/2011 - Cuadro de texto con autocompletado usando jQuery, Autocomplete y ASP.NET MVC, por luisruizpavon.
Fecha: 12/04/2011 - IE9ify, plugin de jQuery para pinear sitios, añadir tasks y jumplists en IE9, por Gonzalo Pérez
Fecha: 12/04/2011
Publicado en: Variable not found
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.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.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.
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í:
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.
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.
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 llamaremosFriendlyRoute
, 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.
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étodosMapRoute()
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.
Publicado en: Variable not found.
- Ya están las fotos de los #eswebcamp de Barcelona, Valencia, Bilbao, Sevilla y Madrid publicadas
Fecha: 07/04/2011 - Eduard Tomás: ¡Mis primeros artículos en desarrolloweb! Se avecina un gran tutorial de ASP.NET MVC: http://kcy.me/2l2b y http://kcy.me/2l2c
Fecha: 06/04/2011 - David Ebbo: Blogged: Open your solution files as admin
Fecha: 04/04/2011 - Eduard Tomàs: [BlogPost] Como usar el pathInfo para pasar parámetros en ASP.NET MVC
Fecha: 04/04/2011 - Josué Yeray Julián: ¿Tienes Windows Phone 7? ¿Te gusta Geeks.ms? No te pierdas la aplicación que hemos preparado para ti!!
Fecha: 04/04/2011 - Lanzado jQuery 1.5.2.
Fecha: 01/04/2011 - El gran Phil Haack responde a una de las consultas del #eswebcamp de ayer: cómo crear tu propia galería Nuget.
Fecha: 01/04/2011 - "Javascript for c# developers", gran serie de posts de JMBucknall.
Fecha: 01/04/2011 - Phil Haack: Alright, NuGet 1.2 released! :) And check out the updated NuGet.exe and Package Explorer! :)
Fecha: 31/03/2011 - Libro gratuito de jQuery en español: Fundamentos de jQuery.
Fecha: 31/03/2011 - Scaffolding Actions and Unit Tests with MvcScaffolding.
Fecha: 30/03/2011 - Rounding corners using CSS3.
Fecha: 30/03/2011 - Jorge Serrano: Patrón Observador en .NET - pattern Observer
Fecha: 30/03/2011 - Eduard Tomàs: TIP: Javascript Module Pattern (mantén organizado tu código javascript).
Fecha: 30/03/2011 - Wow!! RT Josué Yeray Julián: Ya está disponible el 1º capítulo de mi libro sobre #wp7 gratis y de libre descarga
Fecha: 27/03/2011 - When should a method be a property?
Fecha: 25/03/2011 - Convention based localization with ASP.NET MVC.
Fecha: 25/03/2011 - Building an ASP.NET MVC 3 app with code first and EF 4.1.
Fecha: 25/03/2011 - Scaling a #javaScript codebase
Fecha: 20/03/2011 - Rumores: algunas características de ASP.NET MVC que quizás pasen a la próxima versión de Webforms.
Fecha: 20/03/2011 - Phil Haack: How model binding to decimals in ASP.NET MVC is broken and how to fix it using a custom model binder! :)
Fecha: 19/03/2011 - José Manuel Alarcón: En mi blog técnico: "Cómo crear URLs amigables personalizadas con ASP.NET Web Forms"
Fecha: 18/03/2011 - Ugo Lattanzi: The Big View Engine Comparison – Razor vs. Spark vs. NHaml vs. Web Forms View Engine
Fecha: 16/03/2011 - Gisela Torres: Refrescar un WebGrid cada X tiempo de forma asíncrona
Fecha: 16/03/2011 - Brutal!!! Eduard Tomàs: Previsualizar imágenes subidas en ASP.NET MVC sin guardarlas en servidor. No es ajax.
Fecha: 15/03/2011 - Gran post! Eduard Tomàs: [BlogPost] Previsualización de imágenes en ASP.NET MVC. Primer post (sin usar ajax).
Fecha: 15/03/2011
Publicado en: Variable not found
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
- Barcelona, 22 de marzo
- Valencia, 24 de marzo
- Bilbao, 29 de marzo
- Sevilla, 31 de marzo (hey, juego en casa! ;-))
- Madrid, 5 de abril
¡Espero veros por allí! :-)
Publicado en: Variable not found.
- Scaling a javaScript codebase
Fecha: 20/03/2011 - Rumores: algunas características de ASP.NET MVC que quizás pasen a la próxima versión de webforms.
Fecha: 20/03/2011 - Phil Haack: How model binding to decimals in ASP.NET MVC is broken and how to fix it using a custom model binder!
Fecha: 19/03/2011 - José Manuel Alarcón: "Cómo crear URLs amigables personalizadas con ASP.NET Web Forms"
Fecha: 18/03/2011 - ASP.NET MVC: The Big View Engine Comparison – Razor vs. Spark vs. Nhaml vs. Web Forms View Engine
Fecha: 16/03/2011 - Gisela Torres: Refrescar un WebGrid cada X tiempo de forma asíncrona ASP.NET MVC jQuery
Fecha: 16/03/2011 - Eduard Tomàs: Previsualizar imágenes subidas en ASP.NET MVC sin guardarlas en servidor. No es ajax.
Fecha: 15/03/2011 - Eduard Tomàs: Previsualización de imágenes en ASP.NET MVC. Primer post (sin usar ajax).
Fecha: 15/03/2011 - Esta tarde se lanza Internet Explorer 9
Fecha: 14/03/2011 - campusMVP: Preguntas frecuentes sobre Tracing en ASP.NET:
Fecha: 14/03/2011
Publicado en: www.variablenotfound.com.
[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: 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. 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:
Ante este error, tenemos varias fórmulas para conseguir acceso a los recursos localizados: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'.
- 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.
[LocalizedDisplayNameAtribute]
Es realmente sencillo implementar un atributo que nos permita conseguir algo parecido a lo descrito anteriormente. Simplemente heredando deDisplayNameAttribute
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); }
}
}
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.
Espero que te resulten interesantes. :-)
- Kristof Claes: Registering a namespace for all Razor views
Fecha: 10/03/2011 - Lanzado Google Chrome 10.
Fecha: 10/03/2011 - Creating PDF Documents with ASP.NET and iTextSharp.
Fecha: 10/03/2011 - José Manuel Alarcón: "Internet Information Server (IIS) Express y Visual Studio 2010 SP1"
Fecha: 10/03/2011 - MSDN Online: Visual Studio 2010 SP1 is now available for MSDN Subscribers.
Fecha: 09/03/2011 - John Katsiotis: Just blogged and uploaded a nuget package - Linked-In OAuth Library
Fecha: 09/03/2011 - Elijah Manor: "ASP.NET Sprites with MVC & Razor Helpers!"
Fecha: 09/03/2011 - Introducing Data Annotations Extensions.
Fecha: 09/03/2011 - Generating EF Code First model classes from an existing database. Via Scott Guthrie
Fecha: 09/03/2011 - DotNetCurry: "Output Caching Actions Gotcha in ASP.NET MVC 3"
Fecha: 09/03/2011 - David Ebbo: Using a custom build of MVC 3 ASP.NET MVC
Fecha: 09/03/2011 - campusMVP: ¡¡Acabamos de superar las 100.000 lecturas de nuestros artículos y libros en Scribd!! :-))
Fecha: 09/03/2011 - Interesante: Nuget package explorer.
Fecha: 09/03/2011
Publicado en: Variable not found
Esta semana he vuelto a encontrar un ejemplo, creo que bastante ilustrativo, del fenómeno. La captura de pantalla pertenece a la herramienta gratuita Bulk Rename Utility, una utilidad imprescindible cuando necesitamos renombrar de forma masiva archivos en un directorio.
Funcionalmente, ninguna pega, más bien todo lo contrario: es potentísima, e incluso una vez la has utilizado un poco resulta hasta fácil de manejar.
Ahora bien, a nivel de interfaz la primera impresión es una auténtica agresión al usuario. De hecho, hasta los propios autores se lo toman con filosofía, como podemos leer en el primer párrafo del tutorial de su web:
“You've installed the software, you've launched it, and you now see a million and one controls and boxes on the screen. The first thing to do is DO NOT PANIC!”Pero vaya, como ya decía años atrás, el que esté libre de pecado que tire la primera piedra ;-)
Publicado en: Variable not found.
Publicado por José M. Aguilar a las 12:18 p. m.
Etiquetas: antipatrones, desarrollo, diseño, interfaces
Resulta que si utilizamos Internet Explorer en cualquiera de sus versiones todo funciona correctamente y a toda velocidad, pero la carga de páginas, imágenes y scripts en local se eterniza si estamos utilizando Firefox o Google Chrome.
Al parecer, en ambos casos se trata de un problema con la resolución de nombres del equipo en IPv6, que no es capaz de traducir de forma eficiente el nombre localhost. En Firefox se puede arreglar a nivel de aplicación, pero si queremos que todo vaya bien en Chrome habrá que modificar la configuración global del equipo.
Solución en Firefox
Si sólo queremos corregir este problema en Firefox, debemos abrir la aplicación y teclear en su barra de direcciones “about:config”. Tras ello, lo primero será confirmar nuestra decisión de acceder a la configuración de la aplicación en la pantalla donde se nos informa de los mil y un peligros a los que nos exponemos al hacerlo.A continuación en el filtro teclead “ipv6”, lo que provocará que en la lista de parámetros del sistema se quede únicamente el que nos permitirá desactivar la resolución de nombres, llamado network.dns.disableIPv6. Pues bien, en ese punto, simplemente hay que modificar su valor a true, haciendo doble clic sobre la fila:
A partir de ese momento, y una vez reiniciada la aplicación, ya podremos acceder con Firefox a nuestras aplicaciones en local a toda velocidad.
Solución en Chrome y Firefox
El truco anterior no funcionará con Chrome, donde no he visto cómo desactivar la resolución de nombres en IPv6. Sin embargo, se puede corregir el problema editando editando el archivo hosts, disponible en la carpeta \Windows\System32\drivers\etc, que debe quedar como el siguiente:Es decir, dejamos comentada con la almohadilla (#) la línea que resuelve localhost como “::1” (IPv6) y descomentamos la que lo resuelve como 127.0.0.1 (IPv4). Eso sí, ojo que poder salvar este archivo modificado debéis abrirlo con el bloc de notas o similar como administrador del sistema, en caso contrario no tendréis permisos de escritura. Una vez salvado el archivo, el problema habrá desaparecido.
Además, en este caso, dado que el cambio sobre el archivo hosts afecta a toda la máquina, también Firefox volverá a la normalidad, matando dos pájaros de un tiro, y haciendo innecesaria la utilización del primer método descrito :-)
Fuente:
Publicado en: Variable not found.
Publicado por José M. Aguilar a las 11:46 a. m.
Etiquetas: desarrollo, productividad, trucos, vs2010
Espero que te resulten interesantes. :-)
- Phil Haack: Defining default content for Razor Layout sections using a cool technique, Razor Delegates!
Fecha: 06/03/2011 - Francisco Charte: Hoy se cumplen 30 años del lanzamiento del Sinclair ZX81
Fecha: 05/03/2011 - ¿Todavía un 12% de IE6? IE6CountDown
Fecha: 05/03/2011 - jsFiddle: editor online de javascript/html/css.
Fecha: 04/03/2011 - Alternativas a Reflector
Fecha: 04/03/2011 - Gran serie sobre EF y Oracle, by Javier Torrecilla.
Fecha: 04/03/2011 - Vídeos en español sobre LightSwitch, by Jorge Serrano.
Fecha: 04/03/2011 - Html5 web storage in esence.
Fecha: 03/03/2011 - Llega EntityFramework 4.1. Post del equipo de desarrollo (en) - Vía Pablo Núñez
Fecha: 03/03/2011 - A smarter EF include method, by Nick Berardi.
Fecha: 28/02/2011 - Charles Vallance: Blogged: ASP.NET MVC 3 Email Validation with Unobtrusive jQuery Validation ASP.NET MVC
Fecha: 28/02/2011 - Phil Haack: Templated Razor delegates. A cool hidden gem of Razor.
Fecha: 28/02/2011
Publicado en: Variable not found
App_GlobalResources
para ir componiendo los interfaces.De esta forma, los literales de texto de las vistas son sustituidos por expresiones que, ya en tiempo de ejecución, son tomadas del archivo de recursos correspondiente al idioma actual:
Sin embargo, recientemente me he encontrado con un escenario en el que, para facilitar el mantenimiento de los contenidos de las vistas, me resultaba mucho más cómodo disponer de versiones distintas de las mismas en sus correspondientes archivos Razor (.cshtml), y seleccionar en tiempo de ejecución cuál de ellas debía ser enviada en función de la cultura del usuario. Eso sí, en caso de no existir una vista específica para la cultura actual, era necesario retornarla en el idioma por defecto.
Así, en este caso, asumiremos que las vistas localizadas se llamarán siempre de la forma {vista}_{idioma}. Por ejemplo, la traducción al inglés de la vista “about” estará definida en el archivo “about_en.cshtml” (usando Razor), al francés en “about_fr.cshtml”, y así sucesivamente. Por simplificar, utilizaremos sólo los dos primeros caracteres del idioma, aunque podrían ser referencias culturales completas, como “about_es-es.cshtml”.
Una vez definida la nueva convención, sólo nos falta implementar en el controlador el mecanismo de selección de vistas en función de la cultura actual. En un primer acercamiento, podríamos implementarla directamente sobre las acciones, como en el siguiente ejemplo:
public ActionResult About()
{
string currentLang = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
var viewResult = ViewEngines.Engines.FindView
(ControllerContext, "About_" + currentLang, null);
if (viewResult.View == null)
return View("About_es");
return View("About_" + currentLang);
}
Aunque funcionalmente es correcto, es fácil ver que la introducción de esta lógica contaminaría bastante el código del controlador, además de atentar contra el principio DRY en cuanto tuviéramos varias acciones en las que introducirlo. Obviamente, debemos encontrar otra solución más apropiada y elegante a esta cuestión.Y como siempre, en ASP.NET MVC existen multitud de fórmulas para resolver problemas de este tipo. En este post vamos a ver dos de ellas: extendiendo el conjunto de ActionResults, y mediante filtros personalizados.
Usando ActionResults
Empezando por el final, el resultado que vamos a lograr es el que podemos ver en el siguiente código de controlador:public ActionResult About()
{
return LocalizedView();
}
LocalizedView()
no es sino un método rápido para instanciar un LocalizedViewResult
, cuya implementación veremos a continuación. La lógica que incluye utilizará los motores de vistas para buscar una vista compuesta por el nombre indicado como parámetro (por defecto el nombre de la acción actual) y la cultura establecida en el hilo de ejecución; de no encontrarse, se mostrará la vista correspondiente a la cultura por defecto.El código de este ActionResult es el siguiente:
public class LocalizedViewResult : ViewResult
{
private string _defaultCulture;
public LocalizedViewResult(string defaultCulture)
{
_defaultCulture = defaultCulture;
}
public override void ExecuteResult(ControllerContext context)
{
string currentCulture = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
if (string.IsNullOrWhiteSpace(ViewName))
{
ViewName = context.RouteData.GetRequiredString("action");
}
var viewResult = ViewEngines.Engines.FindView(context, this.ViewName + "_" + currentCulture, null);
if (viewResult.View == null)
ViewName += "_" + _defaultCulture;
else
ViewName += "_" + currentCulture;
base.ExecuteResult(context);
}
}
Como se puede observar, el método
ExecuteResult()
obtiene el idioma actual y comprueba si existe una vista que siga la convención de nombrado que hemos definido previamente (vista_idioma.cshtml). Si se encuentra, se anexa al nombre original de la vista el sufijo del idioma; en caso contrario se actúa de la misma forma, pero utilizando el nombre por defecto.Por último, para hacer más rápido su uso desde las acciones, necesitamos implementar el método
LocalizedView()
en una clase base de la que deberíamos heredar nuestros controladores:public class ControladorBase: Controller
{
public LocalizedViewResult LocalizedView(string viewName = null, object model = null,
string master = null, string defaultLang = "es")
{
if (model != null)
{
ViewData.Model = model;
}
return new LocalizedViewResult(defaultLang)
{
ViewName = viewName,
MasterName = master,
ViewData = ViewData,
TempData = TempData,
};
}
}
Obviamente, el método
LocalizedView()
sólo podría ser utilizado desde controladores descendientes de ControladorBase
. Si preferís no heredar de un controlador base, siempre podríais crearlo como método extensor de Controller
, aunque entonces deberíais invocarlo con el this
por delante, y queda algo menos elegante.Las siguientes acciones muestran posibles usos de esta implementación. Como se puede observar, son bastante explícitas y dejan clara su intencionalidad:
public ActionResult Prueba()
{
return LocalizedView("About");
}
public ActionResult About()
{
return LocalizedView();
}
Usando filtros
Los filtros presentan una solución bastante menos intrusiva al problema y, para mi gusto bastante mejor, puesto que no obligaría a modificar la implementación tradicional de las acciones. Para hacernos una idea, lo que pretendemos conseguir es lo siguiente:[LocalizedView]
public ActionResult About()
{
return View();
}
El simple hecho de decorar una acción con el atributo LocalizedView
provocará que el retorno de la acción sea analizado, y se ejecute la lógica de selección de vistas ya descrita anteriormente. Además, el hecho de ser un filtro hace posible su aplicación a controladores completos, o incluso su aplicación global registrándolo en la inicialización de la aplicación.El código del filtro personalizado es el siguiente:
public class LocalizedViewAttribute : ActionFilterAttribute
{
private string _defaultLang;
public LocalizedViewAttribute(string defaultLang = "es")
{
_defaultLang = defaultLang;
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var viewResult = filterContext.Result as ViewResultBase;
if (viewResult != null)
{
if (string.IsNullOrWhiteSpace(viewResult.ViewName))
{
viewResult.ViewName = filterContext.RouteData.GetRequiredString("action");
}
string currentLang = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
var v = ViewEngines.Engines.FindView(
filterContext.Controller.ControllerContext,
viewResult.ViewName + "_" + currentLang, null
);
if (v.View == null)
viewResult.ViewName += "_" + _defaultLang;
else
viewResult.ViewName += "_" + currentLang;
}
base.OnResultExecuting(filterContext);
}
}
En el código se puede observar que en primer lugar se comprueba que el resultado de la acción sea de tipo ViewResultBase
; en caso contrario, es decir, cuando la acción no retorne una vista, simplemente se ejecutará la lógica por defecto. A continuación, ya asumiendo que la acción retorna una vista, puede observarse la aplicación de la convención de nombrado cuando el nombre de la misma sea nulo, tomándolo de la acción actual. Finalmente, tras comprobar la existencia o no de la vista localizada, se modifica el valor de la propiedad
ViewName
del resultado, que identifica la vista que finalmente será enviada al cliente.Desde el punto de vista de la implementación de las acciones queda prácticamente igual de claro que usando la técnica del
ActionResult
, pero en cambio, ésta requiere menos andamiaje. A continuación, dos posibles ejemplos de uso de este atributo:[LocalizedView]
public ActionResult About()
{
return View();
}
[LocalizedView("en")]
public ActionResult Prueba()
{
return View("About");
}
Publicado en: Variable not found.
Publicado por José M. Aguilar a las 9:47 a. m.
Etiquetas: asp.net, aspnetmvc, desarrollo, localizacion, trucos
Espero que te resulten interesantes. :-)
- AvanadeSpain Empleo: ¿Quieres trabajar con Windows Mobile en Sevilla? Ponte en contacto con nosotros
Fecha: 25/02/2011 - campusMVP: en todos los navegadores el fantástico JSON On-Line Viewer (Si usas IE9 ponlo en modo compatibilidad)
Fecha: 25/02/2011 - Buen post! RT Eduard Tomàs: [BlogPost] Rendering de vistas parciales de MVC3 y Razor
Fecha: 25/02/2011 - Scott Guthrie: Nice post about a new DataAnnotations library on NuGet - enable even easier validation in ASP.NET MVC:
Fecha: 24/02/2011 - MongoDB con Norm, por Marc Rubiño.
Fecha: 24/02/2011 - jQuery 1.5 Cheat sheet.
Fecha: 24/02/2011 - ASP.NET MVC custom data annotation validator. Server and Client, by K. Scott Allen
Fecha: 24/02/2011 - campusMVP: 10 plugins de jQuery que te ayudarán a mejorar tus formularios
Fecha: 24/02/2011 - Phil Haack: Changing the base type of a Razor view to add your own properties
Fecha: 21/02/2011
Publicado en: Variable not found
Espero que te resulten interesantes. :-)
- ReSharper 6 Bundles Decompiler, Free Standalone Tool to Follow.
Fecha: 18/02/2011 - Malcolm Sheridan: Get Selected Row from ASP.NET MVC 3 WebGrid ASP.NET MVC
Fecha: 17/02/2011 - Cacheitis, por Rodrigo Corral. Muy bueno :-)
Fecha: 17/02/2011 - Ejemplo de uso de unobstrusive ajax en ASP.NET MVC 3.
Fecha: 17/02/2011 - Webcast de José Manuel Alarcón: Certificaciones Microsoft y sus oportunidades: Webcast gratis
Fecha: 15/02/2011 - Gisela Torres: Bing Maps & Geolocation APIs | Return(GiS)
Fecha: 15/02/2011 - Interesante... David Ebbo: Build your Web Application at runtime
Fecha: 14/02/2011 - David Ebbo: Register your HTTP modules at runtime without config
Fecha: 14/02/2011 - All jQuery UI components now on #NuGet (vía Hadi Hariri)
Fecha: 14/02/2011 - Scott Guthrie: NuGet 1.1 release
Fecha: 14/02/2011
Publicado en: Variable not found