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!
domingo, 20 de diciembre de 2009
Por cosas de la procrastinación, tenía una máquina pendiente de formatear desde hace unos años ;-P, y he aprovechado el fin de semana para hacerlo. Como sabréis, esto no es tarea fácil, se requiere mucho pragmatismo, gran concentración y, principalmente, vencer al Diógenes digital que todos llevamos dentro ;-D.

Y claro, una vez que nos ponemos, el problema es cómo conseguir minimizar los daños colaterales. Esta máquina, aunque algo antigua, estaba todavía en uso y tenía gran cantidad de archivos y software instalado en su disco de sistema. También contaba con un disco exclusivamente para datos, pero éste no me suponía ningún problema.

En estos casos normalmente no basta con hacer un salvado del disco duro, a lo bruto, sobre otro disco, formatear y listo; así sólo conseguiremos tener acceso desde la nueva máquina a los ficheros físicos del sistema anterior, pero no podremos realizar tareas de nivel superior, como copiar configuraciones, exportar o importar datos desde aplicaciones, etc. Y lo que es imposible, al menos en mi caso, es planificar este movimiento con tanta exactitud que no se quede ni un byte por detrás.

La conclusión a la que llegué es que la única forma de hacerlo con cierta tranquilidad era virtualizando el sistema anterior. Esto me permitiría acceder en vivo a la configuración anterior y traspasar archivos con la seguridad que necesitaba.

Dis2VhdY aquí es donde ha entrado en juego Disk2Vhd, la magnífica herramienta de Sysinternals (¿he dicho Microsoft? ;-)), que es capaz de generar un disco duro virtual (archivo con extensión .vhd) a partir de un disco duro físico. Y lo mejor de todo, que puede hacerlo sobre el propio equipo que está generando el volcado, es decir, en caliente.

El único requisito es disponer de espacio libre (por ejemplo, como yo lo hice, en un disco duro externo), estar corriendo Windows XP SP2, Windows Server 2003 SP1 o superiores, incluyendo sistemas x64, y suficiente espacio en un disco duro como para almacenar el archivo resultante del volcado.

La aplicación es muy sencilla de utilizar. Se descarga desde su sitio web y se ejecuta, no requiere instalación (también puedes usarla directamente); tras ello, simplemente debemos elegir los discos a virtualizar, seleccionar una ubicación de salida para el archivo .vhd, esperar unas horitas y ya lo tenemos. Normalmente bastará con virtualizar el disco de sistema.
Consejo #1: para que la conversión se realice más rápidamente, lo mejor es hacer que el .vhd a generar resida en un disco duro distinto del que estamos virtualizando, aunque se puede realizar sobre el mismo.
Una vez con el archivo .vhd a buen recaudo, ya podemos formatear tranquilamente el disco del sistema, montar el nuevo sistema operativo y comenzar a instalar las aplicaciones que vayamos a necesitar.

Para acceder al sistema anterior tal y como estaba antes de la masacre, basta con instalar Virtual PC, crear una máquina virtual, “engancharle” el disco .vhd que hemos generado, y arrancar normalmente, pero ojo:
Consejo #2: haz una copia de seguridad del archivo .vhd antes de realizar cambios sobre el disco duro virtual. Me he encontrado algunos callejones sin salida en los que me ha venido de perlas (p.e., petes del Virtual PC al instalar las Virtual Machine Additions que me dejaban la máquina virtual inutilizada).
La primera vez que enciendes la máquina virtual se llevará un buen rato arrancando Windows; es lógico, pues todos los controladores que tiene instalados corresponden a la máquina física, y el nuevo entorno debe ser configurado, prácticamente igual que si hubiéramos instalado el disco duro físicamente en otro equipo y arrancáramos desde él. Cuando esta reconfiguración finaliza, podremos utilizar con normalidad la máquina virtual e ir pasando las configuraciones y archivos al nuevo sistema tranquilamente.
Consejo #3: revisa la configuración básica de tu máquina virtual para evitar conflictos y funcionamiento anómalo en algunas aplicaciones; nombre de máquina, dirección IP, variables de entorno del sistema operativo, etc. En mi caso, la variable TMP/TEMP apuntaba a una unidad inexistente en el entorno virtual, y provocó algún que otro problemilla.
Y por si lo que queremos acceder a los datos del equipo anterior en bruto, existe la posibilidad de montar un archivo .vhd como si fuera un disco duro más y acceder a su contenido directamente, por lo que podemos evitar la incomodidad de tener que arrancar Virtual PC para todo. Si usas Windows 7 (o 2008), esta capacidad viene “de serie”, sólo tienes que activarla desde el administrador de discos:

