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 ;)

18 años online

el blog de José M. Aguilar

Inicio El autor Contactar

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

¡Microsoft MVP!
martes, 6 de julio de 2010
ASP.NET MVC Esta cuestión, lanzada por un amigo de Variable not found en Facebook, comenzaba un pequeño e interesante debate hace unos días, tras la publicación del enlace al post Persisting model state in ASP.NET MVC using Serialize HTMLHelper, en el que se describía la posibilidad de mantener el estado entre peticiones utilizando un mecanismo similar al infame ViewState de Webforms.

Esta herramienta, como otras muchas, se encuentra en el proyecto MvcFutures, un ensamblado distribuido a través de CodePlex, que ha sido creado por el mismo equipo del framework MVC para experimentar nuevas características y funcionalidades futuras del producto.

Su funcionamiento es muy sencillo. Imaginemos el siguiente controlador, que envía a la vista un objeto de tipo User:

public ActionResult Prueba()
{
    return View("MiVista", new User
                                {
                                    Nombre = "José",
                                    FechaNacimiento = DateTime.Now,
                                    Email = "micorreo@correo.com",
                                    Pais = "España",
                                    Provincia = "Sevilla"
                                });
}

Desde la vista correspondiente podemos serializar el objeto del Modelo cuyo estado nos interese preservar, como en el siguiente ejemplo, en el que hacemos persistir las propiedades del objeto User almacenado en la propiedad  Model de la vista:

<% using (Html.BeginForm()) { %>
    <%= Html.Serialize("Userdata", Model) %>
    ... (resto del formulario)
<% } %>

Y ya desde la acción receptora de los datos del formulario, para volver a materializar el objeto serializado bastaría con incluirlo como parámetro de la misma, decorándolo con el atributo [Deserialize] de la siguiente forma:

[HttpPost]
public ActionResult Prueba([Deserialize]User userData, ... ) // Otros parámetros recibidos
{
    // ...
}

Como de costumbre, el nombre del parámetro de la acción debe coincidir con el asignado al campo oculto donde se ha almacenado la información, que coincide con el identificador que hemos indicado ("Userdata") en la llamada al helper en la vista.

El único requisito fundamental para poder obrar esta magia es que la clase y todas sus propiedades sean serializables, como la siguiente:

[Serializable]
public class User
{
    public string Nombre { get; set; }
    public DateTime FechaNacimiento { get; set; }
    public string Pais { get; set; }
    public string Provincia { get; set; }
    public string Email { get; set; }
    public string FacebookUser { get; set; }
    public string TwitterUser { get; set; }
}

¿Y cómo ve ve esto en tiempo de ejecución? El resultado será algo como el siguiente, donde podemos apreciar un cierto tufillo a ViewState:

<form action="/user/prueba" id="form0" method="post">
    <input name="userData" type="hidden" 
        value="/wEy6QIAAQAAAP////8BAAAAAAAAAAwCAAAATk12Y0Z1dHVyZXNTZXJp />
              YWxpemF0aW9uLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhb
              CwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAI012Y0Z1dHVyZXNTZXJpYW
              xpemF0aW9uLk1vZGVscy5Vc2VyBwAAABc8Tm9tYnJlPmtfX0JhY2tpbmd
              GaWVsZCA8RmVjaGFOYWNpbWllbnRvPmtfX0JhY2tpbmdGaWVsZBU8UGFp
              cz5rX19CYWNraW5nRmllbGQaPFByb3ZpbmNpYT5rX19CYWNraW5nRmllb
              GQWPEVtYWlsPmtfX0JhY2tpbmdGaWVsZB08RmFjZWJvb2tVc2VyPmtfX0
              JhY2tpbmdGaWVsZBw8VHdpdHRlclVzZXI+a19fQmFja2luZ0ZpZWxkAQA
              BAQEBAQ0CAAAACgAAAAAAAAAACgoKCgoL"
    ... (resto del formulario)
</form>

Por defecto, el contenido del objeto se volcará en la página utilizando codificación Base64, aunque esto puede modificarse utilizando la sobrecarga del helper Serialize() que permite indicar el tipo de serialización (SerializationMode) a emplear, eligiéndola entre texto plano (por defecto, PlainText o Base64), cifrada, firmada, o firmada y cifrada.

En cualquier caso, se trata de agregar un chorizo de cierto volumen a la página, que la hará más pesada y afectará al rendimiento de nuestro sistema, sobre todo si el objeto que queremos hacer persistir es complejo, por lo que podría pensarse…

