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!
Mostrando entradas con la etiqueta desarrollo. Mostrar todas las entradas
Mostrando entradas con la etiqueta desarrollo. Mostrar todas las entradas
martes, 18 de septiembre de 2012
Seguro que alguna vez habéis notado que al generar URLs hacia acciones de una aplicación MVC usando helpers como 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.
martes, 11 de septiembre de 2012
ASP.NET MVCEste es el mensaje que deberíamos interiorizar, si no lo hemos hecho ya, a la vista de las múltiples novedades introducidas en las últimas versiones de la plataforma ASP.NET y MVC:

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.
martes, 17 de julio de 2012
ASP.NET MVCHace poco hablábamos de la creación de Display Modes personalizados en ASP.NET MVC 4, y veíamos cómo hacerlo usando la clase 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)
});
 
imageHecho 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;
        }
    }
Vistas estructuradas por carpetasY 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.
martes, 12 de junio de 2012
ASP.NET MVCSabemos que las editor y display templates son magníficas herramientas de productividad que nos facilitan la creación de potentes interfaces de usuario. Resumidamente, son las vistas creadas específicamente para generar el interfaz de edición o de visualización de un objeto concreto que son almacenadas en las carpetas EditorTemplates y DisplayTemplates, dentro de /Views/Shared.

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
Publicado en Variable not found.
martes, 5 de junio de 2012
ASP.NET MVC¡Bueno, pues parece que esto se mueve! Hace unos días ha sido publicada la Release Candidate de ASP.NET MVC 4 coincidiendo con la liberación de Windows 8 Release Preview y Visual Studio 2012 Release Candidate (que, de hecho, incluye de serie MVC 4 RC).

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.
miércoles, 30 de mayo de 2012
ASP.NET MVCComo venimos comentando desde la aparición de la preview de MVC 4, el Display Mode o modo de visualización es un nuevo mecanismo que permite la creación de aplicaciones capaces de retornar vistas específicas según las características y capacidades del cliente conectado u otros parámetros del entorno de la petición.

Esto tiene mucha utilidad directa, por ejemplo, a la hora de construir aplicaciones web que adapten su interfaz en función de si el cliente es un dispositivo móvil o desktop, pero como casi siembre ocurre en el framework, se trata de un mecanismo muy extensible y fácil de adaptar a otras necesidades.
martes, 8 de mayo de 2012
ASP.NETImaginad que tenemos un sistema web de cierto volumen y decidimos estructurarlo en aplicaciones independientes, cada una publicada en un subdominio propio:
  • www.acme.org, que sería el sitio principal.
  • crm.acme.org, con el sistema CRM de la empresa.
  • erp.acme.org, con un sistema de gestión empresarial.
  • administration.acme.org con las herramientas de administración del sistema.
  • etc.
Desde un punto de vista operativo, es probable que nos interese suministrar un mecanismo de autenticación de usuarios compartido entre todas estas aplicaciones, de forma que el usuario, una vez identificado, pueda pasar de una a otra sin necesidad de introducir de nuevo sus credenciales.

No es una tarea complicada en ASP.NET, aunque hay que hacer algunos ajustillos para que todo funcione correctamente. Veámoslos.
miércoles, 18 de abril de 2012
De vez en cuando surgen en Stackoverflow geniales hilos como este, que te alegran el día en que los descubres. En este caso miles de usuarios proponían, votaban y debatían sobre cuál es la viñeta o tira cómica favorita de los programadores y otros especímenes de nuestra fauna digital.  

Y las veinte más votadas, auténticas joyas algunas de ellas, fueron las siguientes...
martes, 17 de abril de 2012
La programación web tradicional, basada en el modelo de petición-respuesta de página completa, no requería demasiado código en cliente más allá que el necesario para realizar validaciones y lógica de presentación simple: ¿para qué complicarnos en cliente si podíamos hacerlo todo en servidor?
martes, 20 de marzo de 2012
Como vengo comentando desde hace un tiempo, SignalR es un framework realmente impresionante y aporta unas posibilidades enormes en prácticamente cualquier tipo de aplicación. Ya hemos visto qué es y las bases en las que se sustenta, y también hemos visto algunos ejemplos de uso utilizando conexiones persistentes (aquí y aquí), que es el enfoque de menor nivel disponible a la hora de desarrollar servicios basados en esta plataforma.