Montando un VHD en Windows 7

Publicado en: Variable not found
jueves, 17 de diciembre de 2009

ASP.NET MVC 2 RC disponible Recién salida del horno, tenemos ya la versión candidata de ASP.NET MVC 2, la última antes de la versión final que aparecerá antes de marzo, el mes previsto para el lanzamiento de Visual Studio 2010.

Como indica Haack en su post, la mayor parte del trabajo se ha centrado en corregir bugs e introducir mejoras a funcionalidades existentes, como:

  • relativas a la validación en cliente:
    • separación de los scripts de validación a un único archivo, que puede ser incluido en cualquier parte de la vista.
    • soporte para la globalización de mensajes.
    • mejora de Html.ValidationSummary() para que soporte mensajes de error a nivel de modelo, y no vinculados a campos concretos.
    • se permite la inclusión de botones que se salten la validación de formularios.
    • se permite especificar cuándo se ejecutan las validaciones: mientras el usuario teclea, cuando se pierde el foco en un control o sólo en el submit.
  • otras mejoras:
    • plantillas T4 que generan código según la versión del framework destino.
    • el código HTML generado por el diálogo “Add View” es consistente con los templated helpers.

Enlaces:

Publicado en: Variable not found

miércoles, 16 de diciembre de 2009

Validación de formularios ASP.NET desde javascript Cada vez que tengo que forzar la validación de los datos de un formulario Webforms mediante javascript me veo obligado a preguntarle a Google, ese que todo lo sabe, cómo era el nombre de la función. Cosas de la edad, supongo ;-)

Así que, a modo de auto-recordatorio y con la intención de que pueda ser útil a alguien más, ahí va: la función se llama Page_ClientValidate(). Retorna “true” si, una vez evaluados todos los validadores de la página, el valor de los campos es correcto (al menos en cliente; otra cosa son las comprobaciones en servidor, p.e., las definidas en un CustomValidator).

Y como ejemplo de uso, puede valer el siguiente. Se trata de un botón de envío en un formulario donde se compone un correo electrónico:

   1: ...
   2: <asp:Button ID="btnEnviar" runat="server" Text="Enviar mail" 
   3:      OnClick="btnEnviar_Click"
   4:      OnClientClick="return confirmar();"
   5: />
   6: ...
   7:  
   8: <script type="text/javascript">
   9:     function confirmar() {
  10:         if (!Page_ClientValidate())   // Fuerza la validación en cliente
  11:             return false;             
  12:             
  13:         return confirm('¿Seguro que desea realizar el envío?');
  14:     }
  15: </script>

Como se puede observar, en el atributo OnClientClick del botón incluye un script en el que se retorna el valor devuelto por la función confirmar. Si el retorno es false, se cancela el Postback, evitando así que se invoque al evento btnEnviar_Click que es el que realiza el envío del mail propiamente dicho.

En el cuerpo de la función confirmar(), en primer lugar, validamos la página; si ésta no supera el proceso, los validadores habrán mostrado sus mensajes de error y retornamos falso, haciendo que se anule el postback. Si la validación es correcta, solicitamos al usuario que confirme la acción y retornamos su decisión.

Publicado en: Variable not found.

domingo, 13 de diciembre de 2009