¿Estamos trayendo de nuevo el fantasma del ViewState a las aplicaciones MVC?

En mi opinión, no. Y si bien es cierto que estamos utilizando una técnica parecida al ViewState para conservar el estado de objetos, no creo que sea un mal comparable.

El ViewState en Webforms no es una opción si realmente queremos sacar provecho de su potencia; si lo eliminamos totalmente, dejaríamos Webforms como un ASP clásico con esteroides ;-). Aunque ASP.NET 4 ha mejorado mucho el control sobre éste, sigue siendo necesario su uso para mantener el estado de la vista y dar soporte al modelo stateful de esta tecnología.

En ASP.NET MVC, la persistencia de un objeto completo en la página es un caso excepcional; no se me ocurren muchos escenarios en los que pueda sernos útil, y ninguno donde sea la única solución existente. Por ejemplo, si tenemos un proceso que el usuario debe realizar en varios pasos, u otro escenario en el que debamos mantener el estado del Modelo, está claro que debemos hacerlo persistir de algún modo, ya sea en cliente o en servidor.

Como comentaba el amigo Eduard Tomás hace unos días, utilizar variables de sesión no es siempre la mejor opción, y a veces hay que recurrir al cliente para almacenar cierta información. Imaginemos, por ejemplo, un asistente para el registro de usuarios en una comunidad on-line; si realmente no nos interesa almacenar dato alguno en el servidor hasta que el usuario confirme su registro en el último paso, una posibilidad bastante interesante sería delegar el almacenamiento temporal, la información introducida en cada uno de los pasos del asistente, a la capa cliente.

Aún en este escenario, el uso de la serialización de MvcFutures es totalmente opcional. De hecho, es perfectamente posible conseguir el mismo efecto introduciendo manualmente las propiedades de los objetos en campos hidden individuales, y recuperándolas posteriormente desde el controlador  utilizando el Model Binder. Sin embargo, cuando el objeto sea complejo, el helper Serialize() puede ahorrarnos muchas pulsaciones de tecla y evitar errores, aunque sea a costa de aumentar el peso de la página (imaginemos un objeto compuesto a su vez por otros, o colecciones).

En lo que seguro que estamos totalmente de acuerdo, como siempre ocurre en estos casos, es en la necesidad de aplicar el sentido común y prudencia en su uso. Antes de utilizar esta técnica hay que tener en cuenta el peso adicional que vamos a añadir a las páginas; asimismo, desde el punto de vista de la seguridad, siempre tener en mente que la información almacenada en cliente es fácilmente manipulable.

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

Publicado en: Variable not found
martes, 29 de junio de 2010
Las validaciones automáticas de ASP.NET MVC son una fórmula muy útil y productiva de comprobar los datos introducidos por nuestros usuarios. Como sabemos, basta decorar las propiedades de las entidades del Modelo con atributos que indiquen las restricciones a aplicar en cada caso, y el framework MVC se encargará del resto, incluso en la capa cliente.

Como sabemos, el sistema de validación es capaz de generar scripts capaces de comprobar en cliente que los datos introducidos encajan con las restricciones introducidas en el modelo mediante anotaciones de datos.

Básicamente, el script comprueba los datos durante la pérdida de foco de los controles, y justo antes de realizar el submit de datos al servidor, no permitiendo el envío hasta que los campos contengan información correcta. Este automatismo, válido y conveniente la mayor parte de las veces, complica algunos escenarios.

Mirando el código del script MicrosoftMvcValidation.js he descubierto un pequeño truco para poder enviar los datos de un formulario omitiendo las validaciones en cliente, es decir, forzar el submit sean cuales sean los datos introducidos en los controles.

La cuestión está en indicar en el botón de envío que no debe realizar las validaciones, estableciéndole una propiedad llamada disableValidation. A lo bestia, bastaría con añadirle el atributo al botón como se muestra en el siguiente código:

disableValidation
De esta forma, en la pulsación del botón “atrás” (en este caso se trata de un asistente) se saltaría los controles en cliente y podríamos volver al paso anterior.

Obviamente con el método anterior estaríamos introduciendo marcado incorrecto en la página. Si queremos pasar las validaciones W3C podemos conseguir lo mismo desde script, lo que haría innecesaria la aparición del atributo disableValidation en el tag <button>:

<script type="text/javascript">
    document.getElementById("atras").disableValidation = true;
</script>

Publicado en: Variable not found
lunes, 21 de junio de 2010
ASP.NET MVCHace unos días experimentábamos con controladores capaces de implementar automáticamente la lógica de acciones cuya misión era únicamente retornar la vista por defecto.

