Manipulación de datos en AJAX mediante el control ModalPopupExtender (I)

Hace algunas semanas publiqué un artículo dedicado a la construcción de cuadros de diálogo en AJAX utilizando el extensor ModalPopupExtender, donde se describían los principales pasos a dar para diseñar un panel que se mostrara al usuario con la apariencia de un diálogo de selección de valores.

Dicho ejemplo, aunque correcto en su funcionamiento, adolecía de un aspecto muy importante: la capacidad de interactuar contra un origen de datos para, desde AJAX, poder realizar las operaciones habituales que se efectuan durante el acceso a una base de datos: consulta, inserción, modificación y borrado.

Con la voluntad de "desfacer tal entuerto", en el presente artículo -repartido en dos entregas- se expondrá una técnica que nos permitirá desarrollar dicha característica de la programación basada en AJAX, por lo que sin más dilación entremos en materia.

 

El ejemplo a desarrollar

Tomando como base un caso similar al planteado en el artículo mencionado al comienzo, supongamos que partimos de un WebForm en el que debemos rellenar varios datos relativos al envío de mercancías, siendo uno de ellos el correspondiente a la empresa de transporte a través de la cual se realizará el envío. La información concerniente a los diversos transportistas disponibles se encuentra ubicada en la tabla Shippers, de la base de datos Northwind, siendo necesario acceder a dicha tabla, pero desde AJAX, en forma de un cuadro de diálogo construido entorno a un ModalPopupExtender. Además de la funcionalidad obvia de selección de datos de un registro de la tabla Shippers, el cuadro de diálogo deberá ofrecer las operaciones típicas de edición sobre la tabla: alta, baja y modificación.

La pieza clave de toda esta maquinaria, que permitirá el acceso a los datos de la tabla Shippers desde AJAX, será un servicio WCF, cuya implementación describiremos en un próximo apartado.

 

Elementos de la interfaz de usuario