Plantillas T4 para ASP.NET MVCHe comentado muchas veces por aquí que el proyecto T4MVC, creado por David Ebbo, es una magnífica solución para evitar el uso de “magic strings”, o literales de cadena, para identificar vistas, acciones o controladores en un proyecto ASP.NET MVC, lo cual es considerado una práctica a evitar por ser bastante tendente a introducir errores en nuestras aplicaciones.

El término T4 viene de “Text Template Transformation Toolkit”, una tecnología presente en Visual Studio 2008 y siguientes (incluso en VS2005 instalando algunos complementos), utilizada para generar código automáticamente desde el IDE partiendo de plantillas.

Estas plantillas son archivos con extensión .tt que contienen un script (escrito en un lenguaje .NET como C#), cuya misión es exclusivamente generar código fuente. Cada vez que la plantilla se modifica, ésta es ejecutada y su resultado (por ejemplo, un archivo .cs) incluido en el mismo proyecto donde se encuentra.

En el caso que nos ocupa, T4MVC, se trata de una plantilla que genera clases de utilidad en función del contenido del propio proyecto ASP.NET MVC donde se encuentran, facilitando el acceso a acciones, vistas, e incluso archivos estáticos del mismo.

Por ejemplo, es capaz de recorrer las carpetas de las vistas (/Views o la que le indiquemos), y por cada una de ellas generar una propiedad que nos permita acceder a dicho nombre usando tipado fuerte de forma mucho más elegante:

public virtual ActionResult AcercaDe()
{
    return View("About");      // Antes
    return View(Views.About);  // Con T4MVC
}

También nos permite hacer lo mismo con las referencias a acciones de los controladores, posibilitando la creación de enlaces desde las vistas con una facilidad pasmosa. Observad en el siguiente ejemplo que se han eliminado tanto la referencia literal a la acción “Eliminar” como la referencia al parámetro con nombre “id”, que aunque no sea una constante de cadena, está fuera de todo control en tiempo de compilación:

// En vistas...
<%= Html.ActionLink("Eliminar!", "Eliminar", new { id = Model.Id } ) %> // <- Antes
<%= Html.ActionLink("Eliminar!", MVC.Productos.Eliminar(Model.Id)) %>   // <- Con T4MVC
 
// Y en controladores...
return RedirectToAction("Eliminar", new { id = producto.Id });  // <- Antes
return RedirectToAction(MVC.Productos.Eliminar(producto.Id));   // <- Con T4MVC

Otra curiosa posibilidad es referenciar los archivos estáticos de contenidos, como por ejemplo imágenes, permitiéndonos detectar en compilación si movemos o cambiamos de nombre el recurso:

<img alt="Logo" src="../Content/logo.gif" />             // <- Antes
<img alt="Logo" src="<%= Links.Content.logo_gif %>" />   // <- Con T4MVC

O incluso con librerías de scripts… pero en este caso, aporta otra interesante capacidad: cambiar a la versión minimizada de la misma (<librería>.min.js), si está disponible, cuando el proyecto se ejecuta en explotación:

<script type="text/javascript" src="/Scripts/jquery-1.3.2.js"></script>             // <- Antes
<script type="text/javascript" src="<%= Links.Scripts.jquery_1_3_2_js %>"></script> // <- Con T4MVC

En fin, que las posibilidades son amplias, y las ventajas de utilizar este método, obvias; por ejemplo, si renombramos una vista, se detectará el problema en tiempo de compilación, y será más sencillo localizar los puntos a corregir. Y por no hablar de la alegría de disfrutar de intellisense para referenciar vistas, acciones o recursos mientras programamos. :-)

T4MVC, siempre recién modificado De momento sólo le he encontrado un pequeño problema: únicamente se genera código cuando modificamos la plantilla o cuando ejecutamos manualmente el archivo .tt. (con el botón derecho del ratón, “ejecutar herramienta personalizada”).