En este post ascenderemos a un nivel de abstracción mucho mayor que el proporcionado por las conexiones persistentes y veremos cómo utilizar los Hubs, otro mecanismo proporcionado por SignalR que nos permitirá lograr una integración increíble entre el código cliente y de servidor, y hará aún más sencilla la implementación de este tipo de servicios.
martes, 13 de marzo de 2012
Qué divertidoLo divertido de escribir sobre productos que están todavía en fase de desarrollo es que cambian... y a veces, ¡de qué forma! Pues esto ha ocurrido con SignalR: recientemente se publicó la revisión 0.4 y bastantes cosas de las tratadas en el post anterior de la serie ha quedado en agua de borrajas. En fin, estaba avisado, así que mucho no puedo quejarme ;-)

Por tanto, esta tercera entrega de la serie vamos a dedicarla (otra vez ;-)) a las conexiones persistentes, veremos qué cosas han cambiado con la llegada de la revisión 0.4, y desarrollaremos un nuevo ejemplo que ilustre las novedades que podemos encontrar a la hora de trabajar a bajo nivel con SignalR.
martes, 14 de febrero de 2012
ASP.NET MVCComo sabemos, los helpers como Url.Action() y Html.ActionLink() son capaces de generar URLs hacia acciones partiendo de la información disponible en la tabla de rutas y de los parámetros que les suministramos.

Por ejemplo, el siguiente código genera un enlace hacia la acción Edit del controlador Friends, suministrándole el valor 3 al parámetro de ruta id:
@Html.ActionLink("Edit friend", "Edit", "Friends", new {id = 3}, null)
Sin embargo, estos helpers no nos ayudan demasiado cuando desconocemos a priori el valor de los parámetros. De hecho, si en lugar del 3 que hemos suministrado quisiéramos enviar un valor obtenido a través de scripting tendríamos un problema, puesto que el bloque de código anterior se ejecuta en servidor y el enlace sería generado antes incluso de ejecutarse el script para obtener el valor a enviar.

Esta es una pregunta que suelo ver muy a menudo en comunidades y foros (como el de ASP.NET MVC en MSDN) y me han realizado más de una vez en el curso de ASP.NET MVC 3 que tutorizo en CampusMVP, así que vamos a ver un par de enfoques para enfrentarnos a este escenario, bastante frecuente al trabajar en sistemas web y, sobre todo, en aquellos muy basados en scripting y Ajax.
martes, 24 de enero de 2012
Hace poco estuvimos viendo por aquí conceptos básicos sobre SignalR, el componente que nos permite crear espectaculares aplicaciones en las que múltiples usuarios pueden estar colaborando de forma simultánea, asíncrona, y en tiempo real.

Entre otras cosas, comentábamos que SignalR  crea una capa de abstracciones sobre una conexión virtual permanente entre cliente y servidor, sobre la que podemos trabajar de diferentes formas:
  • mediante conexiones persistentes, la opción de menor nivel, que proporciona mecanismos de notificación de conexión y desconexión de clientes, así como para recibir y enviar mensajes asíncronos a clientes conectados, tanto de forma individual como colectiva.
  • mediante el uso de “hubs”, que ofrece una interfaz de desarrollo mucho más sencilla, con una integración entre cliente y servidor que parece pura magia, y que seguro será la opción más utilizada por su potencia y facilidad de uso.
En este post estudiaremos la primera opción, conexiones persistentes. Los hubs los veremos en un artículo posterior de la serie, aunque si sois impacientes ya podéis ir leyendo el fantástico post del amigo Marc Rubiño sobre el tema, “Push con SignalR”.