Una vez creado un nuevo proyecto de tipo ASP.NET Web Application, al que daremos el nombre ManipulacionDatosAjaxModalPopupExtender, nos situaremos en el diseñador del formulario Default.aspx, donde escribiremos el código de marcado necesario para dotar a nuestra página del aspecto que hemos comentado. De todos estos elementos destacaremos el control Panel pnlShippers, que actuará como cuadro de diálogo al combinarlo con el extensor ModalPopupExtender mpeShippers. No debemos olvidar la inclusión de un ScriptManager y un UpdatePanel, que darán a nuestra página el toque "a la AJAX". Veamos dicho código a continuación.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ManipulacionDatosAjaxModalPopupExtender._Default" %>

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Acceso a base de datos desde AJAX</title>
    <style type="text/css">
        .CajaDialogo
        {
            background-color: #99ffcc;
            border-width: 4px;
            border-style: outset;
            border-color: Yellow;
            padding: 0px;
            width: 375px;
            font-weight: bold;
            font-style: italic;
        }
        .CajaDialogo div
        {
            margin: 5px;
            text-align: center;
        }
        .CajaDialogo td
        {
            text-align: left;
        }
        .FondoAplicacion
        {
            background-color: Gray;
            filter: alpha(opacity=70);
            opacity: 0.7;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
            <div>
                <table border="2">
                    <tr>
                        <td colspan="3" align="center">
                            <asp:Label ID="Label1" runat="server" Text="Información de envío" Font-Bold="True"
                                Font-Underline="True" />
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <asp:Label ID="Label2" runat="server" Text="Destinatario:" />
                        </td>
                        <td colspan="2">
                            <asp:TextBox ID="txtDestinatario" runat="server" />
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <asp:Label ID="Label3" runat="server" Text="Domicilio:" />
                        </td>
                        <td colspan="2">
                            <asp:TextBox ID="txtDomicilio" runat="server" Width="300" />
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <asp:Label ID="Label4" runat="server" Text="Transportista:" />
                        </td>
                        <td>
                            <asp:TextBox ID="txtTransportista" runat="server" Width="175" />
                        </td>
                        <td>
                            <asp:Button ID="btnSeleccionarTransportista" runat="server" Text="Seleccionar transportista"
                                Width="175" />
                        </td>
                    </tr>
                    <tr>
                        <td colspan="3" align="center">
                            <asp:Button ID="btnProcesarEnvio" runat="server" Text="Procesar envío" />
                        </td>
                    </tr>
                </table>
                <br />
                <br />
                <asp:Panel ID="pnlShippers" runat="server" CssClass="CajaDialogo" Style="display: none;">
                    <div style="padding: 10px; background-color: #0033CC; color: #FFFFFF;">
                        <asp:Label ID="Label5" runat="server" Text="Empresas de transporte" />
                    </div>
                    <div>
                        <table>
                            <tr>
                                <td>
                                    <asp:Label ID="Label6" runat="server" Text="Transportista:" />
                                </td>
                                <td>
                                    <asp:DropDownList ID="ddlShippers" runat="server" Width="150px" />
                                </td>
                                <td>
                                    <input id="btnInsertar" type="button" value="Insertar" />
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    <asp:Label ID="Label7" runat="server" Text="Nombre:" />
                                </td>
                                <td>
                                    <asp:TextBox ID="txtCompanyName" runat="server" />
                                </td>
                                <td>
                                    <input id="btnModificar" type="button" value="Modificar" />
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    <asp:Label ID="Label8" runat="server" Text="Teléfono:" />
                                </td>
                                <td>
                                    <asp:TextBox ID="txtPhone" runat="server" />
                                </td>
                                <td>
                                    <input id="btnBorrar" type="button" value="Borrar" />
                                </td>
                            </tr>
                        </table>
                    </div>
                    <div>
                        <asp:Button ID="btnAceptar" runat="server" Text="Aceptar" />
                        &nbsp;&nbsp;
                        <asp:Button ID="btnCancelar" runat="server" Text="Cancelar" />
                    </div>
                </asp:Panel>
                <cc1:ModalPopupExtender ID="mpeShippers" runat="server" 
                    TargetControlID="btnSeleccionarTransportista"
                    PopupControlID="pnlShippers"
                    OkControlID="btnAceptar" 
                    CancelControlID="btnCancelar"
                    BackgroundCssClass="FondoAplicacion" 
                    DropShadow="True" />
            </div>
        </ContentTemplate>
    </asp:UpdatePanel>
    </form>
</body>
</html>

Al ejecutar la aplicación en su estado actual, cuando hagamos clic sobre el botón btnSeleccionarTransportista, el control ModalPopupExtender visualizará el cuadro de diálogo, como vemos en la siguiente figura.

 

A partir de este momento, nuestro objetivo principal consistirá en proporcionar la funcionalidad adecuada a cada uno de los elementos que componen el panel asociado al ModalPopupExtender; comenzaremos pues por el control ddlShippers, que deberá mostrar los diferentes valores del campo CompanyName existentes en la tabla Shippers. La primera acción a desempeñar consistirá en la creación del servicio WCF.

 

El servicio WCF. Nuestro canal de comunicación

Como decíamos anteriormente, un servicio WCF representa el componente clave que hará que los datos de la tabla Shippers fluyan adecuadamente desde el lado servidor hacia el cliente y viceversa. Para añadirlo a nuestro proyecto, seleccionaremos la opción de menú de Visual Studio 2008 Project > Add New Item, eligiendo en la ventana que aparece a continuación la plantilla correspondiente a AJAX-enabled WCF Service. Como vemos en la siguiente figura, el nombre que daremos al servicio será WSDatos.svc.

 

El código por defecto que contiene un servicio WCF recién creado se reparte en un archivo de marcado con la extensión .svc y otro de code-behind con la extensión .svc.cs; el primero podemos verlo al hacer clic derecho sobre el servicio en el Explorador de soluciones y seleccionar la opción View Markup.

Ambos archivos de código contienen, de forma predeterminada, aquellas directivas, atributos, etc., precisados por el servicio, que facilitan el trabajo al desarrollador, el cual puede comenzar de inmediato a escribir los métodos que compondrán el servicio, sin necesitar entretenerse en estas tareas iniciales de configuración.

El código de marcado del servicio sería el siguiente.

<%@ ServiceHost Language="C#" Debug="true" Service="ManipulacionDatosAjaxModalPopupExtender.WSDatos" CodeBehind="WSDatos.svc.cs" %>

Mientras que a continuación tenemos su code-behind.

using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace ManipulacionDatosAjaxModalPopupExtender
{
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class WSDatos
    {
        // Add [WebGet] attribute to use HTTP GET
        [OperationContract]
        public void DoWork()
        {
            // Add your operation implementation here
            return;
        }

        // Add more operations here and mark them with [OperationContract]
    }
}

 

La clase Shipper. El vehículo de transporte para los datos

Para que los registros de la tabla Shippers puedan "viajar" a través de nuestro servicio WCF, es necesario disponer de una clase que permita ser seriada, y que imite la estructura de registro de la tabla de datos.

Es por ello que añadiremos al proyecto una nueva clase llamada Shipper, cuyos miembros corresponderán a los diferentes campos de la tabla Shippers. Seguidamente vemos el código de esta clase.

using System.Runtime.Serialization;

namespace ManipulacionDatosAjaxModalPopupExtender
{
    [DataContract]
    public class Shipper
    {
        private int mnShipperID;
        private string msCompanyName;
        private string msPhone;

        public Shipper(int nShipperID, string sCompanyName, string sPhone)
        {
            mnShipperID = nShipperID;
            msCompanyName = sCompanyName;
            msPhone = sPhone;
        }

        [DataMember]
        public int ShipperID
        {
            get { return mnShipperID; }
            set { mnShipperID = value; }
        }

        [DataMember]
        public string CompanyName
        {
            get { return msCompanyName; }
            set { msCompanyName = value; }
        }

        [DataMember]
        public string Phone
        {
            get { return msPhone; }
            set { msPhone = value; }
        }
    }
}

Para que los objetos que instanciemos de esta clase puedan ser seriados, observemos que la declaración de la clase está calificada con el atributo DataContract, mientras que con el atributo DataMember hemos calificado sus propiedades.

Si utilizamos un símil automovilístico para este proceso que estamos desarrollando, podríamos decir que el servicio WSDatos representa la autopista por donde viajan los datos, mientras que la clase Shipper representa el vehículo utilizado por estos para desplazarse; en el caso de que intervengan varios objetos Shipper, el vehículo será una colección genérica List<Shipper>.

 

Creación de un método para el servicio

Como hemos mencionado en un apartado anterior, la primera operación que necesitamos realizar es llenar el DropDownList ddlShippers con los nombres de los transportistas de la tabla Shippers; por lo cual, la obtención de una lista con estos nombres será la primera tarea a desempeñar por el servicio, en cuyo editor de código nos situaremos para escribir el método ObtenerShippers, que vemos a continuación.

using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;

namespace ManipulacionDatosAjaxModalPopupExtender
{
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class WSDatos
    {
        [OperationContract]
        public List<Shipper> ObtenerShippers()
        {
            SqlConnection cnConexion = new SqlConnection(
                ConfigurationManager.ConnectionStrings["CadConexion"].ConnectionString);

            SqlCommand cmdComando = new SqlCommand("SELECT * FROM Shippers", cnConexion);

            cnConexion.Open();
            SqlDataReader drReader = cmdComando.ExecuteReader();

            List<Shipper> lstShippers = new List<Shipper>();

            while (drReader.Read())
            {
                lstShippers.Add(new Shipper((int)drReader["ShipperID"],
                    drReader["CompanyName"].ToString(),
                    drReader["Phone"].ToString()));
            }

            cnConexion.Close();

            return lstShippers;
        }
    }
}

Antes de comenzar a escribir el método, hemos añadido varios espacios de nombre (Configuration, Data.SqlClient, etc.) necesarios para las tareas que deberá acometer nuestro servicio, y hemos eliminado el método de prueba DoWork. Por otro lado, para que los métodos puedan ser adecuadamente expuestos por el servicio, hemos de calificarlos usando el atributo OperationContract.

Entrando ya en el código del método ObtenerShippers, podemos observar que se trata de un proceso sencillo, en el que nos conectamos a la base de datos y obtenemos todos los registros de la tabla Shippers, los cuales recorremos mediante un DataReader. Cada registro es añadido a un objeto Shipper, y este a su vez a una colección List<Shipper>, la cual devolvemos como valor de retorno.

Como ya habremos observado, la cadena de conexión a la base de datos Northwind está ubicada en el archivo Web.config del proyecto, y la forma de recuperarla consiste en utilizar la colección ConnectionStrings del objeto ConfigurationManager. A continuación podemos ver el fragmento de Web.config que contiene esta cadena de conexión.

<connectionStrings>
  <add name="CadConexion" 
       connectionString="Data Source=localhost;Integrated Security=SSPI;Initial Catalog=Northwind;" 
       providerName="System.Data.SqlClient"/>
</connectionStrings>

 

Registrando el servicio para acceder desde el lado cliente

Puesto que los métodos que vamos a escribir para nuestro servicio necesitamos que sean llamados desde el código Javascript de la página, es necesario indicarle a esta de algún modo la existencia del servicio, a fin de que pueda posteriormente utilizarlo.

El modo de informar a la página de esta circunstancia consiste en registrar el servicio a través del componente ScriptManager que habíamos incluido anteriormente; más concretamente, utilizando un componente ServiceReference incluido dentro de la etiqueta <Services> del ScriptManager, como vemos a continuación.

<asp:ScriptManager ID="ScriptManager1" runat="server">
    <Services>
        <asp:ServiceReference Path="WSDatos.svc" />
    </Services>
</asp:ScriptManager>

Asignando a la propiedad Path del ServiceReference el nombre (y ruta si es necesario) del servicio, el WebForm quedará preparado para que los métodos del servicio sean llamados desde el código cliente de la página.

Además de escribir este código de marcado para el ScriptManager de modo directo, podríamos haber utilizado también su ventana de Propiedades, como vemos en la siguiente figura, para hacer uso del editor de referencias de servicio.

 

Llamando al servicio desde el lado cliente

Y por fin llegamos al momento que estábamos esperando: la ejecución desde el código cliente de la página, del método que hemos escrito en el servicio.

Como nuestro objetivo consiste en rellenar el DropDownList ddlShippers con los nombres obtenidos de la tabla de la base de datos, esta operación la realizaremos en la carga inicial de la página; por lo tanto, en la etiqueta <body> asignaremos al evento onload el nombre de una función Javascript, cuyo código conectará con el servicio WSDatos, ejecutando su método ObtenerShippers.

Dado que estamos trabajando en un contexto de ejecución asíncrono, al concluir la ejecución de ObtenerShippers, si esta ha tenido éxito, se realizará una llamada al nombre de la función pasada como primer parámetro al método - ObtenerShippersCompleted en este ejemplo-, la cual recibirá como parámetro un array con los valores de los registros de la tabla Shippers, que fueron generados en el método ObtenerShippers del servicio; simplemente tendremos que recorrer el array, pasando los valores al control de lista de la página.

Si se produjera algún tipo de error, también podremos capturarlo utilizando una función cuyo nombre situaríamos como segundo parámetro del método -ObtenerShippersError. Seguidamente vemos el código de las operaciones que acabamos de describir.

<head runat="server">    
<%
   1: --
   2: //....
   3: --
%> <script type="text/javascript" language="javascript">
   1:  
   2:     function body_onload()
   3:     {
   4:         WSDatos.ObtenerShippers(ObtenerShippersCompleted, ObtenerShippersError);
   5:     }
   6:     
   7:     function ObtenerShippersCompleted(aShippers)
   8:     {
   9:         var ddlShippers = document.getElementById("ddlShippers");
  10:         var nIndice;
  11:         var oOption;
  12:         
  13:         for (nIndice = 0; nIndice < aShippers.length; nIndice++)
  14:         {
  15:             oOption = document.createElement("OPTION");            
  16:             oOption.text = aShippers[nIndice].CompanyName;
  17:             oOption.value = aShippers[nIndice].ShipperID;
  18:             
  19:             ddlShippers.options.add(oOption);
  20:         }        
  21:     }
  22:     
  23:     function ObtenerShippersError(oError)
  24:     {
  25:         alert(oError.get_message());
  26:     }
  27:     
</script> </head> <body onload="body_onload()"> <%
   1: --
   2: //....
   3: --
%> </body>

Como resultado del anterior código, al ejecutar el programa, cuando hagamos clic en el botón que abre el ModalPopupExtender, este mostrará ya cargado el control DropDownList, lo que vemos en la siguiente figura.

Llegados a este punto finalizamos la primera entrega del artículo, dejando para una segunda parte aquellas operaciones relacionadas con la creación de métodos del servicio que reciben parámetros y permiten la edición de los registros de la base de datos. No obstante, en los siguientes enlaces para C# y VB se encuentra disponible el proyecto de ejemplo al completo.

Un saludo.

Published 8/8/2008 13:14 por Luis Miguel Blanco
Archivado en: ,,
Comparte este post:

Comentarios

Thursday, August 14, 2008 11:18 AM por Luis Miguel Blanco

# re: Manipulación de datos en AJAX mediante el control ModalPopupExtender (I)

Hola Enrique

Muchas gracias por tu opinión y por leer el post, me alegro que te haya gustado 8-)