Para evitar la molestia de tener que hacerlo a mano, David, el artífice de la herramienta, ha empleado un ingenioso truco: engañar al IDE para que piense que la plantilla siempre ha sido modificada cuando tenemos el fichero abierto. Así, en cada vez que compilemos el proyecto, el proceso del auto-guardado hará que se ejecute el script y generando los archivos oportunos, volviendo a marcar el archivo .tt como modificado.

Otra mala noticia es que no podemos utilizar esta solución con Visual Web Developer 2008 Express, sólo funciona con sus hermanos mayores. La buena es que sí será posible utilizarlo, y de hecho ya funciona en la Beta 2, en todas las ediciones de Visual Studio 2010, express incluida :-)

En cuanto a la forma de utilizarlo, es bien sencillo. Una vez descargada la plantilla, basta con descomprimir los dos archivos que incluye (.tt y .settings.t4) en el raíz del proyecto MVC para comenzar a disfrutarlo.

Enlaces:

Publicado en: Variable not found.

martes, 8 de diciembre de 2009

Controladores en ASP.NET MVCCuando desarrollamos sobre el framework MVC, estamos acostumbrados a crear nuestros controladores partiendo de la clase base Controller, que nos proporciona métodos, propiedades y mecanismos que nos ahorran mucho trabajo en su implementación. Por ejemplo, toda la lógica de localización e invocación de las acciones está definida en esta clase, así como métodos de creación de los tipos más utilizados de ActionResult, las llamadas al model binder, o propiedades de acceso rápido a datos del contexto.

Sin embargo, y es una prueba más de la flexibilidad de diseño del framework, esto no es ni mucho menos obligatorio. La factoría de controladores por defecto no es tan exigente a la hora de localizar estas clases como primer paso durante el proceso de una petición.

Si observamos el código del marco de trabajo, concretamente la clase ControllerTypeCache, nos encontramos con este método estático, responsable de determinar si un tipo dado cumple las condiciones necesarias para ser considerado un controlador válido:

internal static bool IsControllerType(Type t)
{
    return ((((t != null)
             && t.IsPublic) 
             && (t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) 
             && !t.IsAbstract)) 
             && typeof(IController).IsAssignableFrom(t));
}

Como podemos observar, en primer lugar se comprueba que la clase sea pública; es lógico, pues el framework debe instanciarla. A continuación, comprueba que su nombre atienda a la convención de acabar en “Controller” (sí, la convención que aprendimos a desmontar hace algún tiempo ;-)). La siguiente es que no se trate de una clase abstracta, algo básico si pretendemos instanciarla. Por último, se exige que implemente el interfaz IController cuya definición es la siguiente:

public interface IController
{
    void Execute(RequestContext requestContext);
}

Es decir, en ningún caso aparece la clase Controller, ni siquiera la clase ControllerBase, antecesora de la primera. Basta con implementar el método Execute() definido en el interfaz.

Por tanto, cumpliendo estos requisitos podríamos escribir un controlador tan reducido como el siguiente:

using System.Web.Routing;
using System.Web.Mvc;
 
namespace MasterViewModel.Controllers
{
    public class PersonaController : IController
    {
        public void Execute(RequestContext contexto)
        {
            contexto.HttpContext.Response.Write(
                    "Ejecutando la acción " + contexto.RouteData.Values["action"] +
                    " con parámetro " + contexto.RouteData.Values["id"]
            );
        }
    }
}

Incluyendo dicho controlador en un proyecto en el que mantengamos las rutas por defecto, si realizamos una petición del tipo GET /Persona/Beber/Cerveza, obtendremos en el navegador un mensaje como el siguiente:

Resultado de ejecución

Sin embargo, aunque el desarrollo de controladores así de ligeros pueda resultar a priori una idea atractiva  pensando sobre todo en optimizar aún más la velocidad de respuesta, en la práctica no tiene demasiado sentido hacerlo. Sería prácticamente igual de eficiente sobrescribir el método ExecuteCore de la clase Controller, y nos beneficiaríamos de los métodos y propiedades implementados en la misma.

