Formularios web AJAX en ASP.NET Web Forms - UpdatePanel
En el anterior artículo de este Blog Formularios web AJAX con jQuery en ASP.NET Core MVC, vimos cómo implementar un formulario web Http POST en ASP.NET Core MVC, que utilizaba jQuery AJAX para enviar datos al servidor, y a su vez, recibía de manera asíncrona una respuesta HTML que le permitía actualizar la información visual en la misma página.
En este primer artículo del 2020, me gustaría recordar lo fácil e intuitivo que resultaba implementar esta misma funcionalidad AJAX en ASP.NET Web Forms, utilizando solamente los controles <asp:UpdatePanel />
y <asp:ScriptManager />
.
Creando el proyecto
Para este ejemplo, utilizaremos Visual Studio 2019 para crear un nuevo proyecto Web ASP.NET Web Forms, con plataforma de destino .NET Framework 4.7.1 .
La aplicación de ejemplo consistirá en un sencillo sistema de Blog, en el cual, a través de un formulario, introduciremos Posts que serán enviados al Servidor para ser almacenados en una Base de Datos. Además, cada vez que creemos un nuevo Post, se actualizará una lista de Posts en pantalla con el nuevo contenido de la Base de Datos.
Nota: Todo este proceso lo haremos sin enviar en ningún momento la pagina completa al Servidor, o sea, mediante AJAX enviaremos solo los datos y recibiremos solo la información necesaria para actualizar parcialmente la página.
Realizando el ejemplo de manera síncrona - Postback
Antes de implementar la funcionalidad AJAX en nuestra aplicación de ejemplo, realizaremos el desarrollo de la misma utilizando el mecanismo estándar (síncrono) que provee ASP.NET Web Forms por defecto, o sea, los Postbacks.
Lo haremos así, para posteriormente ver lo fácil que es reutilizar el código ya existente y transformarlo en asíncrono (AJAX), sin escribir ni una sola línea de código en JavaScript.
El Modelo de datos
En primer lugar crearemos la clase Post.cs
que será el Modelo de datos principal de la aplicación.
public class Post
{
public Post()
{
this.Id = Guid.NewGuid();
this.Fecha = DateTime.Now;
}
public Guid Id { get; set; }
public DateTime Fecha { get; set; }
public string Nombre { get; set; }
public string Email { get; set; }
public string Titulo { get; set; }
public string Comentario { get; set; }
}
La página ASPX
Seguidamente, crearemos un nuevo Formulario Web Forms (página ASPX) con el nombre Blog.aspx
. En esta página implementaremos toda la funcionalidad del ejemplo.
La página constará de un formulario web donde introduciremos los datos (Post) que serán enviados al Servidor. También tendremos una lista paginada que mostrará los registros (Posts) que ya están almacenados en la Base de Datos.
El código sería el siguiente:
<%--FORMULARIO PARA ENVÍO DE DATOS--%>
<h3>BlogPost</h3>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<asp:TextBox runat="server" ID="idNombre" CssClass="form-control input-sm"
placeholder="Nombre"></asp:TextBox>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<asp:TextBox runat="server" ID="idEmail" CssClass="form-control input-sm"
placeholder="Email" TextMode="Email" ></asp:TextBox>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<asp:TextBox runat="server" ID="idTitulo" CssClass="form-control input-sm"
placeholder="Titulo"></asp:TextBox>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<asp:TextBox runat="server" ID="idComentario" CssClass="form-control input-sm"
placeholder="Comentario" TextMode="MultiLine" Rows="5"></asp:TextBox>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group float-right">
<asp:Button runat="server" ID="idSubmitBtn" OnClick="idSubmitBtn_Click"
CssClass="btn btn-primary" Text="Enviar Datos"/>
</div>
</div>
</div>
<%--LISTA DE POSTS ENVIADOS AL SERVIDOR --%>
<h3>PostList</h3>
<%--Control de servidor ListView--%>
<asp:ListView runat="server" ID="idListView"
ItemPlaceholderID="itemPlaceHolder"
OnItemDataBound="idListView_ItemDataBound">
<%--Plantilla de diseño de la Lista Html--%>
<LayoutTemplate>
<%--Control de servidor PlaceHolder, elemento que
contendrá la plantilla <ItemTemplate />--%>
<asp:PlaceHolder runat="server" id="itemPlaceHolder">
</asp:PlaceHolder>
</LayoutTemplate>
<%--Plantilla de los elementos dinámicos de la Lista Html--%>
<ItemTemplate>
<div class="row">
<div class="col-md-12">
<div>
<strong runat="server" id="idFecha" ></strong>
<span runat="server" id="idNombre"></span>
<span runat="server" id="idEmail"></span>
</div>
<strong runat="server" id="idTitulo"></strong>
<p runat="server" id="idComentario"></p>
<hr />
</div>
</div>
</ItemTemplate>
</asp:ListView>
<%--Control de servidor DataPager. Paginador de la Lista Html--%>
<asp:DataPager runat="server" ID="idDataPager"
PagedControlID="idListView" PageSize="5">
<Fields>
<asp:NextPreviousPagerField ButtonType="Button"
ShowFirstPageButton="True"
ShowNextPageButton="False"
PreviousPageText="◄"
FirstPageText="◄◄"
ButtonCssClass="btn btn-sm btn-default" />
<asp:NumericPagerField ButtonType="Button"
NumericButtonCssClass="btn btn-sm btn-default"
CurrentPageLabelCssClass=""
NextPreviousButtonCssClass="btn btn-sm btn-default"/>
<asp:NextPreviousPagerField ButtonType="Button"
ShowLastPageButton="True"
ShowPreviousPageButton="False"
NextPageText="►"
LastPageText="►►"
ButtonCssClass="btn btn-sm btn-default"/>
</Fields>
</asp:DataPager>
El Codebehind
A continuación, crearemos el código de Servidor que gestionará la página Blog.aspx
en su archivo correspondiente Blog.aspx.cs
(Codebehind):
public partial class Blog : System.Web.UI.Page
{
private ApplicationDbContext _applicationDbContext;
protected void Page_Load(object sender, EventArgs e)
{
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
// SIMULAMOS UN DELAY PARA EL UpdateProgress
System.Threading.Thread.Sleep(2000);
// CARGA LOS DATOS EN EL ListView.
using (_applicationDbContext = new ApplicationDbContext())
{
idListView.DataSource = _applicationDbContext.Posts.OrderByDescending(x => x.Fecha).ToList();
idListView.DataBind();
}
}
protected void idSubmitBtn_Click(object sender, EventArgs e)
{
// SIMULAMOS UN DELAY PARA EL UpdateProgress
System.Threading.Thread.Sleep(2000);
// GUARDA EL OBJETO DEL FORM EN LA BD.
using (_applicationDbContext = new ApplicationDbContext())
{
Post post = new Post()
{
Nombre = idNombre.Text,
Email = idEmail.Text,
Titulo = idTitulo.Text,
Comentario = idComentario.Text
};
_applicationDbContext.Posts.Add(post);
_applicationDbContext.SaveChanges();
}
// ESTABLECE EL PAGINADOR EN LA PRIMERA PÁGINA,
// PARA PODER VER EL NUEVO REGISTRO INSERTADO.
idDataPager.SetPageProperties(0, idDataPager.PageSize, true);
// BORRA LOS DATOS DEL FORMULARIO
idComentario.Text = string.Empty;
idNombre.Text = string.Empty;
idEmail.Text = string.Empty;
idTitulo.Text = string.Empty;
}
protected void idListView_ItemDataBound(object sender, ListViewItemEventArgs e)
{
// ENLAZA LOS DATOS A CADA ELEMENTO DEL ListView.
if (e.Item.ItemType == ListViewItemType.DataItem)
{
Post post = e.Item.DataItem as Post;
((HtmlGenericControl)e.Item.FindControl("idFecha")).InnerText = post.Fecha.ToShortDateString();
((HtmlGenericControl)e.Item.FindControl("idNombre")).InnerText = post.Nombre;
((HtmlGenericControl)e.Item.FindControl("idEmail")).InnerText = post.Email;
((HtmlGenericControl)e.Item.FindControl("idTitulo")).InnerText = post.Titulo;
((HtmlGenericControl)e.Item.FindControl("idComentario")).InnerText = post.Comentario;
}
}
}
Nota: Como podemos ver en el código de Servidor, hemos hecho referencia a un objeto privado del tipo
ApplicationDbContext
(Contexto de datos de Entity Framework). Como no es el objetivo de este artículo explicar como enlazar Entity Framework a una base de datos, supondremos que ya hemos definido elDbContext
de la aplicación con el correspondienteDbSet<Post>
del modelo de datosPost.cs
.
En este punto, y si todo ha ido bien, ya podemos ejecutar la aplicación desde la página Post.aspx
y comenzar a crear registros (Posts) en la Base de Datos. Observaremos que cada vez que creamos un registro nuevo, la lista de Posts se actualiza en pantalla con todos los registros de la Base de Datos (incluido el nuevo elemento creado).
De momento, toda esta funcionalidad la realizamos enviando toda la página al Servidor (Postback), y recibiendo una nueva página actualizada con los resultados (de manera síncrona).
A continuación veremos cómo aplicar los cambios necesarios para transformar este proceso síncrono de envío y recepción de datos, en un proceso asíncrono (AJAX) donde solo enviaremos los datos del formulario al Servidor, y recibiremos una actualización parcial de la página con los nuevos resultados.
Aplicando la asincronía - AJAX
Lo interesante de transformar nuestro formulario Web síncrono en un formulario AJAX totalmente funcional, es que no necesitaremos modificar nada del código ya desarrollado, ni escribir ninguna línea de código JavaScript adicional.
Todo el proceso de transformación hacia AJAX, lo realizaremos mediante los controles Web de ASP.NET WebForms <asp:ScriptManager />
y <asp:UpdatePanel />
.
Una plantilla estándar de asincronía en ASP.NET WebForms - AJAX
El proceso a seguir para transformar un formulario Web estándar (Postback) en un formulario AJAX, lo podemos resumir con el siguiente ejemplo o plantilla de código:
<%--Control ScriptManager, Administra las bibliotecas de scripts--%>
<%--y los archivos de script AJAX de ASP.NET--%>
<asp:ScriptManager runat="server" ID="idScriptManager">
</asp:ScriptManager>
<%--Control UpdatePanel, habilita la funcionalidad AJAX asíncrona--%>
<asp:UpdatePanel runat="server" ID="idUpdatePanel">
<%--Contenido que se actualizará de manera asíncrona--%>
<ContentTemplate>
<%--AQUÍ TODO EL CONTENIDO QUE SE ACTUALIZARÁ VÍA AJAX--%>
</ContentTemplate>
<%--Elemento/s Html que desencadenan la llamada AJAX--%>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="idSubmitBtn" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
<%--Control UpdateProgress, AJAX Loader mientras se espera la respuesta asíncrona--%>
<asp:UpdateProgress runat="server" ID="idUpdateProgress"
AssociatedUpdatePanelID="idUpdatePanel" DynamicLayout="true">
<ProgressTemplate>
<%--AQUÍ EL TEXTO O IMAGEN AJAX LOADER--%>
</ProgressTemplate>
</asp:UpdateProgress>
Como vemos, todo se reduce a incluir dentro de la etiqueta <ContentTemplate> <ContentTemplate />
del <asp:UpdatePanel />
el código de la página ASPX que queremos que se gestione y actualice vía AJAX.
El resultado final
Para finalizar, solo quedaría adaptar el código de la página Blog.aspx
a la nueva estructura del control <asp:UpdatePanel />
. Por supuesto, el Codebehind Blog.aspx.cs
seguirá siendo el mismo, no hace falta modificarlo.
El código final sería el siguiente:
<%--Control ScriptManager, Administra las bibliotecas de scripts--%>
<%--y los archivos de script AJAX de ASP.NET--%>
<asp:ScriptManager runat="server" ID="sm">
</asp:ScriptManager>
<%--Control UpdatePanel, habilita la funcionalidad AJAX asíncrona--%>
<asp:UpdatePanel runat="server" ID="idUpdatePanel">
<%--Contenido que se actualizará de manera asíncrona--%>
<ContentTemplate>
<%--AQUÍ TODO EL CONTENIDO QUE SE ACTUALIZARÁ VÍA AJAX--%>
<%--FORMULARIO PARA ENVÍO DE DATOS--%>
<h3>BlogPost</h3>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<asp:TextBox runat="server" ID="idNombre" CssClass="form-control input-sm"
placeholder="Nombre"></asp:TextBox>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<asp:TextBox runat="server" ID="idEmail" CssClass="form-control input-sm"
placeholder="Email" TextMode="Email" ></asp:TextBox>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<asp:TextBox runat="server" ID="idTitulo" CssClass="form-control input-sm"
placeholder="Titulo"></asp:TextBox>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<asp:TextBox runat="server" ID="idComentario" CssClass="form-control input-sm"
placeholder="Comentario" TextMode="MultiLine" Rows="5"></asp:TextBox>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group float-right">
<asp:Button runat="server" ID="idSubmitBtn" OnClick="idSubmitBtn_Click"
CssClass="btn btn-primary" Text="Enviar Datos"/>
</div>
</div>
</div>
<%--LISTA DE POSTS ENVIADOS AL SERVIDOR --%>
<h3>PostList</h3>
<%--Control de servidor ListView--%>
<asp:ListView runat="server" ID="idListView"
ItemPlaceholderID="itemPlaceHolder"
OnItemDataBound="idListView_ItemDataBound">
<%--Plantilla de diseño de la Lista Html--%>
<LayoutTemplate>
<%--Control de servidor PlaceHolder, elemento que
contendrá la plantilla <ItemTemplate>--%>
<asp:PlaceHolder runat="server" id="itemPlaceHolder">
</asp:PlaceHolder>
</LayoutTemplate>
<%--Plantilla de los elementos dinámicos de la Lista Html--%>
<ItemTemplate>
<div class="row">
<div class="col-md-12">
<div>
<strong runat="server" id="idFecha" ></strong>
<span runat="server" id="idNombre"></span>
<span runat="server" id="idEmail"></span>
</div>
<strong runat="server" id="idTitulo"></strong>
<p runat="server" id="idComentario"></p>
<hr />
</div>
</div>
</ItemTemplate>
</asp:ListView>
<%--Control de servidor DataPager. Paginador de la Lista Html--%>
<asp:DataPager runat="server" ID="idDataPager"
PagedControlID="idListView" PageSize="5">
<Fields>
<asp:NextPreviousPagerField ButtonType="Button"
ShowFirstPageButton="True"
ShowNextPageButton="False"
PreviousPageText="◄"
FirstPageText="◄◄"
ButtonCssClass="btn btn-sm btn-default" />
<asp:NumericPagerField ButtonType="Button"
NumericButtonCssClass="btn btn-sm btn-default"
CurrentPageLabelCssClass=""
NextPreviousButtonCssClass="btn btn-sm btn-default"/>
<asp:NextPreviousPagerField ButtonType="Button"
ShowLastPageButton="True"
ShowPreviousPageButton="False"
NextPageText="►"
LastPageText="►►"
ButtonCssClass="btn btn-sm btn-default"/>
</Fields>
</asp:DataPager>
</ContentTemplate>
<%--Elemento/s Html que desencadenan la llamada asíncrona--%>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="idSubmitBtn" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
<%--Control UpdateProgress, AJAX Loader mientras se espera la respuesta asíncrona--%>
<asp:UpdateProgress runat="server" ID="idUpdateProgress"
AssociatedUpdatePanelID="idUpdatePanel" DynamicLayout="true">
<ProgressTemplate>
<div style="position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);">
<img id="idAjaxLoader" alt="Enviando ..." src="Images/loading.gif" />
</div>
</ProgressTemplate>
</asp:UpdateProgress>
Como vemos, el código y las configuraciones adicionales son bastante simples y fáciles de entender.
Nota: Para cualquier duda o pregunta utilizar la zona de comentarios del artículo.
Ahora solo quedaría volver a ejecutar la aplicación desde la página Post.aspx
, y comprobar que la funcionalidad sigue siendo la misma, pero esta vez utilizando AJAX.
Nota: Como podemos ver en el código, en el control
<asp:UpdateProgress />
hemos utilizado la imagenloading.gif
a modo de AJAX Loader. Está imagen no es más que un Gif animado que simula un proceso de espera. Por si lo necesitan para el ejemplo, está disponible para descargar al final del artículo.
Nuevo comentario
Comentarios
@Pablo:
Te recomiendo que leas detenidamente el artículo de este Blog Entity Framework 6 y Sql Server Compact CE en ASP.NET MVC 5 , donde podrás ver cómo implementar una base de datos ligera, compacta, totalmente funcional e integrada en el propio proyecto, en conjunción con el ORM Entity Framework.
Un saludo.
Hola! , muy buen articulo! Me podrias decir como montar la BD, como hacer la conexion?
Muchas gracias de antemano!
Hola mi nombre es Mauricio tengo que algunos errores bro, pero me gusto mucho tu ayuda, pero si me pudieras a mi correo te lo agradeciería mucho bro para corregir mis errores mauriciomenacousin@gmail.com