Feliz verano igualmente ;-)

Luismi

Wednesday, August 26, 2009 5:51 PM por Enrique Gomez

# re: Manipulación de datos en AJAX mediante el control ModalPopupExtender (I)

Muy bien Luis Miguel. Muy interesante y claro el blog y este tema de AJAX.  Ojala continues capacitando con estos temas.

Thursday, September 3, 2009 7:17 PM por Luis Miguel Blanco

# re: Manipulación de datos en AJAX mediante el control ModalPopupExtender (I)

Hola Enrique

Muchas gracias por tu interés en el blog. Celebro que este artículo te haya resultado de ayuda.

Un saludo.

Luismi

Monday, September 28, 2009 8:57 PM por adrian

# re: Manipulación de datos en AJAX mediante el control ModalPopupExtender (I)

Hola

Me parece excelente este turorial, al igual que tu blog.

He seguido el tutorial al pie de la letra, pero me aparece un error al momento de ejecutarlo:

Error en tiempo de ejecución de Microsoft BLOCKED SCRIPT 'WSDatos' no está definido

si me pudiras ayudar te estaría muy agradecido

Gracias

Saturday, October 3, 2009 1:08 PM por Luis Miguel Blanco

# re: Manipulación de datos en AJAX mediante el control ModalPopupExtender (I)

Hola Adrián

Se trata de un error extraño, prueba a quitar y volver a poner la referencia hacia el servicio en el ScriptManager, ya que parece que no está encontrando el servicio wcf.

Un saludo,

Luismi