Publicado en: Variable not found.

martes, 1 de diciembre de 2009

Redirección basada en retornar un HTTP 302ASP.NET MVC ofrece “de serie” mecanismos para transferir el control desde una acción a otra utilizando para ello redirecciones HTTP. Esto significa que cuando un cliente realiza una petición y el método de acción desea que ésta sea procesada desde otra acción, el navegador será informado de la URL a la que debe dirigirse mediante una respuesta de tipo 302 para que, tras recibirla, realice una nueva solicitud que finalmente ejecutará la lógica apropiada.

En el diagrama de la derecha se muestra el proceso completo de transferencia desde una petición a /home/mail hacia /mail/index utilizando la ruta por defecto, y muestro una posible implementación en ASP.NET MVC a continuación:

public class HomeController : Controller
{
    [...]
    // GET /Home/Mail
    public ActionResult Mail()
    {
        return RedirectToAction("index", "mail");
    }
 
}
 
public class MailController : Controller
{
    [...]
    // GET /Mail
    public ActionResult Index()
    {
        return View();
    }
 
}

Aunque es la forma habitual de realizar redirecciones en ASP.NET MVC, hace unos días estaba pensando que hay ocasiones en las tanta petición puede resultar pesada y vendría bien disponer de algo parecido al Server.Transfer, presente desde que peleábamos con ASP clásico, que era capaz de transferir la ejecución a otro punto sin necesidad de que el cliente replanteara la petición.

Y no es que me parezca una práctica especialmente recomendable, puesto que hay muchas cuestiones de ese tipo que pueden resolverse en MVC a nivel configuración de rutas o simplemente replanteando la estructura de direcciones del sitio, pero la verdad es que cuando te pica el gusanillo…

Intento erróneo #1: invocar directamente una acción desde otra

Bueno, en realidad este intento no llegué a hacerlo, pero se trata de un error bastante frecuente cuando se empieza con ASP.NET MVC framework, y me ha parecido interesante reflejarlo aquí como aviso a navegantes:

public class HomeController : Controller
{
    // GET /Home/Mail
    public ActionResult Mail()
    {
        return new MailController().Index(); // <- Mal
    }
 
}

Aunque a primera vista parece tener sentido, y de hecho funcionará muchas veces, se trata de una mala práctica y puede causarnos muchos problemas, y sobre todo, como en el ejemplo anterior, si se trata de llamadas de acciones de distintos controladores. Hay que tener en cuenta que en el contexto de petición estará la información de la petición inicial (/home/mail), no el que podría esperarse desde el método de acción new MailController().Index().

También es peligroso porque al invocarlo directamente estaríamos saltándonos todos los filtros que hubiéramos podido establecer mediante atributos en la acción final, MailController.Index(). Imaginad, por ejemplo, que la acción está protegida por un [Authorize]… :-O

Intento erróneo #2: invocar a Server.Transfer en el controlador

El segundo intento fue, pues eso, invocar directamente a Server.Transfer() desde el cuerpo de una acción. Pensaba que en cualquier caso no era una buena idea puesto que el método de acción que lo invocase sería difícilmente comprobable usando pruebas unitarias, pero bueno, estaba dispuesto a sacrificar esta ventaja:

public class HomeController : Controller
{
    // GET /Home/Mail
    public ActionResult Mail()
    {
        Server.Transfer(Url.Action("Index", "Mail")); // Mal
        return new EmptyResult();                     // En realidad podría devolver
    }                                                 // un nulo, o cualquier otra cosa
}

Al ejecutar el proyecto, el resultado obtenido es una bonita excepción “Error al ejecutar la solicitud secundaria” al intentar procesar la transferencia hacia la dirección “/Mail” (generada por el helper Url.Action()).

Primer acercamiento a la solución correcta: default.aspx