martes, 17 de enero de 2012
Uau!!Una aplicación que mezcla internet, asincronía, y múltiples usuarios colaborando e interactuando al mismo tiempo siempre es merecedora de un “¡uau!”. Seguro que, al igual que un servidor, en algún momento os habéis quedado maravillados con la interactividad que presentan algunos sistemas web modernos, como Facebook, Google Docs, o muchos otros, en las que estamos recibiendo actualizaciones, prácticamente en tiempo real, sin necesidad de recargar la página.

Por ejemplo, en Google Docs, si estamos editando un documento online y otro usuario accede al mismo, podemos ver sobre la marcha que ha entrado, e incluso las modificaciones que va realizando sobre el documento. O algo más cotidiano, en un simple chat vía web van apareciendo los mensajes tecleados por nuestros compañeros de sala como por arte de magia. Ambos sistemas utilizan el mismo tipo de solución: el envío asíncrono de datos entre servidor y clientes en tiempo real.

En esta serie de artículos veremos cómo podemos implementar sorprendentes funcionalidades de este tipo utilizando SignalR, un framework open source desarrollado por gente del equipo de ASP.NET, que nos facilitará bastante la tarea.
martes, 10 de enero de 2012
ASP.NET MVCHace poco escribía un post en el que mostraba cómo se podía conseguir establecer el foco inicialmente en un control de edición, algo que era posible con Webforms pero no directamente con las herramientas que ASP.NET MVC trae de fábrica.

La solución propuesta consistía en introducir en la vista código de script para desplazar el foco hasta el control indicado mediante una llamada al helper Html.SetFocusTo(), que implementábamos en el mismo post, aunque hay otras formas para conseguirlo.

martes, 20 de diciembre de 2011
ASP.NET MVCHace pocas semanas profundizamos en los mecanismos de obtención de metadatos del modelo en ASP.NET MVC y vimos cómo extender el framework para dotarlo de vías alternativas desde las que obtener esta información usando un proveedor personalizado.

Sin embargo, no es este el único mecanismo de extensión del framework a este respecto: también podemos crear fácilmente nuevos atributos que aporten información extra de metadatos a las clases y propiedades del Modelo. Y esta es la razón de ser del interfaz IMetadataAware.
miércoles, 14 de diciembre de 2011
ASP.NET MVCUno de los aspectos más criticados por los desarrolladores cuando comienzan a trabajar con ASP.NET MVC es el hecho de tener que volver a resolver problemas que estaban ya más que solucionados en Webforms.

Y uno de estos casos es un detallito muy simple pero útil: establecer el foco de edición en un control concreto al cargar una página. En Webforms era suficiente con asignar al atributo defaultFocus del tag <form> el nombre del control que nos interesara, y ya lo teníamos solucionado; ASP.NET MVC no trae ninguna solución “de serie” para conseguirlo, aunque, como veremos en este post, no es mucho más complicado que la alternativa Webforms una vez hemos preparado la infraestructura necesaria.
martes, 29 de noviembre de 2011
ASPNETMVCEn ASP.NET MVC normalmente utilizamos atributos para aportar información adicional a las propiedades del Modelo, incluyendo detalles como su descripción textual, formato de presentación, tipo de datos, etc. Esta información puede ser utilizada desde la capa vista para generar etiquetas, editores y, en algunos casos, incluso lógica de edición o presentación en la página.

Sin embargo, los atributos en el propio código de la clase no son la única vía para especificar metadatos en el framework. En este post veremos cómo extender el framework para crear nuevas vías para especificar esta información.
martes, 15 de noviembre de 2011
No es algo excesivamente frecuente, pero en ocasiones podemos necesitar limpiar el valor de un campo de tipo file (el que usamos para hacer los uploads) de un formulario, por ejemplo, para evitar que el usuario envíe un archivo que por cualquier motivo no deba ser subido al servidor.