Así, partíamos de un controlador como el siguiente:

public class HomeController: Controller
{
    public ActionResult Index()
    {
        return View();
    }
 
    public ActionResult Company()
    {
        return View();
    }   
 
    public ActionResult Services()
    {
        return View();
    }
    
    public ActionResult Contact()
    {
        return View();
    }
    
}

Y veíamos cómo simplemente heredando de la clase AutoController que definíamos en el mismo post, podíamos omitir la implementación de las acciones anteriores, consiguiendo un código mucho más compacto:

public class HomeController: AutoController
{
}

Está claro que utilizando como base la clase AutoController hemos ahorrado mucha codificación, pero, perezosos como somos, seguro que todavía estamos escribiendo más de la cuenta.

Si el único código que vamos a tener en un controlador es la definición de la propia clase, quizás podamos hacer algo para evitar incluso tener que codificar eso, ¿no? De la misma forma que hicimos con las acciones, seguro que podríamos crear un controlador por defecto para procesar las peticiones entrantes para las que no podamos encontrar un controlador específico.

¡A por la factoría de controladores!

La factoría de controladores es el componente de ASP.NET MVC encargado de localizar e instanciar el controlador apropiado para procesar cada petición entrante. Por defecto, este trabajo lo realiza la clase DefaultControllerFactory, pero como en otras ocasiones, la flexibilidad del framework MVC permite sustituir muy fácilmente este componente por otro que realice la tarea que nos interesa.

Dentro de la factoría de controladores, el método GetControllerType() es utilizado por el framework para localizar el tipo (la clase) de controlador en función del nombre obtenido desde los parámetros de ruta. De hecho, este mismo método es el que utilizamos hace más de un año para saltarnos a la torera la convención de nombrado de controladores del framework.

De hecho, si heredamos de la factoría de controladores por defecto, basta con sobrescribir este método GetControllerType() para conseguir el comportamiento que pretendemos. En primer lugar, ejecutamos la lógica por defecto; sólo en el caso de no encontrar un controlador utilizaremos por defecto el controlador AutoController que creamos en el post anterior, de forma que sea él el que se encargue de proporcionar la implementación automática de sus acciones:

public class AutoControladoresControllerFactory: DefaultControllerFactory
{
    protected override Type GetControllerType(RequestContext requestContext, 
                                              string controllerName)
    {
        return base.GetControllerType(requestContext, controllerName) 
               ?? typeof (AutoController);
    }
}

Ya lo último que necesitamos es indicar al framework la factoría de controladores que debe utilizar, la clase AutoControladoresControllerFactory, introduciendo el siguiente código en el archivo global.asax.cs:

protected void Application_Start()
{
    // Establecemos la nueva factoría de controladores...
    ControllerBuilder.Current.SetControllerFactory(
                                typeof(AutoControladoresControllerFactory)
                              );
 
    // Y el código habitual en el Application_Start()
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
}

Y con eso, hemos terminado. A partir de ese momento, todas las peticiones cuyo controlador sea imposible localizar según el procedimiento estándar, serán procesadas por el controlador automático AutoController, que buscará para cada acción una vista y la retornará mágicamente al usuario.

Así, una petición de tipo GET /Home/Services, que requeriría normalmente la existencia de una clase controlador HomeController y un método de acción Services(), podrá ser resuelta sin implementar ninguno de estos dos elementos, siempre que exista una vista definida en ~/Views/Home/Products.aspx.
Ya en el post anterior comenté algunas contraindicaciones que podía tener la utilización de esta técnica. ¡No dejes de revisarlo si piensas emplearla en producción!
imageA modo de demo, he colgado en Skydrive un ejemplo (para Visual Studio 2010) en el que se ha eliminado directamente el controlador Home, y han sido añadidas diversas vistas a las que se puede acceder utilizando estos automatismos.

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

Publicado en: Variable not found
martes, 15 de junio de 2010
ASP.NET MVC Aunque las convenciones propuestas por el framework ASP.NET MVC nos ayudan a estructurar nuestras aplicaciones y, la mayoría de veces, a ser más productivos, de vez en cuando también nos obligan a introducir código repetitivo para respetar el patrón propuesto.

Por ejemplo, en el caso de controladores con acciones que retornan la vista por defecto, solemos utilizar un código como el siguiente:

public class HomeController: Controller
{
    public ActionResult Index()
    {
        return View();
    }
 
    public ActionResult Company()
    {
        return View();
    }   
 
    public ActionResult Services()
    {
        return View();
    }
    