Aparentemente, la solución para transferir el control hacia otra acción pasa por recorrer el ciclo de vida completo de la petición, pero sin necesidad de que el navegador vuelva a hacerla. Ahora la cuestión es cómo conseguir esto armando el menor estropicio posible.

Si os fijáis, al crear el proyecto ASP.NET MVC, en la plantilla se habrá incluido un archivo en la carpeta raíz del mismo, llamado default.aspx. Su objetivo es transferir la ejecución al framework MVC si un usuario realiza una petición al raíz del sitio, siendo esta página el documento por defecto.

Observemos el código por defecto del método Page_Load que encontramos en su code-behind:

public void Page_Load(object sender, System.EventArgs e)
{
    // Change the current path so that the Routing handler can correctly interpret
    // the request, then restore the original path so that the OutputCache module
    // can correctly process the response (if caching is enabled).
 
    string originalPath = Request.Path;
    HttpContext.Current.RewritePath(Request.ApplicationPath, false);
    IHttpHandler httpHandler = new MvcHttpHandler();
    httpHandler.ProcessRequest(HttpContext.Current);
    HttpContext.Current.RewritePath(originalPath, false);
}

Como indican los comentarios, lo que se hace es cambiar la ruta original de la petición, instanciar un nuevo manejador MVC (MvcHttpHandler) y hacer que éste se encargue de procesarla… o en otras palabras, estamos transfiriendo la ejecución, justo lo que queríamos conseguir. Después, para evitar daños colaterales, se vuelve a dejar la ruta de la petición como estaba.

Por tanto, podríamos utilizar este mismo código y crear un método de extensión sobre la clase Controller:

public static class ControllerExtensions
{
    public static void Transfer(this Controller controller, string url)
    {
        string originalPath = controller.Request.Path;
        HttpContext.Current.RewritePath(url, false);
        IHttpHandler httpHandler = new MvcHttpHandler();
        httpHandler.ProcessRequest(HttpContext.Current);
        HttpContext.Current.RewritePath(originalPath, false);
    }
}

Así, podríamos utilizarlo desde nuestras acciones de la siguiente forma:

public class HomeController : Controller
{
    [...]
 
    // GET /Home/Mail
    public ActionResult Mail()
    {
        this.Transfer(Url.Action("index", "mail"));
        return null;
    }
}

Con esto ya tenemos una primera solución a nuestro problema. Sin embargo, aunque puede parecer muy limpio, y de hecho yo estaba bastante conforme con esta solución, este código tiene varios problemas.

En primer lugar, realizar pruebas unitarias sobre la acción Mail sería realmente complicado. Además, la invocación a Transfer() ejecutaría la acción asociada a la ruta de la URL indicada, pero al finalizar continuaría ejecutándose el método Mail(), desde donde la hemos invocado. Esto puede provocar efectos curiosos, si por ejemplo retornásemos un ViewResult o cualquier otro tipo de contenido desde la misma (serían enviados de forma consecutiva el cliente).

Solución final: crear un ActionResult personalizado

Aunque el enfoque anterior es correcto y podría ser válido a pesar de sus inconvenientes, encuentro una solución más fina en una pregunta de StackOverflow, How to simulate Server.Transfer in ASP.NET MVC: crear un resultado de acción (ActionResult) personalizado.

/// <summary>
/// Transfers execution to the supplied url.
/// </summary>
public class TransferResult : RedirectResult
{    
    public TransferResult(string url): base(url)    {    }
   
    public override void ExecuteResult(ControllerContext context)    
    {        
        var httpContext = HttpContext.Current;        
        httpContext.RewritePath(Url, false);        
        IHttpHandler httpHandler = new MvcHttpHandler();        
        httpHandler.ProcessRequest(HttpContext.Current);    
    }
}

De esta forma, evitamos los dos problemas de la anterior aproximación. El método de acción seguiría siendo fácilmente testeable, y evitaríamos resultados indeseados al tratarse de un tipo de respuesta, que evita la posibilidad de introducir código adicional.

Publicado en: Variable not found.