O dicho de otra forma, imaginemos la siguiente porción de un formulario en pantalla, que podría ser generada con el código que podéis ver justo a continuación:
Campo de envío de archivos
<label for="archivo">Archivo a enviar:</label>
<input type="file" id="archivo" name="archivo" />
<input type="button" onclick="limpiarInputFile('archivo');" value="Limpiar" />

Y la pregunta en este momento sería, ¿qué código deberíamos implementar en la función limpiarInputFile() que estamos utilizando en el evento onclick si quisiéramos limpiar o inicializar el contenido del campo archivo?

Aunque de forma intuitiva podría parecer que basta con establecer el valor del campo a una cadena vacía, una prueba rápida nos demostrará que esto no es posible. Desde hace ya tiempo, por motivos de seguridad, los navegadores no permiten el acceso de escritura a la propiedad value en los campos de envío de archivos, por lo que nos encontramos una vía sin salida. Esto lo vemos con el siguiente código, utilizando jQuery:

    function limpiarInputfile(id) {
        var input = $('#' + id);
        var nuevoValor = "c:\\windows\\system32\\mspaint.exe";
        
        alert(input.val());       // Muestra "C:\Datos.dat"
        input.val(nuevoValor);    // Establecemos un nuevo valor
        alert(input.val());       // ¡¡Muestra "C:\Datos.dat"!!
    }

Como vemos, somos vilmente ignorados cuando intentamos establecerle un valor.

Pues bien, una posible solución consiste en eliminar del DOM el elemento <input type="file"> y volver a crearlo justo después en el mismo lugar. He visto por ahí varias implementaciones que obligaban a introducir este elemento dentro de un contenedor, pero he creado otra que creo que es más sencilla e igual de efectiva:

    function limpiarInputfile(id) {
        var input = $('#' + id);
        var clon = input.clone();  // Creamos un clon del elemento original
        input.replaceWith(clon);   // Y sustituimos el original por el clon
    }

Y eso es todo :-). Observad que lo único que hacemos es crean un clon del elemento original cuyo value por supuesto estará en blanco (recordad que esta propiedad no se puede establecer), y justo a continuación eliminamos el elemento original sustituyéndolo por este clon.

Si queremos generalizar este código e implementar esta funcionalidad de forma no intrusiva podríamos hacer lo siguiente:

    $(function () {
        $("input[type=file]").after(
            "<input type='button' class='limpiar-inputfile' value='Limpiar'>"
        );
        $(".limpiar-inputfile").click(function () {
            var input = $(this).prev("input[type=file]");
            var clon = input.clone();
            input.replaceWith(clon);
            return false;
        });
    });

Este código añade automáticamente un botón “Limpiar” a continuación de todos los <input type=file> de la página, implementando en el manejador del evento click la lógica de inicialización del componente que hemos visto antes. De esta forma, sólo se introducirá en la página el botón de limpiado cuando estén activados los scripts, que es en el único momento en que su ejecución tendrá sentido con la solución propuesta.

Espero que os sea de utilidad.

Publicado en: Variable not found.
martes, 8 de noviembre de 2011
ASP.NET MVCEn la pasada charla sobre el sistema de validaciones de MVC 3 vimos un ejemplo, creo que bastante ilustrativo, de los proveedores de validación del framework. Concretamente, implementamos un proveedor capaz de obtener las anotaciones partiendo de las restricciones definidas en el web.config. Es decir, las reglas de comprobación como Required o StringLength no las definíamos a nivel de código mediante atributos, sino en el archivo de configuración, lo que podía aportar interesantes ventajas vistas a flexibilizar nuestras soluciones.

En este post vamos a ver un nuevo ejemplo de cómo utilizar el mismo mecanismo de proveedores, pero esta vez para conseguir de forma muy sencilla simplificar el código de clases del modelo en las que todas sus propiedades sean por defecto obligatorias, salvo aquellas en las que indiquemos expresamente lo contrario.