lunes, 27 de septiembre de 2010
La validación de peticiones es un mecanismo integrado en ASP.NET que evita la entrada al sistema de valores considerados “peligrosos” para su integridad, como tags y otros elementos utilizables para la inyección de scripts (XSS) o introducción de valores no controlados.
Dado que está implementado a nivel de plataforma ASP.NET, los valores recibidos en parámetros de entrada son vigilados con ASP.NET MVC, WebForms e incluso WebPages (la tecnología incluida en el nuevo WebMatrix).
Así, en cada petición son analizados los valores recibidos en el query string, cookies y campos de formulario, generándose una bonita pantalla de error cuando se identifica algo sospechoso:
Según el comportamiento por defecto, el framework realiza esta comprobación en el método
Aunque en la mayoría de ocasiones este mecanismo es muy útil, hay escenarios en los que se vuelve en nuestra contra. Por ejemplo, si en el formulario de datos hay campos destinados a la introducción de texto enriquecido, no nos permitirá la recepción de estos valores, pues normalmente incluirán tags HTML.
Veamos cómo podemos desactivar la comprobación de valores de entrada en distintos casos.
Para evitar la introducción de esta directiva en todas las páginas, podíamos hacerlo de forma global en el web.config, añadiendo el atributo
También en ASP.NET MVC era posible desactivar este comportamiento muy fácilmente añadiendo a la acción del control destinataria de los datos del formulario el atributo [
Concretamente, se ha modificado el momento en el ciclo de vida del tratamiento de las peticiones donde se realiza la validación de la información de entrada. De esta forma, ahora se aplicarán también a servicios web, handlers o módulos HTTP personalizados, que antes no eran comprobados.
Así, por defecto, se realizará la validación antes de que entren en escena las directivas de página en Webforms o los atributos del controlador, por lo que se producirá la excepción de validación aunque la hayamos desactivado con ellos.
La forma de evitar esto es indicar en el web.config que queremos trabajar en modo compatible con versiones anteriores, introduciendo en el elemento
Sin embargo, fijaos que al deshabilitar la validación lo hacemos sobre la petición completa; por ejemplo, si la desactivamos para una página donde existe un único campo en el que debe introducirse HTML estaríamos abriendo la posibilidad de que un usuario malintencionado introdujera código script en otros campos del formulario a los que posiblemente no prestemos tanta atención.
Curiosamente, analizando el código de ASP.NET he descubierto que existe un atajo que nos permite saltarnos la validación en un campo concreto del formulario, aunque la comprobación de peticiones se encuentre habilitada para la página, como es por defecto.
El hecho es que si el nombre del campo del formulario comienza por dos caracteres de subrayado, por ejemplo, “
Por tanto, si introducimos en un formulario cuadros de texto o textareas en los que queremos saltarnos esta validación, basta con nombrarlos siguiendo esta regla, como en el siguiente ejemplo, para ASP.NET MVC:
Asimismo, puede utilizarse con ASP.NET Webforms. En este caso, sólo debemos asegurarnos de que el nombre (el atributo name) asignado en cliente al control comience por el doble subrayado:
Publicado en: Variable not found.
Dado que está implementado a nivel de plataforma ASP.NET, los valores recibidos en parámetros de entrada son vigilados con ASP.NET MVC, WebForms e incluso WebPages (la tecnología incluida en el nuevo WebMatrix).
Así, en cada petición son analizados los valores recibidos en el query string, cookies y campos de formulario, generándose una bonita pantalla de error cuando se identifica algo sospechoso:
Según el comportamiento por defecto, el framework realiza esta comprobación en el método
IsDangerousString()
de una clase interna de System.Web
, llamada CrossSiteScriptingValidation
. Si estudiamos su implementación, podemos observar que se comprueban los siguientes aspectos:- si se detecta algún ampersand “
&
”, se asegura que el siguiente carácter no sea una almohadilla “#”. Esto impide la entrada de entidades como "|
;" - si se detecta algún símbolo “<”, se asegura que el siguiente carácter no sea alfabético, el signo de exclamación “!”, interrogación “?” o la barra de división “/”. Esto evitará la entrada de etiquetas HTML (como <script>), el cierre de las mismas o la introducción de directivas o comentarios, aunque dejará pasar algunas expresiones que pueden ser construcciones lógicas como “a<1”.
HttpRequestValidationException
, cuya representación en pantalla hemos visto anteriormente.Aunque en la mayoría de ocasiones este mecanismo es muy útil, hay escenarios en los que se vuelve en nuestra contra. Por ejemplo, si en el formulario de datos hay campos destinados a la introducción de texto enriquecido, no nos permitirá la recepción de estos valores, pues normalmente incluirán tags HTML.
Veamos cómo podemos desactivar la comprobación de valores de entrada en distintos casos.
Desactivación en versiones anteriores a ASP.NET 4
Antes de la llegada de ASP.NET 4, en Webforms era posible desactivar la validación de peticiones simplemente añadiendo la directivaValidateRequest=false
en las páginas:Para evitar la introducción de esta directiva en todas las páginas, podíamos hacerlo de forma global en el web.config, añadiendo el atributo
validateRequest="false"
en la sección <pages>
del system.web:También en ASP.NET MVC era posible desactivar este comportamiento muy fácilmente añadiendo a la acción del control destinataria de los datos del formulario el atributo [
ValidateInput]
enviándole false
como argumento:Desactivación en ASP.NET 4
ASP.NET 4 ha introducido una serie de breaking changes que pueden hacer que aplicaciones web que antes funcionaban correctamente dejen de hacerlo. Uno de estos cambios es la forma en que se validan las peticiones.Concretamente, se ha modificado el momento en el ciclo de vida del tratamiento de las peticiones donde se realiza la validación de la información de entrada. De esta forma, ahora se aplicarán también a servicios web, handlers o módulos HTTP personalizados, que antes no eran comprobados.
Así, por defecto, se realizará la validación antes de que entren en escena las directivas de página en Webforms o los atributos del controlador, por lo que se producirá la excepción de validación aunque la hayamos desactivado con ellos.
La forma de evitar esto es indicar en el web.config que queremos trabajar en modo compatible con versiones anteriores, introduciendo en el elemento
<httpRuntime>
el atributo requestValidationMode="2.0"
:¿Y la desactivación para un único campo?
En apartados anteriores hemos visto cómo desactivar las comprobaciones de la petición sobre páginas (en el caso de Webforms) y acciones (en ASP.NET MVC). El problema de la aparición del error “se detectó un posible valor Request.Form peligroso” lo solucionaríamos muy rápidamente con las técnicas descritas.Sin embargo, fijaos que al deshabilitar la validación lo hacemos sobre la petición completa; por ejemplo, si la desactivamos para una página donde existe un único campo en el que debe introducirse HTML estaríamos abriendo la posibilidad de que un usuario malintencionado introdujera código script en otros campos del formulario a los que posiblemente no prestemos tanta atención.
Curiosamente, analizando el código de ASP.NET he descubierto que existe un atajo que nos permite saltarnos la validación en un campo concreto del formulario, aunque la comprobación de peticiones se encuentre habilitada para la página, como es por defecto.
El hecho es que si el nombre del campo del formulario comienza por dos caracteres de subrayado, por ejemplo, “
__Texto
”, no se efectúan sobre él las comprobaciones de validación, probablemente para evitar que se realice sobre los campos especiales como “__VIEWSTATE
” o “__EVENTVALIDATION
”. Por tanto, si introducimos en un formulario cuadros de texto o textareas en los que queremos saltarnos esta validación, basta con nombrarlos siguiendo esta regla, como en el siguiente ejemplo, para ASP.NET MVC:
Asimismo, puede utilizarse con ASP.NET Webforms. En este caso, sólo debemos asegurarnos de que el nombre (el atributo name) asignado en cliente al control comience por el doble subrayado:
Publicado en: Variable not found.
Publicado por José M. Aguilar a las 9:30 p. m.
Hay
9 comentarios, ¡participa tú también!
Etiquetas: asp.net, aspnetmvc, desarrollo, trucos
domingo, 26 de septiembre de 2010
Estos son los enlaces publicados en Variable not found en Facebook y Twitter desde el domingo, 19 de septiembre de 2010 hasta el domingo, 26 de septiembre de 2010.
Espero que te resulten interesantes. :-)
Publicado en: Variable not found
Espero que te resulten interesantes. :-)
- Cookies grabadas a fuego: Evercookie.
Fecha: 26/09/2010 - EF4 Code First, MVC2 y Unity para atarlo todo un poco... , por Eduard Tomás.
Fecha: 24/09/2010 - La mejor forma de aprender a programar, y OrderedDictionary vs Dictionary, por José Manuel Alarcón.
Fecha: 24/09/2010 - Proveedor de SiteMaps para ASP.NET MVC. Fecha: 23/09/2010
- Cómo eliminar las cadenas mágicas en consultas con DataServiceQuery
Fecha: 23/09/2010 - Face Detection API: detección de caras en fotos de forma sencilla (vía CampusMVP)
Fecha: 22/09/2010 - El interfaz IValidatableObject de ASP.NET MVC 3.
Fecha: 22/09/2010 - Filtros globales y condicionales en MVC 3.
Fecha: 22/09/2010 - Brutal! Explicación muy buena en vídeo sobre la vulnerabilidad de ASP.NET (vía CampusMVP) Fecha: 21/09/2010
- Elijah Manor: "HowTo: Facebook Connect & ASP.NET MVC"
Fecha: 20/09/2010 - ¡Imprescindible! Buenas prácticas para ASP.NET MVC , creado por Microsoft.
Fecha: 20/09/2010
Publicado en: Variable not found
martes, 21 de septiembre de 2010
Este es un error que me ha hecho perder un buen rato mientras trabajaba con objetos POCO en Entity Framework 4, y espero que pueda ayudar a alguien que se encuentre con el mismo problema.
El tema comienza con el lanzamiento de una excepción
Hay ocasiones en las que la excepción lanzada al crear el
Sin embargo, este
Tras un buen rato de infructuosa investigación, acudí a Google, ese que todo lo sabe, y encontré la primera recomendación si esto te ocurre: revisa minuciosamente la definición de tus entidades POCO. Éstas deben coincidir exactamente con la definición del modelo conceptual, tanto en sus propiedades escalares, de tipos complejos y las propiedades de navegación.
Y el "minucionamente" no sobra. En mi caso había definido de forma incorrecta una propiedad de navegación, y por más que repasaba no veía ningún error; obviamente, desconfié de la recomendación que había encontrado, y me puse de nuevo a buscar la solución por mi cuenta: seguí escudriñando el EDM, regenerándolo, eliminando entidades que no usaba, modificándolas... para nada, claro.
Al final, la moraleja de esta historia es la siguiente: aunque pienses que tus entidades son correctas, vuelve a revisarlas, que fijo que algo te estás dejando por detrás.
Espero que, a diferencia de uno que yo me sé ;-), esta tontería no os haga perder mucho tiempo.
Publicado en: Variable not found.
El tema comienza con el lanzamiento de una excepción
InvalidOperationException
justo en el momento de crear un ObjectSet<T>
, como en el siguiente código:class MiContexto : ObjectContext
{
...
private ObjectSet<Cliente> clientes;
public ObjectSet<Cliente> Clientes
{
get
{
return clientes ??
(clientes = CreateObjectSet<Cliente>());
}
}
}
Hay ocasiones en las que la excepción lanzada al crear el
ObjectSet
es de tipo MetadataException
, y contiene información suficiente para detectar el error, por lo que es sencillo dar con la solución.Sin embargo, este
InvalidOperationException
y el texto que la acompaña (no se encontró la información de metadatos y asignaciones para EntityType 'Namespace.Entidad') son bastante confusos. De hecho, supongo que al ver esta descripción lo normal es que ponerse como loco a destripar el EDM para ver a dónde se ha ido la información de metadatos y asignaciones, a cambiar los namespaces… y por supuesto, todo en vano.Tras un buen rato de infructuosa investigación, acudí a Google, ese que todo lo sabe, y encontré la primera recomendación si esto te ocurre: revisa minuciosamente la definición de tus entidades POCO. Éstas deben coincidir exactamente con la definición del modelo conceptual, tanto en sus propiedades escalares, de tipos complejos y las propiedades de navegación.
Y el "minucionamente" no sobra. En mi caso había definido de forma incorrecta una propiedad de navegación, y por más que repasaba no veía ningún error; obviamente, desconfié de la recomendación que había encontrado, y me puse de nuevo a buscar la solución por mi cuenta: seguí escudriñando el EDM, regenerándolo, eliminando entidades que no usaba, modificándolas... para nada, claro.
Al final, la moraleja de esta historia es la siguiente: aunque pienses que tus entidades son correctas, vuelve a revisarlas, que fijo que algo te estás dejando por detrás.
Espero que, a diferencia de uno que yo me sé ;-), esta tontería no os haga perder mucho tiempo.
Publicado en: Variable not found.
domingo, 19 de septiembre de 2010
Estos son los enlaces publicados en Variable not found en Facebook desde el domingo, 12 de septiembre de 2010 hasta el domingo, 19 de septiembre de 2010.
Espero que te resulten interesantes. Y sobre todo, ojo al primero de ellos :-)
Publicado en: Variable not found
Espero que te resulten interesantes. Y sobre todo, ojo al primero de ellos :-)
- ScottGu: Important ASP.NET Security Vulnerability
Fecha: 18/09/2010 - Security Response: Security Advisory 2416728 has been released to address a vulnerability in ASP.NET
Fecha: 18/09/2010 - ¡Muy bueno! José Manuel Alarcón: Archivos temporales de compilación de ASP.NET
Fecha: 18/09/2010 - Interesante: jQuery UI en la CDN de Microsoft. /via Phil Haack
Fecha: 17/09/2010 - The Ultimate Guide for New IE9 users Who Fear Change " (Vía campusMVP)
Fecha: 17/09/2010 - Curiosidad: ¿is es as, o as es is? Eric Lippert nos lo cuenta.
Fecha: 17/09/2010 - .NET String Formatting Cheat Sheet" por Dylan Beattie
Fecha: 16/09/2010 - Determinar el tamaño del ViewState usando distintas técnicas, por Scott Mitchell
Fecha: 16/09/2010 - Abrir enlaces externos en ventanas independientes con jQuery, por Scott Mitchell
Fecha: 16/09/2010 - Post de gpeipman: implementación de PagedResult
, para retornar datos paginados desde repositorios
Fecha: 16/09/2010 - Post de marc_Rubino: ASP. NET MVC como Servicio REST
Fecha: 16/09/2010 - Luis Ruiz Pavón: Autenticación con DNIe en aplicaciones ASP.NET
Fecha: 15/09/2010 - jQuery Barcode Generator Plugin jQuery
Fecha: 14/09/2010 - Cambios en la validación de peticiones introducidos por ASP.NET 4.
Fecha: 14/09/2010 - Fórmulas para la implementación de Ajax en ASP.NET MVC.
Fecha: 14/09/2010 - Particularidades del uso de app_offline.htm en aplicaciones ASP.NET MVC.
Fecha: 14/09/2010 - Scott Guthrie: I just blogged about "Automating Deployment with Microsoft Web Deploy" and cover how to setup & use it:
Fecha: 13/09/2010 - Todas las herramientas de Sysinternals en una única descarga: "Sysinternals suite".
Fecha: 13/09/2010 - Gonzalo Pérez C. muestra un ejemplo práctico de uso de Google Maps, jQuery y ASP.NET.
Fecha: 13/09/2010 - Unai Zorrila continúa su serie de trucos para mejorar el rendimiento en #EF4.
Fecha: 13/09/2010 - Hoy es el día del programador, ¡Felicidades! X-D
Fecha: 13/09/2010
Publicado en: Variable not found
miércoles, 15 de septiembre de 2010
ASP.NET MVC, por su natural integración con Ajax, es un candidato perfecto para implementar software al “estilo 2.0”, consiguiendo efectos sorprendentes y nuevas formas de interacción con el usuario desde la web similares a las que nos ofrecen aplicaciones como GMail o Blogger.
En este post vamos a ver cómo implementar un formulario con auto-salvado, es decir, capaz de ir enviado su contenido al servidor periódicamente con objeto de evitar la pérdida de toda la información si el usuario sale de forma involuntaria de la aplicación o se pierde la conexión.
Y todo ello de forma no intrusiva, sin afectar a las funcionalidades habituales en casos en que el usuario haya deshabilitado los scripts en el navegador… ¿qué más podemos pedir? ;-)
La siguiente captura muestra el interfaz principal del mantenimiento; seguro que apreciáis la originalidad del diseño ;-)
Al editar o crear un post accederemos a la pantalla de edición, en la que pretendemos crear la funcionalidad de autoguardado, que se puede ver en ejecución en la siguiente captura:
Mientras el usuario esté introduciendo los datos solicitados por el formulario, el sistema de auto-guardado irá activándose cada X segundos, enviando al servidor los datos presentes en los campos del formulario en ese momento. Eso sí, el usuario en ningún momento será interrumpido, puesto que este envío se realizará en segundo plano, mediante una discreta llamada Ajax.
Así, si se produce algún problema como el cierre involuntario del navegador, la salida de la aplicación, o simplemente al usuario se le corta el suministro eléctrico, se podrá recuperar la información enviada en el último salvado automático.
El diagrama del modelo conceptual de Entity Framework es el que se muestra adjunto. Una única entidad,
Hemos creado también la correspondiente clase de metadatos con la que aportaremos información necesaria para la validación de la información:
La clase de servicio que implementa las funcionalidades de alto nivel del modelo se denomina
El método
Por simplificar el ejemplo, no se han tenido en cuenta los posibles errores de concurrencia.
El último método de acción,
Como se puede observar, tras comprobar la validez de los datos recibidos, invocamos al método
El método retorna un objeto anónimo serializado en JSON, que presenta como máximo dos propiedades:
La llamada a
El formulario de edición es totalmente normal, como podría una vista tipada de edición generada por Visual Studio, lo que asegurará la compatibilidad con clientes sin scripts. Será idéntico para el interfaz de edición y de creación:
Fijaos que estamos dejando un campo oculto para almacenar el
El siguiente código script muestra la forma en que activamos el guardado automático sobre el formulario de edición:
Al terminar la carga de la página invocamos a la función
Observad que hemos introducido una condición previa: si el usuario no ha introducido nada aún en el campo
Sería muy sencillo introducir aquí otras condiciones para impedir peticiones Ajax en momentos en los que no tenga sentido. Por ejemplo, podríamos crear un flag global que indicara si el valor de los campos han cambiado (utilizando los eventos de cambio o pulsación de teclas de los controles), y sólo permitir la llamada cuando dicho indicador fuera cierto.
La función
Lo único que hacemos con ese valor es almacenarlo en el campo oculto que habíamos dispuesto para este propósito. Ya la siguiente vez que guardemos el
La función
El cuerpo de la función
Si en el formulario existe un elemento visual llamado #autosaving, la función anterior se encargará de mostrarlo y ocultarlo cuando corresponda, a modo de indicador de progreso. En nuestro caso, las vistas incluyen un marcado como el siguiente:
Obviamente la implementación descrita no es perfecta; podría ser mejorada en mucho añadiendo más automatismos, como la detección automática de cambios en el formulario, o el control de errores, pero no era ese el objetivo que pretendía, así que os lo dejo de deberes ;-)
Podéis descargar el proyecto de demostración, para VS2010 y SQL Express 2008.
Publicado en: Variable not found.
En este post vamos a ver cómo implementar un formulario con auto-salvado, es decir, capaz de ir enviado su contenido al servidor periódicamente con objeto de evitar la pérdida de toda la información si el usuario sale de forma involuntaria de la aplicación o se pierde la conexión.
Y todo ello de forma no intrusiva, sin afectar a las funcionalidades habituales en casos en que el usuario haya deshabilitado los scripts en el navegador… ¿qué más podemos pedir? ;-)
1. Objetivo
Vamos a empezar por el final, para que quede claro a dónde pretendemos llegar. Implementaremos un mantenimiento para la gestión simple de posts de un motor de blogs, para lo que nos basaremos en las vistas y controladores generados automáticamente por Visual Studio.La siguiente captura muestra el interfaz principal del mantenimiento; seguro que apreciáis la originalidad del diseño ;-)
Al editar o crear un post accederemos a la pantalla de edición, en la que pretendemos crear la funcionalidad de autoguardado, que se puede ver en ejecución en la siguiente captura:
Mientras el usuario esté introduciendo los datos solicitados por el formulario, el sistema de auto-guardado irá activándose cada X segundos, enviando al servidor los datos presentes en los campos del formulario en ese momento. Eso sí, el usuario en ningún momento será interrumpido, puesto que este envío se realizará en segundo plano, mediante una discreta llamada Ajax.
Así, si se produce algún problema como el cierre involuntario del navegador, la salida de la aplicación, o simplemente al usuario se le corta el suministro eléctrico, se podrá recuperar la información enviada en el último salvado automático.
2. La solución, a vista de pájaro
La implementación del autoguardado atenderá estructuralmente al patrón MVC, por lo que:- implementaremos en el Modelo la lógica de recuperación y actualización de datos. Utilizaremos una clase de servicio en la que expondremos los métodos de manipulación de información que vamos a permitir; para el acceso a datos utilizaremos Entity Framework.
- el controlador dispondrá de una acción específica para salvar los datos enviados desde la vista.
- la vista incluirá los scripts que realizan la invocación periódica de la acción del controlador vía Ajax, suministrándole la información disponible en el formulario.
3. El Modelo
Nuestro modelo es bastante simple, puesto que estamos centrándonos exclusivamente en las funcionalidades que necesitamos para implementar el ejemplo de autoguardado.El diagrama del modelo conceptual de Entity Framework es el que se muestra adjunto. Una única entidad,
Post
, con escasas propiedades para no distraernos mucho de nuestro objetivo final.Hemos creado también la correspondiente clase de metadatos con la que aportaremos información necesaria para la validación de la información:
[MetadataType(typeof(PostMetadata))]
public partial class Post
{
}
public class PostMetadata
{
[DisplayName("Título")]
[Required]
public string Titulo { get; set; }
[DisplayName("Texto")]
public string Texto { get; set; }
}
La clase de servicio que implementa las funcionalidades de alto nivel del modelo se denomina
BlogServices
, con la siguiente estructura:public class BlogServices: IDisposable
{
public IEnumerable<Post> GetPosts() { }
public Post GetPostById(int id) { }
public void Save(Post post) { }
public bool DeletePost(int id) { }
public void Dispose() {}
}
El método
Save()
es el único que merece la pena destacar. Su objetivo es comprobar si existe el post, para lo cual nos basamos en la propiedad IdPost
: si vale cero, asumiremos que el post es de nueva creación, por lo que lo insertaremos en la base de datos; en caso contrario, adjuntaremos la entidad al contexto y la marcamos como modificada para que sea actualizada al confirmar los cambios:public void Save(Post post)
{
post.Fecha = DateTime.Now;
if (post.PostId == 0)
{
post.Fecha = DateTime.Now;
_data.AddToPosts(post);
}
else
{
_data.Posts.Attach(post);
_data.ObjectStateManager.ChangeObjectState(post, EntityState.Modified);
}
_data.SaveChanges();
}
Por simplificar el ejemplo, no se han tenido en cuenta los posibles errores de concurrencia.
4. El Controlador
El controlador que podréis encontrar en el código fuente del proyecto incluye las acciones habituales para crear, actualizar, eliminar y obtener la relación de posts almacenados en el sistema.public class PostsController : Controller
{
private BlogServices services = new BlogServices();
[HttpGet] public ActionResult Index() { ... }
[HttpGet] public ActionResult Edit(int id) { ... }
[HttpPost] public ActionResult Edit(Post post) { ... }
[HttpGet] public ActionResult Create() { ... }
[HttpPost] public ActionResult Create(Post post) { ... }
[HttpGet] public ActionResult Delete(int id) { ... }
[HttpPost] public JsonResult SavePostAjax(Post post) { ... }
}
El último método de acción,
SavePostAjax()
, es el que recibirá los datos desde la vista, y su implementación es la mostrada a continuación:[HttpPost]
public JsonResult SavePostAjax(Post post)
{
if (this.ModelState.IsValid)
{
services.Save(post);
Thread.Sleep(2000); // Un retardo
return Json(new
{
grabado = true,
Id = post.PostId
}
);
}
else
{
return Json(new
{
grabado = false
});
}
}
Como se puede observar, tras comprobar la validez de los datos recibidos, invocamos al método
Save()
del objeto services
, una instancia de la clase BlogService
descrita en el Modelo y cuya implementación hemos visto anteriormente, que grabará los datos en el almacén. El método retorna un objeto anónimo serializado en JSON, que presenta como máximo dos propiedades:
grabado
, que indicará si el Post
ha sido almacenado con éxito, e Id
, que retornará el identificador asignado al Post
cuando haya sido salvado por primera vez.La llamada a
Thread.Sleep()
hace que la ejecución quede en pausa un par de segundos, para que cuando estamos en local al menos nos de tiempo a ver los mensajes en pantalla. Obviamente, este código no debería aparecer en producción.5. La Vista
En la vista es donde se encuentra la mayor parte de la magia del autosalvado. Obviamente, estas funcionalidades estarán basadas en scripts, por lo que nos apoyaremos bastante en la inigualable jQuery.El formulario de edición es totalmente normal, como podría una vista tipada de edición generada por Visual Studio, lo que asegurará la compatibilidad con clientes sin scripts. Será idéntico para el interfaz de edición y de creación:
Fijaos que estamos dejando un campo oculto para almacenar el
PostId
del Post
que estamos editando. Cuando estemos creando un post, éste campo almacenará el valor cero, pero una vez el proceso automático envíe por primera vez sus datos, introduciremos en su interior el ID asignado desde la base de datos; así, el siguiente autosalvado, ya no realizará una inserción sobre la base de datos, sino una actualización :-)El siguiente código script muestra la forma en que activamos el guardado automático sobre el formulario de edición:
<script type="text/javascript">
$(function () {
backgroundSaver('<%= Url.Action("SavePostAjax")%>', 15000, getData, setId);
});
function getData() {
if ($.trim($("#Texto").val()) == "")
return null;
else
return {
PostId: $("#PostId").val(),
Titulo: $("#Titulo").val() || "No definido",
Texto: $("#Texto").val()
};
}
function setId(id) {
$("#PostId").val(id);
}
</script>
Al terminar la carga de la página invocamos a la función
backgroundSaver()
, cuyo código veremos más adelante, pasándole los siguientes parámetros:- en primer lugar, el nombre de la acción que recibirá los datos, obtenida con el helper
Url.Action()
, - a continuación el número de milisegundos entre guardado y guardado de datos; en el ejemplo anterior, los datos serán enviados cada 15 segundos,
- seguidamente, la función que utilizará
backgroundSaver()
para obtener los datos a enviar al servidor,getData()
, - y por último, la función mediante la cual
backgroundSaver()
nos informará del identificador asignado al post cuando éste haya sido guardado por primera vez,setId()
.
getData()
debe retornar un objeto anónimo con los datos que queremos enviar al servidor. En nuestro caso, dado que se trata de una instancia de Post, definimos en el objeto anónimo las propiedades PostId
, Titulo
y Texto
, cuyos valores tomamos de los campos del formulario. En el título, además, introducimos un valor por defecto para cuando éste no haya sido definido aún.Observad que hemos introducido una condición previa: si el usuario no ha introducido nada aún en el campo
Texto
, retornamos un nulo, lo que provocará que backgroundSaver()
no efectúe la llamada Ajax al servidor. Sería muy sencillo introducir aquí otras condiciones para impedir peticiones Ajax en momentos en los que no tenga sentido. Por ejemplo, podríamos crear un flag global que indicara si el valor de los campos han cambiado (utilizando los eventos de cambio o pulsación de teclas de los controles), y sólo permitir la llamada cuando dicho indicador fuera cierto.
La función
setId()
será invocada por el proceso de autoguardado cuando haya conseguido almacenar los datos, enviándonos como argumento el identificador asignado al Post
por la base de datos. Lo único que hacemos con ese valor es almacenarlo en el campo oculto que habíamos dispuesto para este propósito. Ya la siguiente vez que guardemos el
Post
, bien sea de forma manual pulsando sobre el botón "Almacenar" del formulario, o bien de forma automática mediante el autosalvado, la información será actualizada en la base de datos y no se insertará un nuevo registro.La función
backgroundSaver()
también es bastante simple. Internamente define dos métodos:prepareNext()
, que programa el siguiente autosalvado,saveForm()
, que es la que lanza la petición Ajax al servidor.
var backgroundSaver = function (action, timer, getData, setId) {
var saveForm = function () {
var data = getData();
if (data == null) { // Cancelamos el envío
prepareNext();
return;
}
$("#autosaving").show();
$.ajax({
url: action,
type: 'post',
cache: false,
data: data,
success: function (retorno) {
$("#autosaving").hide();
if (retorno.grabado) {
if (typeof (setId) == 'function') {
setId(retorno.Id);
}
}
prepareNext();
},
error: function (d) {
$("#autosaving").hide();
prepareNext();
}
});
}
var prepareNext = function () {
setTimeout(saveForm, timer);
};
prepareNext();
}
El cuerpo de la función
saveForm()
contiene la llamada Ajax al servidor. Tras comprobar que la llamada a la función callback getData()
no ha retornado un nulo, se realiza la llamada utilizando el método ajax()
de jQuery. Si la petición se completa con éxito, se invoca a setId()
para notificar al código cliente del ID asignado al Post
.Si en el formulario existe un elemento visual llamado #autosaving, la función anterior se encargará de mostrarlo y ocultarlo cuando corresponda, a modo de indicador de progreso. En nuestro caso, las vistas incluyen un marcado como el siguiente:
6. ¡Y eso es todo!
A pesar de la extensión del este post, implementar esta característica es realmente sencillo, y aporta una espectacularidad impresionante a nuestros formularios, muy en la línea de las modernas aplicaciones 2.0, y por supuesto, grandes ventajas para los usuarios.Obviamente la implementación descrita no es perfecta; podría ser mejorada en mucho añadiendo más automatismos, como la detección automática de cambios en el formulario, o el control de errores, pero no era ese el objetivo que pretendía, así que os lo dejo de deberes ;-)
Podéis descargar el proyecto de demostración, para VS2010 y SQL Express 2008.
Publicado en: Variable not found.