(...) entonces, ¿esto quiere decir que podemos utilizar un middleware para dar soporte CORS a soluciones basadas en la primera versión de WebAPI o en MVC 4?Respuesta corta: sí. Siempre que se cumplan los requisitos de plataforma exigidos por Katana y los middlewares que queramos usar, nada impide hacerlo sobre aplicaciones con versiones anteriores de estos frameworks.
Y vamos con la respuesta larga…
Pues bien, tras instalarla, los que hayáis intentado probar ASP.NET MVC 5 desde Visual Studio 2012 probablemente os hayáis sorprendido con las pocas opciones que tenemos a la hora de crear proyectos de este tipo. Si bien están implementadas otras características disponibles en la versión 2013 como los scaffolders, el soporte para Razor 3, o la adopción de Bootstrap en la maquetación de vistas generadas, no podemos decir lo mismo de la aplicación del concepto “One ASP.NET“ a la creación de proyectos.
ASP.NET MVC 5 es, quizás, el producto con menos cambios y novedades de todos los citados anteriormente. Ojo, esto no tiene por qué ser necesariamente malo: quiere decir que nuestro querido framework ha alcanzado ya un nivel de madurez importante y no necesita grandes cambios para seguir dándonos todo lo que necesitamos para crear aplicaciones web. Sin embargo, no puedo negar una cierta desazón ante la falta de esas novedades espectaculares que han venido acompañando entregas anteriores (¿recordáis cuando llegó Razor? ¡Uaaaaau!).
Pero antes que nada, deciros que ASP.NET MVC 5 completo (tooling y ensamblados) está disponible en estos momentos exclusivamente en Visual Studio 2013. Si utilizamos Visual Studio 2012 podemos descargar y utilizar los binarios usando Nuget, pero no habrá soporte a nivel de IDE hasta mediados de Noviembre, cuando se prevé que será lanzada una actualización del entorno.
Y dicho esto, vamos a comentar los cambios más destacados que acompañan a esta nueva versión.
En este tercer y último post de la serie (ir al primero o segundo) , vamos a ver una última técnica para realizar inyección de parámetros a acciones ASP.NET MVC que, aunque aporta menos a la hora de comprender las misteriosas interioridades del framework, es ciertamente mucho más cómoda y práctica en caso de que deseemos aplicar esta técnica.
Publicado por José M. Aguilar a las 9:10 a. m.
Etiquetas: aspnetmvc, autofac, desarrollo, patrones, trucos
Pues bien, desde hace unas semanas tenemos disponible la versión 3.0 de Unity, que ha venido acompañando también a la reluciente versión 6 de la Enterprise Library. Como sabemos, esta creación del equipo de Patterns & Practices de Microsoft contiene un conjunto de componentes reutilizables llamados “application blocks” que resuelven problemáticas comunes en el desarrollo de sistemas, como logging, tracing, acceso a datos, gestión de excepciones y otras, incluyendo por supuesto inyección de dependencias e inversión de control.
En este post vamos a ver cómo podemos usar fácilmente esta nueva versión de Unity en nuestras aplicaciones ASP.NET MVC y algunas de las novedades que ofrece para este tipo de sistemas.
System.Web.Optimization
traerá (aún está en beta) algunas novedades interesantes al sistema de bundling que se incluye de serie en los proyectos ASP.NET MVC y se distribuye a través de Nuget en el paquete Microsoft.AspNet.Web.Optimization.En particular, vamos a centrarnos en una característica muy práctica si queremos utilizar una Content Delivery Network (CDN) externa (como la de Microsoft, Google o incluso una propia) para delegar a ella la carga de bibliotecas de script comunes, pero queremos a la vez proporcionar una alternativa local por si acaso ésta fallase debido a cualquier motivo.
Publicado por José M. Aguilar a las 9:15 a. m.
Etiquetas: asp.net, aspnetmvc, novedades, optimización, trucos
Y ciertamente, me ha parecido muy interesante porque es un escenario que encuentro habitualmente en empresas de desarrollo: comprenden los beneficios de reducir el acoplamiento entre componentes, pero les parece algo demasiado complejo como para aplicar en su día a día porque desconocen cuáles son y cómo usar las herramientas de que disponemos para conseguirlo.
Por tanto, en este post vamos a ver, paso a paso y de forma totalmente práctica, cómo evolucionar desde un controlador MVC fuertemente acoplado a clases del modelo hasta otro totalmente desacoplado usando inyección de dependencias y contenedores de inversión de control.
Y aunque las técnicas y ejemplos que mostraremos están muy enfocados a controladores MVC, los conceptos tratados son aplicables en cualquier tipo de tecnología y arquitectura.
Url.Action()
o Html.ActionLink()
, y cómo usando una simple línea de código podíamos hacer que las rutas se generaran usando sólo minúsculas.
Publicado por José M. Aguilar a las 9:30 a. m.
Etiquetas: aspnetmvc, desarrollo, trucos, validadores
Hoy vamos a ver una solución a un problema al que seguro nos hemos enfrentado cientos de veces: la edición en formularios de propiedades de tipo
enum
.1. El escenario
Imaginad que tenemos dos enumeraciones como las siguientes:public enum Color { Black, Blue, Red, Green } public enum Language { English, Spanish, Italian, Portuguese }
Usadas una clase del Modelo tal que:
public class Contact { public string Name { get; set; } public Language MainLanguage { get; set; } public Color? FavouriteColor { get; set; } }Y, a continuación, queremos crear un formulario de edición para la misma, con el código:
<h2>Edit contact</h2> @using(Html.BeginForm()) { @Html.EditorForModel() <input type="submit" value="Send" /> }Como era de esperar, el resultado una vez en ejecución es algo similar a lo que podemos ver en la captura de pantalla de la derecha.
Las propiedades
MailLanguage
y FavouriteColor
, a pesar de ser enumeraciones, muestran como control de edición un simple cuadro de texto en el que podemos introducir cualquier cosa.Y dado que son
enum
y podemos conocer sus valores de antemano, ¿no sería más lógico que la edición de estas propiedades se realizaran utilizando desplegables? 2. Editor template para enums
Sin duda, la fórmula más sencilla y reutilizable para conseguir el objetivo que pretendemos es crear una plantilla de edición personalizada para las enumeraciones.Como seguro sabréis, los editor templates proporcionan un mecanismo para generar controles de edición específicos para tipos de datos; podemos tener un editor para propiedades de tipo
string
(p.e., un cuadro de texto), de tipo DateTime
(p.e., un cuadro de texto con un date picker), etc. Lo único que hay que hacer es crear en la carpeta /Views/Shared/EditorTemplates vistas parciales con el nombre del tipo de datos para el que se desea crear el editor (string.cshtml, datetime.cshtml, etc.).Por tanto, probemos introduciendo el siguiente código en /Views/Shared/EditorTemplates/Enum.cshtml:
@model Enum @{ var isRequired = this.ViewData.ModelMetadata.IsRequired; var type = this.ViewData.ModelMetadata.ModelType; if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { type = type.GenericTypeArguments[0]; } var values = Enum.GetValues(type); var items = (from object value in values select new SelectListItem { Text = Enum.GetName(type, value), Value = value.ToString() }); } @Html.DropDownListFor(m=>m, items, isRequired ? "Select...": "(None)")Como se puede observar, prácticamente sólo hacemos tres cosas:
- En primer lugar, determinamos la obligatoriedad de la propiedad a editar observando los metadatos del modelo.
- A continuación, creamos una lista de elementos de tipo
SelectListItem
recorriendo los valores disponibles en la enumeración. Observad que es necesario comprobar si el tipo que nos está llegando es anulable (Nullable<TEnum>
) y obtener el tipo de la enumeración de su parámetro genérico. - Por último, usamos DropDownListFor() para generar el desplegable. En caso de tratarse de una propiedad obligatoria, añadimos un primer elemento con el texto “Select…”, y en caso contrario usamos el texto “(None)” para identificar el elemento nulo.
string
) para editar las propiedades de tipo enum
, lo cual nos obliga a indicar en las propiedades la plantilla a utilizar (las dos fórmulas usadas a continuación son equivalentes):public class Contact { public string Name { get; set; } [UIHint("Enum")] public Language MainLanguage { get; set; } [DataType("Enum")] public Color? FavouriteColor { get; set; } }Ya en ejecución, observamos que el resultado ha mejorado notablemente, como se puede apreciar en la captura de pantalla :-)
En este punto podríamos considerar que hemos alcanzado el objetivo que nos proponíamos, pero… ¿no os parece aún demasiado trabajo tener que decorar cada propiedad de tipo enumeración con estos atributos? ¿No podríamos dejar al framework que se encargue de estas minucias?
3. Proporcionando metadatos con metadata providers
Ya hemos hablado por aquí en varias ocasiones de los proveedores de metadatos, o metadata providers. De hecho, si no tienes claro lo que son, te recomendaría que leyeras el post “Mamá, ¿de dónde vienen los metadatos” que publiqué haces unos meses.Muy resumidamente, se trata del mecanismo que obtiene los metadatos de las clases del Modelo desde donde se encuentren definidos. El proveedor por defecto los obtiene examinando las clases y sus propiedades mediante reflexión y obteniendo los atributos (anotaciones) que definen restricciones y otras características, pero podemos crear proveedores personalizados que las extraigan desde otras fuentes.
Y vamos a utilizar esta característica para evitar tener que especificar el atributo
[UIHint]
a las propiedades de tipo enum
. El proveedor cuyo código veremos a continuación hereda del usado por defecto en ASP.NET MVC 4 (CachedDataAnnotationsModelMetadataProvider
) y sobrescribe el procedimiento de obtención de metadatos:public class EnumMetadataProvider: CachedDataAnnotationsModelMetadataProvider { protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype( CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor) { var metadata = base.CreateMetadataFromPrototype(prototype, modelAccessor); var type = metadata.ModelType; if (type.IsEnum || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && type.GetGenericArguments().Length == 1 && type.GetGenericArguments()[0].IsEnum)) { metadata.TemplateHint = "Enum"; } return metadata; } }Observad que lo único que estamos haciendo es invocar el método de la clase base y, posteriormente, comprobar si el tipo de la propiedad es un enumerado o un tipo anulable de enumerado, en cuyo caso introduce en la propiedad de metadatos
TemplateHint
el nombre de la plantilla a utilizar (“Enum”).Por último, ya sólo queda establecer un objeto de este tipo como proveedor de metadatos por defecto para la aplicación:
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); // ... ModelMetadataProviders.Current = new EnumMetadataProvider(); } }De esta forma, por fin podemos eliminar de nuestra clase del Modelo los molestos atributos y todas las enumeraciones de nuestra aplicación se mostrarán en el formulario con la plantilla de edición que hemos creado anteriormente :-)
Publicado en: Variable Not Found.
El temario es una completa y profunda revisión del utilizado en el curso de MVC 3, que tan buenas críticas ha recibido por parte de los alumnos que lo han seguido, pero más completo, adaptado a las novedades de la nueva versión, a las nuevas herramientas, y con un enfoque mucho más práctico.
Y para que veáis que no se ha escatimado en recursos, ahí va un vídeo de presentación muy chulo:
Url.Action()
o Html.ActionLink()
, éstas son generadas usando mayúsculas y minúsculas según hubiéramos indicado en los parámetros de las llamadas:Helper | URL generada |
---|---|
@Url.Action("index", "home") | /home/index |
@Url.Action("List","Products", new{ Category="PC" }) | /Products/List?Category=PC |
@Url.Action("VIEWALL", "PRODUCTS") | /PRODUCTS/VIEWALL |
Como podemos ver, la URL resultante queda a criterio del desarrollador o, lo que es peor, al puro azar. A veces incluso no es algo que podamos elegir fácilmente, puesto que son generadas por otros componentes como T4MVC. Y no sé si desde el punto de vista del SEO tendrá su impacto, pero desde luego el ofrecer estas direcciones sin un aspecto unificado no da buena impresión.
En versiones anteriores a ASP.NET 4.5, esto podíamos solucionarlo creando helpers, o rutas personalizadas que, heredando de
Route
, realizaran esta conversión a minúsculas. Sin embargo, ahora disponemos de un mecanismo más cómodo para forzar que las URLs generadas sean normalizadas a minúsculas, así:public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.LowercaseUrls = true; routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); [...] } }
Un detalle, sin duda interesante, para tener en cuenta en nuestros desarrollos.
Publicado en: Variable not found.
Asíncronía = bueno
A grandes rasgos, la explicación es la siguiente: IIS tiene disponible un número limitado de hilos (threads) destinados a procesar las peticiones. Cuando llega una petición, uno de estos hilos es asignado en exclusiva a ella y permanecerá ocupado hasta que haya sido totalmente procesada. Si llegan peticiones cuando todos los hilos están ocupados, se introducen en una cola, también limitada. Cuando el tamaño máximo de esta cola ha sido superado, ya al servidor no le queda más remedio que responder con un error HTTP 503 al usuario indicándole que está seriamente ocupado.
DefaultDisplayMode
proporcionada por el framework, con la que podíamos cubrir la mayoría de necesidades comunes.Así, veíamos cómo el siguiente código era suficiente para registrar un nuevo Display Mode llamado “iPhone”, que sería activado cuando en el identificador del agente de usuario (encabezado user-agent de la petición) incluyera el texto “iPhone”:
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("iPhone") { ContextCondition = (context => context.Request.UserAgent.IndexOf ("iPhone", StringComparison.OrdinalIgnoreCase) >= 0) });Hecho esto, ya podíamos definir vistas alternativas a las habituales específicas para este dispositivo, cuyos nombres de archivo acabarían siempre en “.iphone.cshtml”. Observad que estamos usando la clase
DefaultDisplayMode
, a la que estamos facilitando la condición que debe cumplirse para que se active este Display mode.Sin embargo, si pensamos crear muchas vistas específicas para dispositivos, podríamos encontrarnos con un maremagnum de archivos como el que veis en la captura de pantalla adjunta.
Obviamente, no es una situación fácilmente manejable, así que ¿por qué no cambiar la forma de nombrar los archivos dependiendo del Display Mode actual? Pues dicho y hecho, vamos a conseguir que cada dispositivo disponga de una carpeta específica para guardar sus vistas.
Heredando de DefaultDisplayMode
Si analizamos el código fuente de la clase DefaultDisplayMode
, veremos que hay varios miembros virtuales que podemos sobrescribir para tomar el control, y uno de ellos es el método TransformPath()
, encargado de transformar la ruta hacia el archivo físico donde está definida la vista teniendo en cuenta el nombre del Display Mode actual.El código por defecto del método es el siguiente:
protected virtual string TransformPath(string virtualPath, string suffix)
{
if (string.IsNullOrEmpty(suffix))
return virtualPath;
string extension = Path.GetExtension(virtualPath);
return Path.ChangeExtension(virtualPath, suffix + extension);
}
O sea, que se reemplaza la extensión del archivo, normalmente “.cshtml” por el resultado de concatenar el sufijo suministrado (el nombre del Display Mode) a dicha extensión. Por esta razón, el comportamiento por defecto del framework es utilizar construcciones como “nombrevista.iphone.cshtml”.Si, como es el caso, queremos cambiar la ruta donde van a intentar localizarse las vistas, lo único que tenemos que hacer es crear una clase descendiente de
DefaultDisplayMode
, sobrescribir la forma de “montar” la ruta hacia la vista, y utilizar esta nueva clase para registrar los modos de visualización que nos interesen. Una posible implementación podría ser la siguiente: public class OrganizedDisplayMode: DefaultDisplayMode
{
public OrganizedDisplayMode(string displayModeId): base(displayModeId)
{
}
protected override string TransformPath(string virtualPath, string suffix)
{
if (string.IsNullOrEmpty(suffix))
return virtualPath;
// Transforms /index.cshtml --> /suffix/index.cshtml
int lastSeparator = virtualPath.LastIndexOf('/');
virtualPath = virtualPath.Substring(0, lastSeparator) +
"/" + suffix +
virtualPath.Substring(lastSeparator);
return virtualPath;
}
}
Y en la inicialización de la aplicación ya podríamos registrar este Display Mode y asociarlo a la condición que esperamos que cumpla la petición: DisplayModeProvider.Instance.Modes.Insert(0,
new OrganizedDisplayMode("iPhone")
{
ContextCondition =
context => context.Request.UserAgent.Contains("iPhone")
});
De esta forma, ya podemos organizar las vistas como podéis observar en la captura de pantalla de la derecha: cada dispositivo (o Display Mode registrado) dispondría de una carpeta en cuyo interior se encontrarían las vistas específicas para el mismo.Y observad que su funcionamiento no se limita a las vistas asociadas a controladores concretos, la solución también sería válida en vistas compartidas (Shared) y con aquellas incluidas en áreas :-)
Publicado en Variable not found.
Publicado por José M. Aguilar a las 9:18 a. m.
Etiquetas: asp.net, aspnetmvc, desarrollo, novedades, trucos
System.ComponentModel.DataAnnotations
:CreditCardAttribute
, que puede ser utilizado para validar números de tarjeta de crédito.EmailAddressAttribute
, que validará direcciones de correo electrónico.FileExtensionsAttribute
, para validar extensiones en nombres de archivo.PhoneAttribute
, que indica cuándo una propiedad debe contener un número de teléfono válido.UrlAttribute
, que comprobará si el contenido de una propiedad es una URL válida.CompareAttribute
, que antes estaba disponible en System.Web.Mvc y ha “ascendido” a anotación de datos general, permite validar la igualdad de dos propiedades.
También se ha incluido soporte para el atributo
MembershipPasswordAttribute
, recientemente incluido en System.Web.Security
, que comprueba si una contraseña cumple los requisitos establecidos para las mismas en el membership provider.Publicado en Variable not found.
Lo más habitual es utilizarlas para crear editores o visualizadores para tipos de datos concretos, en cuyo caso las vistas disponibles en las carpetas citadas anteriormente se llaman igual que el tipo de dato a editar o visualizar.
Por ejemplo, si tenemos una vista llamada int.cshtml en la carpeta /Views/Shared/EditorTemplates , será la utilizada como editor de propiedades de tipo entero. Algo parecido utilizamos por aquí, hace ya bastante tiempo, para integrar jQuery datepicker en nuestras aplicaciones.
También es habitual encontrar en estas carpetas plantillas vinculadas a propiedades u objetos de forma directa mediante el atributo
[UIHint]
(como en este post), mediante el cual se indica explícitamente qué plantilla debe utilizarse: [DisplayName("Long description")]
[StringLength(140), Required]
[UIHint("LimitedTextArea")] // Use the template "LimitedTextArea.cshtml"
public string Description { get; set; }
Y otra alternativa, aunque quizás algo menos utilizada, es la posibilidad de indicar la plantilla a utilizar en la misma llamada al helper
EditorFor()
o DisplayFor()
que provoca su renderizado: @Html.EditorFor(m=>m.Description, "LimitedTextArea")
Sin embargo, hay una posibilidad adicional, que curiosamente no es demasiado conocida, consistente en utilizar el atributo DataTypeAttribute
para indicar la plantilla de edición o visualización a emplear para la propiedad a la que se aplica.El funcionamiento es trivial: decoramos las propiedades con
[DataType]
suministrándole como parámetro el tipo de información que va a contener, y el sistema intentará localizar una plantilla con ese nombre. Veamos un ejemplo: [DataType(DataType.EmailAddress)]
public string Email { get; set; }
[DataType(DataType.Time)]
public DateTime StartDate { get; set; }
[DataType(DataType.Html)]
public string LongDescription { get; set; }
En el momento de renderizar un editor (o un display) template para la primera propiedad, el sistema utilizará, si existe, el archivo EmailAddress.cshtml; en caso de no existir, intentará localizar un string.cshtml, y si tampoco lo encuentra generará un editor por defecto. En el segundo caso se realizará la misma operación con Time.cshtml, y el último de ellos usará la plantilla Html.cshtml como primera opción.Podemos utilizar como nombre de plantilla todos los valores que encontramos en la enumeración
DataType
:DateTime
Date
Time
Duration
PhoneNumber
Currency
Text
Html
MultilineText
EmailAddress
Password
Url
ImageUrl
Y como viene siendo costumbre, vamos a dar un repaso a todo lo que encontramos en esta nueva entrega, que presumiblemente será la última (bueno, o penúltima, nunca se sabe) antes de la versión definitiva. Eso sí, para no hacer el post demasiado largo nos centraremos exclusivamente en los cambios más destacables y visibles que se han introducido respecto a la versión beta.