    public ActionResult Contact()
    {
        return View();
    }
    
}

No es que sea algo terrible, pero en controladores con muchas acciones de este tipo puede resultar un poco pesado… ¿no podríamos hacer algo para mejorar esto?

Controladores automáticos

Si observamos el código del controlador anterior, vemos que la lógica de ejecución es muy simple: cada acción implementada retorna al usuario la vista que le corresponde según convención. Obviamente se trata de un comportamiento que puede ser generalizado jugando con los mecanismos de extensión del framework.

Una vía muy rápida para hacerlo es sobrescribir el método HandleUnknownAction() de la clase Controller, descrito por aquí hace tiempo, que nos permite procesar las peticiones realizadas a acciones no existentes en el controlador.

Recordemos que cuando una petición llega a un controlador, se ejecuta el método cuyo nombre coincide con el de la acción invocada; en caso de no existir, el framework llamada a HandleUnknownAction(), permitiéndonos tomar el control de la situación. En nuestro caso, podríamos introducir en este método la lógica para retornar al usuario, de forma automática, una vista cuyo nombre coincide con el de la acción, siguiendo la convención de nombrado estándar.

Para ello, basta con crear una clase base llamada AutoController, e introducir el siguiente código para tener el problema resuelto:

public class AutoController : Controller
{
    protected override void HandleUnknownAction(string actionName)
    {
 
        // Intentamos localizar una vista con el nombre de la acción...
 
        ViewEngineResult viewResult = ViewEngines.Engines.FindView
                            (this.ControllerContext, actionName, null) ;
 
        if (viewResult.View != null)
        {
            View(actionName).ExecuteResult(ControllerContext);
            return;
        }
 
        // Si no hemos encontrado nada, seguimos el 
        // comportamiento por defecto…
 
        base.HandleUnknownAction(actionName);
    }

¿Sencillo, no? Lo único que hacemos en el código es utilizar la colección de ViewEngines para buscar una vista cuyo nombre coincida con el de la acción que se está intentando ejecutar, retornándola al usuario cuando sea posible localizarla.

Si no es posible localizar una vista para la acción invocada, se ejecutará el tratamiento por defecto para esta situación, que no es más que el lanzamiento de una excepción de tipo HttpException con el error 404 (not found).

¡Y eso es todo! A partir de este momento, todas las clases controlador que hereden de AutoController incluirán este comportamiento, por lo que será posible obviar la implementación de métodos cuya única misión sea retornar la vista por defecto según convención.

Por ejemplo, el controlador que escribíamos al comienzo de este post podría quedar de la siguiente forma, bastante más compacta:

public class HomeController: AutoController
{
}

Pero ojito, que no es oro todo lo que reluce…

Eso sí, antes de usar esta técnica tenemos que tener claro lo que significa realmente para no toparnos con sorpresas desagradables.

Cada petición recibida por el controlador que no se encuentre implementada de forma explícita será procesada con el automatismo introducido en HandleUnknowAction() sin pasar por ningún tipo de filtro, ni enviar información alguna en el ViewData.

Por ejemplo, sería posible acceder a vistas directamente, sólo con saber su nombre,  lo que en algunos escenarios puede resultar peligroso desde el punto de vista de la seguridad del sistema.

Obviamente, la técnica tampoco será válida en aquellas ocasiones en que la vista espere recibir algún tipo de información del controlador (como datos de vista, o la indicación de utilización de una página maestra concreta), o cuando la acción deba estar decorada con un filtro.

En estos casos, estas acciones concretas deberán seguir siendo implementadas explícitamente en el controlador, aunque el resto puedan seguir siendo procesadas de forma automática:

public class HomeController: AutoController
{
    // GET /Home/Customers
    // Sólo para usuarios registrados
    [Authorize]
    public ActionResult Customers()
    {
        return View();
    }
 
    // GET /Home/References
    public ActionResult References()
    {
        var services = new AppServices();
        return View(services.GetReferences());
    }
 
    // GET /Home/{CualquierOtraCosa}
    // Procesado por defecto, retorna la vista {CualquierOtraCosa}
}

En resumen, en este post hemos estudiado una técnica que nos permite crear controladores capaces de aportar un comportamiento por defecto para todas las peticiones realizadas hacia el mismo, ahorrándonos la escritura de acciones que simplemente retornan la vista por defecto.

Y aunque, como todo, en la práctica presenta limitaciones y peligros, su uso puede ser interesante en sitios web sin grandes requisitos de seguridad, como webs de información pública, cuyos controladores sean principalmente del tipo descrito.

Publicado en: Variable not found.