May 2008 - Artículos

Durante el desarrollo de una aplicación Web, es posible que nos encontremos ante el diseño de un WebForm en el que debido a las características de la información a recabar, resulte conveniente que ciertos datos sean introducidos desde un formulario independiente del principal, empleando una funcionalidad similar a la que ofrecen los cuadros de diálogo de Windows Forms, cuando abrimos un formulario mediante el método ShowDialog de la clase Form.

 

ModalPopupExtender. Descripción general

Afortunadamente, mediante el uso de AJAX, implementar este funcionamiento no solamente es posible, sino que resulta muy sencillo gracias al uso de ModalPopupExtender, uno de los componentes incluidos en el paquete de controles Ajax Control Toolkit.

ModalPopupExtender es un extensor, lo que significa que permite ampliar o extender la funcionalidad de un control ASP.NET ya existente. Para el caso que nos ocupa, podemos aplicar dicho extensor a un control Panel -también podríamos haber utilizado un control Table-, consiguiendo que el comportamiento de este último se asemeje al de un cuadro o caja de diálogo, de forma que cuando el ModalPopupExtender es activado y el control Panel asociado que representa el diálogo es visualizado, el resto de controles de la página quedan deshabilitados hasta que no pulsemos el botón de confirmación o cancelación que a tal efecto deberemos haber incluido en el control Panel.

En el presente artículo vamos a desarrollar un ejemplo que nos permita describir los elementos más importantes del ModalPopupExtender, así como su aplicación práctica en la construcción de un diálogo de selección de datos.

 

El escenario de trabajo

Supongamos que debemos desarrollar un formulario Web para realizar una toma de datos, con un aspecto como el mostrado en la siguiente figura.

Entre el conjunto de controles TextBox de este formulario, existe uno: txtSituacion, que el usuario debería rellenar con el nombre de una ciudad, mes y año, en un formato como el del siguiente ejemplo:

Zaragoza, marzo de 2008

A pesar de que el usuario tiene plena libertad para escribir este valor dentro del mencionado TextBox, no estaría de más que recibiera una pequeña ayuda. De esta tarea se van a ocupar varios controles DropDownList, que situados dentro de un control Panel, van a funcionar como un cuadro de diálogo gracias a un ModalPopupExtender, y nos van a permitir seleccionar cada una de las partes del dato a introducir en txtSituacion, dejando a la aplicación el trabajo de unir todos los fragmentos para componer la cadena final resultante, que asignaremos al TextBox.

 

Los controles principales de la página

En primer lugar crearemos un nuevo proyecto ASP.NET, y nos situaremos en el diseñador de la página Web, donde comenzaremos por agregar los controles que conformarán el formulario de entrada de datos que ya vimos en la anterior figura. Es muy importante no olvidar la inclusión de un componente ScriptManager a la página, dado que vamos a implementar características AJAX en la misma. El siguiente bloque de código muestra todas las instrucciones necesarias.

<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<div>
    <table border="2">
        <tr>
            <td colspan="2" align="center">
                <asp:Label ID="Label1" runat="server" Text="Datos de solicitud" Font-Bold="True"
                    Font-Underline="True" />
            </td>
        </tr>
        <tr>
            <td>
                <asp:Label ID="Label2" runat="server" Text="Nombre:" />
            </td>
            <td>
                <asp:TextBox ID="txtNombre" runat="server" />
            </td>
        </tr>
        <tr>
            <td>
                <asp:Label ID="Label3" runat="server" Text="e-mail:" />
            </td>
            <td>
                <asp:TextBox ID="txtEmail" runat="server" />
            </td>
        </tr>
        <tr>
            <td>
                <asp:Label ID="Label7" runat="server" Text="Situación:" />
            </td>
            <td>
                <asp:TextBox ID="txtSituacion" runat="server" Width="300" />
            </td>
        </tr>
        <tr>
            <td>
                <asp:Button ID="btnAsistente" runat="server" Text="Asistente de selección" Width="150" />
            </td>
            <td>
                <asp:Button ID="btnGrabar" runat="server" Text="Grabar datos" Width="125" />
            </td>
        </tr>
    </table>
</div>
</form>

 

Los controles del cuadro de diálogo

Continuando en este mismo diseñador del formulario Web, como siguiente paso añadiremos un Panel en cuyo interior situaremos los controles que constituirán nuestro cuadro de diálogo. Adicionalmente incluiremos en la cabecera de la página un estilo para que el aspecto del control Panel y su contenido resulte más atractivo al usuario.

<style type="text/css">
    .CajaDialogo
    {
        background-color: #99ffcc;
        border-width: 4px;
        border-style: outset;
        border-color: Yellow;
        padding: 0px;
        width: 200px;
        font-weight: bold;
        font-style: italic;
    }
    .CajaDialogo div
    {
        margin: 5px;
        text-align: center;
    }
</style>
//….
//….
<asp:Panel ID="pnlSeleccionarDatos" runat="server" CssClass="CajaDialogo">
    <div style="padding: 10px; background-color: #0033CC; color: #FFFFFF;">
        <asp:Label ID="Label4" runat="server" Text="Seleccionar datos" />
    </div>
    <div>
        <table>
            <tr>
                <td>
                    <asp:Label ID="Label5" runat="server" Text="Ciudad:" />
                </td>
                <td>
                    <asp:DropDownList ID="ddlCiudades" runat="server">
                        <asp:ListItem>Zamora</asp:ListItem>
                        <asp:ListItem>Teruel</asp:ListItem>
                        <asp:ListItem>Salamanca</asp:ListItem>
                        <asp:ListItem>Sevilla</asp:ListItem>
                        <asp:ListItem>Lugo</asp:ListItem>
                    </asp:DropDownList>
                </td>
            </tr>
            <tr>
                <td>
                    <asp:Label ID="Label6" runat="server" Text="Mes:" />
                </td>
                <td>
                    <asp:DropDownList ID="ddlMeses" runat="server" />
                </td>
            </tr>
            <tr>
                <td>
                    <asp:Label ID="Label8" runat="server" Text="Año:" />
                </td>
                <td>
                    <asp:DropDownList ID="ddlAnualidades" runat="server" />
                </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>

La siguiente figura muestra el aspecto del panel recién creado.

Fijándonos en el código aspx que acabamos de escribir, nos habremos percatado de que el único control DropDownList al que hemos añadido valores en su lista es ddlCiudades; esto es debido a que los otros dos controles de lista serán llenados en el code-behind de la página, en el evento Load para ser más exactos.

La técnica seguida para añadir los valores a las listas de mes y año consistirá en inicializar una variable DateTime y dos ArrayList; mediante un bucle incrementaremos el valor del mes y año de la fecha, asignándola a los ArrayList; por último, asignaremos estos ArrayList como origen de datos de los controles DropDownList. El código correspondiente a este proceso lo vemos a continuación.

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // rellenar dos listas con los valores
        // de meses y años que usaremos 
        // en los controles de lista desplegable
        ArrayList alMeses = new ArrayList();
        ArrayList alAnualidades = new ArrayList();
        DateTime dtFecha = new DateTime(2000, 1, 1);

        for (int nContador = 1; nContador <= 12; nContador++)
        {
            alMeses.Add(dtFecha.ToString("MMMM"));
            alAnualidades.Add(dtFecha.Year);
            dtFecha = dtFecha.AddMonths(1);
            dtFecha = dtFecha.AddYears(1);
        }

        // asignar las listas a los controles
        this.ddlMeses.DataSource = alMeses;
        this.ddlMeses.DataBind();

        this.ddlAnualidades.DataSource = alAnualidades;
        this.ddlAnualidades.DataBind();
    }
}

La causa de emplear dos técnicas distintas de llenar las listas desplegables -desde código aspx y code-behind- reside simplemente en mostrar al lector las distintas posibilidades existentes de abordar esta operación sobre este tipo de control.

 

Dotando al Panel del comportamiento de un diálogo. ModalPopupExtender entra en acción

Para proporcionar a nuestro control Panel el comportamiento de un cuadro de diálogo nos queda pendiente el detalle más importante: añadir a la página Web el ya comentado control AJAX ModalPopupExtender, que una vez conectado con el Panel, hará que este funcione como una ventana modal, impidiendo al usuario que interactúe con otros elementos que no sean los contenidos dentro del control Panel.

La siguiente lista muestra las principales propiedades del ModalPopupExtender que tendremos que manipular, para configurar adecuadamente nuestra caja de diálogo.

--- TargetControlID. Identificador del control de la página Web que provocará la activación del ModalPopupExtender.

--- PopupControlID. Identificador del control que el ModalPopupExtender muestra como cuadro de diálogo modal. Podemos utilizar cualquier control que actúe como contenedor: Panel, Table, etc.

--- OkControlID. Identificador de un control situado dentro del cuadro de diálogo, que actuará como botón de confirmación. Esta labor corresponderá habitualmente a un control de tipo Button, pero podemos emplear otros tipos.

--- CancelControlID. Identificador de un control situado dentro del cuadro de diálogo, que actuará como botón de cancelación. Esta labor corresponderá habitualmente a un control de tipo Button, pero podemos emplear otros tipos.

--- OnOkScript. Código JavaScript que se ejecutará cuando el control asignado a la propiedad OkControlID desencadene su evento Click.

--- OnCancelScript. Código JavaScript que se ejecutará cuando el control asignado a la propiedad CancelControlID desencadene su evento Click.

--- DropShadow. Valor de tipo bool que permite mostrar una sombra alrededor del borde del cuadro de diálogo, para dotar a este de un efecto de resaltado.

--- BackgroundCssClass. Nombre correspondiente a un estilo CSS, que será aplicado al resto de controles de la página Web no pertenecientes al cuadro de diálogo, y que permanecen deshabilitados mientras este último está en funcionamiento.

Según las propiedades que acabamos de mencionar en esta tabla, la configuración del ModalPopupExtender quedaría como muestra el siguiente fuente.

<cc1:ModalPopupExtender ID="mpeSeleccion" runat="server" 
    TargetControlID="btnAsistente"
    PopupControlID="pnlSeleccionarDatos" 
    OkControlID="btnAceptar" 
    CancelControlID="btnCancelar"
    OnOkScript="mpeSeleccionOnOk()"
    OnCancelScript="mpeSeleccionOnCancel()" 
    DropShadow="True"
    BackgroundCssClass="FondoAplicacion" />

Observemos que los controles para aceptar y cancelar el diálogo corresponden a los botones btnAceptar y btnCancelar incluidos en el Panel pnlSeleccionarDatos. La funcionalidad aportada por estos botones corresponde a las funciones JavaScript indicadas en las propiedades OnOkScript y OnCancelScript, que añadiremos en la cabecera de nuestra página aspx, y que vemos a continuación.

<script language="javascript" type="text/javascript">
function mpeSeleccionOnOk()
{        
    var ddlCiudades = document.getElementById("ddlCiudades");
    var ddlMeses = document.getElementById("ddlMeses");
    var ddlAnualidades = document.getElementById("ddlAnualidades");        
    var txtSituacion = document.getElementById("txtSituacion");
    
    txtSituacion.value = ddlCiudades.options[ddlCiudades.selectedIndex].value + ", " +
        ddlMeses.options[ddlMeses.selectedIndex].value + " de " +
        ddlAnualidades.options[ddlAnualidades.selectedIndex].value;        
}    
function mpeSeleccionOnCancel()
{
    var txtSituacion = document.getElementById("txtSituacion");
    txtSituacion.value = "";
    txtSituacion.style.backgroundColor = "#FFFF99";
}    
</script>

Como ya habíamos comentado, al confirmar la caja de diálogo tomaremos los valores seleccionados en las listas desplegables, y los juntaremos para componer el valor a asignar al TextBox txtSituacion. Por otro lado, si cancelamos el diálogo, vaciaremos el cuadro de texto y le cambiaremos el color de fondo.

Respecto a la apariencia que presentarán los demás controles cuando abramos el cuadro de diálogo, tomarán las características del siguiente estilo que también añadiremos en la cabecera de la página, y utilizaremos desde la propiedad BackgroundCssClass del extensor.

<style type="text/css">
    /* .... */
    .FondoAplicacion
    {
        background-color: Gray;
        filter: alpha(opacity=70);
        opacity: 0.7;
    }
</style>

No obstante, todavía queda un pequeño pero importante detalle que ajustar, ya que cuando ejecutamos la aplicación, justo al ser presentado el contenido de la página, el Panel del cuadro de diálogo se muestra por un instante para desaparecer a continuación, lo cual produce un desagradable efecto, que podemos corregir si aplicamos a este elemento el valor de estilo "display: none" para ocultarlo, como vemos seguidamente.

<asp:Panel ID="pnlSeleccionarDatos" runat="server" CssClass="CajaDialogo" Style="display: none;">

Ahora ya podemos ejecutar la aplicación, en la que al hacer clic sobre el botón btnAsistente, el extensor ModalPopupExtender abrirá el Panel pnlSeleccionarDatos ofreciendo el aspecto de la siguiente figura.

Dado que al cancelar el cuadro de diálogo cambiamos el color de la caja de texto, como detalle complementario vamos a añadir una funcionalidad consistente en devolver a dicha caja su color original cuando vuelva a tomar el foco. Se trata de una operación que bien podíamos haber programado en el código aspx de la página, pero hemos optado por hacerlo en el code-behind a efectos de demostrar las capacidades de generación de código JavaScript de la clase Page.

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // generar código JavaScript para asociar
        // con el evento onfocus del textbox txtSituacion
        System.Text.StringBuilder sbJS = new System.Text.StringBuilder();
        sbJS.Append("function txtSituacionOnFocus()");
        sbJS.Append("{ \n");
        sbJS.Append("    var txtSituacion = document.getElementById('txtSituacion'); \n");
        sbJS.Append("    txtSituacion.style.backgroundColor = '#FFFFFF'; \n");
        sbJS.Append("}");

        // registrar en la página el código JavaScript
        Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
            "CodigoJS", sbJS.ToString(), true);

        // asociar el evento cliente con la función JavaScript para este control
        this.txtSituacion.Attributes.Add("onfocus", "txtSituacionOnFocus()");            
        //....
    }
}

Y con este detalle habríamos completado el ejemplo, cuyo código fuente se encuentra disponible en los siguientes enlaces para C# y VB.

Espero que os resulte de utilidad.

Un saludo.

 

Publicado por Luis Miguel Blanco | 89 comment(s)
Archivado en: ,,

Cuando desde Silverlight utilizamos un servicio Web para conectarnos a una base de datos y rellenar con una de sus tablas un control DataGrid -del modo explicado en este artículo-, puede darse el caso de que nos encontremos con el siguiente error.

El cual nos informa que se ha sobrepasado el tamaño máximo de mensaje soportado por el enlace de tipo BasicHttpBinding, que emplea el canal de comunicación abierto por el servicio Web.

Esta situación sucede cuando la tabla de la base de datos que vamos a utilizar tiene un tamaño considerable; por ejemplo, en el caso de Northwind, el error se producirá si usamos la tabla Orders, pero funcionará correctamente con Products ya que su tamaño es menor.

El mencionado tamaño máximo para el mensaje transmitido por el servicio Web, es un valor que puede ajustarse en el archivo de configuración ServiceReferences.ClientConfig, situado en el proyecto Silverlight de la solución, como vemos en la siguiente figura.

Dentro de dicho archivo de configuración, los atributos maxBufferSize y MaxReceivedMessageSize, pertenecientes al elemento binding, contienen los valores que determinan este tamaño del mensaje, que por defecto se encuentra establecido a 65536, como vemos en el siguiente bloque de código.

<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IWSDatos" 
                  maxBufferSize="65536"
                  maxReceivedMessageSize="65536">
                  
                    <security mode="None" />
                </binding>
            </basicHttpBinding>
        </bindings>      
        <!--...-->

Teóricamente, si aumentamos el valor de estos atributos solucionaríamos el problema, asignando, por ejemplo, el tamaño máximo correspondiente al tipo int.

<binding name="BasicHttpBinding_IWSDatos" 
  maxBufferSize="2147483647"
  maxReceivedMessageSize="2147483647">

Sin embargo esta solución no funciona, ya que al ejecutar la aplicación, el entorno de ejecución hace caso omiso de estos valores de configuración, persistiendo el error.

El truco para corregir el problema radica en asignar estos valores dentro del code-behind de la página XAML de Silverlight, en el evento Loaded del UserControl, por ejemplo.

En concreto, las operaciones que necesitamos realizar consisten en instanciar un tipo BasicHttpBinding, al que asignaremos el tamaño antes mencionado en sus propiedades MaxReceivedMessageSize y MaxBufferSize. Este objeto representa el tipo de enlace utilizado por el servicio.

Por otro lado instanciaremos un tipo EndpointAddress, al que pasaremos en su constructor la dirección del servicio Web. Este objeto representa la dirección que la aplicación Silverlight usará para comunicarse con el servicio.

Finalmente crearemos la instancia del servicio Web, pasando como parámetros a su constructor los objetos que acabamos de crear. El resto de operaciones consisten en las ya conocidas llamadas al método asíncrono del servicio, que se encargará de devolver la tabla de la base de datos; y la creación del manipulador de evento que será llamado cuando finalice el anterior método asíncrono. Todo ello lo vemos en el siguiente bloque de código.

using System.ServiceModel;
//....
public partial class Page : UserControl
{
    //....
    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        // crear un objeto del tipo de enlace que utilizará el servicio
        BasicHttpBinding oBinding = new BasicHttpBinding();
        oBinding.MaxReceivedMessageSize = 2147483647;
        oBinding.MaxBufferSize = 2147483647;

        // crear el objeto con la dirección que el cliente --aplicación Silverlight--
        // usará para comunicar con el servicio
        EndpointAddress oEndpoint = new EndpointAddress(new Uri("http://localhost:54338/WSDatos.svc"));

        // instanciar el servicio
        RefWSDatos.WSDatosClient wsDatos = new SLErrorMessageSize.RefWSDatos.WSDatosClient(oBinding, oEndpoint);
        
        // indicar el manipulador del evento xxxCompleted del servicio
        // que será llamado al terminar de obtener la tabla de la base de datos
        wsDatos.ObtenerTablaOrdersCompleted += new EventHandler<SLErrorMessageSize.RefWSDatos.ObtenerTablaOrdersCompletedEventArgs>(wsDatos_ObtenerTablaOrdersCompleted);
        
        // llamar al método del servicio que devuelve la tabla de la base de datos
        wsDatos.ObtenerTablaOrdersAsync();
    }

    void wsDatos_ObtenerTablaOrdersCompleted(object sender, SLErrorMessageSize.RefWSDatos.ObtenerTablaOrdersCompletedEventArgs e)
    {
        // asignar las filas de la tabla al DataGrid
        this.grdDatos.ItemsSource = e.Result;
    }
}

Al ejecutar nuevamente la aplicación ya no se producirá el error, cargándose el DataGrid con los registros de la tabla, como vemos en la siguiente figura.

El código XAML correspondiente a esta página lo vemos a continuación.

<UserControl xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    x:Class="SLErrorMessageSize.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="500" Height="400" 
    Loaded="UserControl_Loaded">
    
    <Grid x:Name="LayoutRoot" Background="LightCyan" >
        <Grid.RowDefinitions>
            <RowDefinition Height="40" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        
        <TextBlock Grid.Row="0" 
                   HorizontalAlignment="Center" 
                   VerticalAlignment="Center" 
                   Text="Tabla Orders" />
                
        <my:DataGrid x:Name="grdDatos" 
                     AutoGenerateColumns="True" 
                     Margin="5" 
                     Grid.Row="1" />
    </Grid>
    
</UserControl>

El código fuente del ejemplo comentado se encuentra disponible en los siguientes enlaces para C# y VB.

Espero que os resulte de utilidad.

Un saludo.

Publicado por Luis Miguel Blanco | 2 comment(s)
Archivado en:

En un artículo de introducción al control DataGrid publicado anteriormente, vimos que para rellenar de datos este control se debe emplear un tipo que implemente la interfaz IEnumerable, lo que nos permite crear una colección List<MiClase>, donde MiClase representa cada uno de los objetos de la colección visualizada en las filas del control.

Dado que habitualmente la información con la que trabajamos se encuentra ubicada en bases de datos -SQL Server como ejemplo más representativo-, en este artículo explicaremos una técnica que nos permitirá conectarnos a la base de datos Northwind, y utilizar algunas de sus tablas como fuente de datos para el control DataGrid, empleando LINQ y servicios Web WCF como mecanismo para la obtención de los datos.

 

Creación del proyecto Silverlight

El primer paso, como es natural, consistirá en abrir Visual Studio 2008 y crear un nuevo proyecto de tipo Silverlight Application al que daremos el nombre SLDataGridSQL_CS  (en los siguientes enlaces se encuentran disponibles los proyectos en C# y VB). El asistente para la creación del proyecto nos pedirá entonces que indiquemos el tipo de entorno de pruebas dónde será alojado el contenido Silverlight que vamos a desarrollar, a lo que nosotros responderemos seleccionando un proyecto de tipo Web Application, como vemos en la siguiente figura.

Esta elección es muy importante, ya que este tipo de proyecto será el que nos ofrezca la posibilidad de definir un contexto de datos basado en LINQ, cosa que no ocurriría si eligiéramos, por ejemplo, la opción de utilizar un sitio Web como banco de pruebas.

Preparación del contexto de datos

La siguiente acción que llevaremos a cabo será la creación del origen de datos que conectará con la base de datos Northwind; para lo cual nos situaremos en la ventana Explorador de servidores, haremos clic derecho en el nodo Conexiones de datos y seleccionaremos la opción Agregar conexión; tras lo cual se abrirá la caja de diálogo mediante la que crearemos una conexión contra la base de datos ya mencionada, quedando expuestos sus elementos para una posterior selección.

Seguidamente, en la ventana Explorador de soluciones, nos situaremos en el proyecto Web, desde el cual definiremos el contexto de datos basado en LINQ.

Entre las diferentes variantes que LINQ ofrece para la obtención y manipulación de datos se encuentra LINQ to Classes, que está especialmente diseñada para interactuar con bases de datos SQL Server, y será la que emplearemos para obtener los registros de las tablas de Northwind.

Para añadir estas funcionalidades de LINQ a la aplicación haremos clic derecho sobre el proyecto Web y elegiremos la opción Agregar > Nuevo elemento, abriéndose a continuación la ventana Agregar nuevo elemento, desde la que seleccionaremos LINQ to SQL Classes para crear el contexto de datos, al que daremos el nombre DCNorthwind.

Pulsaremos Agregar para confirmar el anterior cuadro de diálogo, abriéndose a continuación el diseñador correspondiente al contexto de datos. Sobre este diseñador arrastraremos -desde la conexión contra Northwind creada en el Explorador de servidores- las tablas con las que vamos a trabajar.

Creando el canal de comunicación

El siguiente paso consiste en desarrollar el mecanismo para el transporte de los datos desde su punto de origen -las tablas de la base de datos-hasta el lugar de destino -el control DataGrid.

Para conseguir este objetivo emplearemos un servicio Web, que añadiremos al proyecto Web de la solución utilizando la opción WCF Service de la ventana Agregar nuevo elemento. Como nombre para el servicio usaremos WSDatos.

Antes de proseguir con el servicio Web volveremos un momento al diseñador de LINQ para realizar un pequeño ajuste. Debido a que los datos que manipularemos "viajarán" gracias al servicio Web, es preciso seriar dicha información, por lo que nos situaremos en la Ventana de propiedades del contexto de datos, asignando a la propiedad Serialization Mode el valor Unidirectional.

Regresando al servicio Web editaremos el archivo IWSDatos.cs, correspondiente a la interfaz del servicio y añadiremos la declaración de los métodos que devolverán las filas de las tablas de la base de datos.

// IWSDatos.cs
// ------------------    

[ServiceContract]
public interface IWSDatos
{
    [OperationContract]
    List<Shippers> ObtenerTablaShippers();

    [OperationContract]
    List<Products> ObtenerTablaProducts();
}

Como siguiente paso editaremos la clase WSDatos -que implementa la interfaz IWSDatos que acabamos de modificar- escribiendo los métodos que hemos declarado en la interfaz del servicio. Para facilitarnos la escritura de la declaración de dichos métodos, podemos hacer clic derecho en el nombre de la interfaz, seleccionando la opción Implementar interfaz, que creará los métodos vacíos.

El código a escribir en sendos métodos consistirá en crear una instancia del contexto de datos y una expresión LINQ para devolver la tabla correspondiente de la base de datos, que el método retornará en forma de colección.

public class WSDatos : IWSDatos
{
    #region Miembros de IWSDatos

    public List<Shippers> ObtenerTablaShippers()
    {
        DCNorthwindDataContext dcNorthwind = new DCNorthwindDataContext();
        var tblShippers = from tblShip
                          in dcNorthwind.Shippers
                          select tblShip;

        return tblShippers.ToList();        
    }

    public List<Products> ObtenerTablaProducts()
    {
        DCNorthwindDataContext dcNorthwind = new DCNorthwindDataContext();
        var tblProducts = from tblProd
                          in dcNorthwind.Products
                          select tblProd;

        return tblProducts.ToList();
    }

    #endregion
}

El servicio Web utiliza WSHttpBinding como tipo de enlace predeterminado, sin embargo, el enlace soportado por Silverlight es BasicHttpBinding. Esta circunstancia nos obliga a editar el archivo Web.config y modificar este valor, que se encuentra en el atributo binding del elemento endpoint, situado a su vez dentro del elemento service, como vemos a continuación.

<system.serviceModel>
<!--....-->   
<services>
 <service behaviorConfiguration="SLDataGridSQL_CS_Web.WSDatosBehavior"
  name="SLDataGridSQL_CS_Web.WSDatos">
  <endpoint address="" binding="basicHttpBinding" contract="SLDataGridSQL_CS_Web.IWSDatos">

Una vez terminadas estas operaciones habremos concluido el desarrollo del servicio Web, por lo que ahora pasaremos a diseñar la interfaz de usuario en Silverlight y su conexión con el servicio que le proporcionará los datos.

Creando la interfaz de usuario

Como acabamos de comentar nos situaremos en el proyecto Silverlight, más concretamente en su archivo Page.xaml, para diseñar la interfaz que emplearemos al ejecutar la aplicación.

En primer lugar quitaremos el panel Grid que por defecto incorpora la página, ya que en nuestro caso vamos a emplear paneles de tipo StackPanel, más concretamente añadiremos uno de estos paneles para contener todos los elementos, y otro anidado al primero, en el que incluiremos un TextBlock a modo de etiqueta y un ListBox con los nombres de las tablas a consultar. Finalmente agregaremos un DataGrid, que será el control visualizador de la tabla seleccionada desde el ListBox. A continuación podemos ver el código XAML resultante.

<UserControl xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"  x:Class="SLDataGridSQL_CS.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="600" Height="400">

    <StackPanel Background="LightYellow">
        <StackPanel Background="LightCyan" 
                    Margin="5" 
                    Height="100" 
                    Orientation="Horizontal" 
                    HorizontalAlignment="Center" >

            <TextBlock Text="Seleccionar tabla" 
                       VerticalAlignment="Center" 
                       Margin="10" />    
    
            <ListBox x:Name="lstTablas" 
                     Width="100" Height="75" 
                     Margin="10" 
                     HorizontalAlignment="Center" 
                     SelectionChanged="lstTablas_SelectionChanged">
                <ListBoxItem Content="Shippers" />
                <ListBoxItem Content="Products" />
            </ListBox>
        </StackPanel>

        <my:DataGrid x:Name="grdDatos" 
                     AutoGenerateColumns="True" 
                     Width="550" Height="250" 
                     Margin="5" />
    </StackPanel>
</UserControl>

Gracias a la combinación de paneles anidados y a las propiedades de orientación de los elementos dentro de dichos paneles, obtendremos un diseño como el que muestra la siguiente figura.

Comunicando con el servicio Web

El siguiente paso a realizar consistirá en conectar el proyecto Silverlight con el servicio Web creado en el proyecto Web, utilizando para ello una referencia hacia dicho servicio. Esta referencia la crearemos situándonos en el Explorador de soluciones, haciendo clic derecho en el nombre del proyecto Silverlight, y seleccionando la opción Agregar referencia de servicio.

Como consecuencia se abrirá la ventana para agregar la referencia, en la que pulsaremos sobre la flecha desplegable del botón Detectar, seleccionando la opción Servicios de la solución; acción esta que traerá al proyecto Silverlight la información del servicio WSDatos. Si desplegamos los elementos del servicio situado en el panel Servicios, llegaremos hasta su interfaz, pudiendo visualizar los métodos de que dispone en el panel Operaciones. El nombre que daremos a nuestra referencia será RefWSDatos, como vemos en la siguiente figura.

Una vez creada la referencia al servicio, solamente nos queda escribir el código para dotar de lógica a la página XAML.

El proceso a desarrollar consistirá en detectar la selección de una tabla mediante el evento SelectionChanged del ListBox. A continuación instanciaremos el servicio Web a través de la referencia -RefWSDatos.WSDatosClient- creada anteriormente, llamando después al método asíncrono que obtiene las filas de la tabla seleccionada. Dado que el mencionado método asíncrono no devuelve valor alguno, crearemos un manipulador de evento que será llamado cuando el método asíncrono finalice su ejecución; siendo en este manipulador donde realmente obtendremos los registros de la tabla en forma de colección, que asignaremos como fuente de datos al DataGrid. En el siguiente bloque de código podemos ver el code-behind de la página XAML.

public partial class Page : UserControl
{
    public Page()
    {
        InitializeComponent();
    }

    private void lstTablas_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        // instanciar el servicio Web
        RefWSDatos.WSDatosClient wsObtenerDatos = new SLDataGridSQL_CS.RefWSDatos.WSDatosClient();

        // comprobar el elemento seleccionado del ListBox
        // asociar el método manipulador del evento xxxCompleted
        // y llamar al método asíncrono que obtiene las filas de la tabla
        if (((ListBoxItem)this.lstTablas.SelectedItem).Content.ToString() == "Shippers")
        {
            wsObtenerDatos.ObtenerTablaShippersCompleted += new EventHandler<SLDataGridSQL_CS.RefWSDatos.ObtenerTablaShippersCompletedEventArgs>(wsObtenerDatos_ObtenerTablaShippersCompleted);
            wsObtenerDatos.ObtenerTablaShippersAsync();
        }
        else
        {
            wsObtenerDatos.ObtenerTablaProductsCompleted += new EventHandler<SLDataGridSQL_CS.RefWSDatos.ObtenerTablaProductsCompletedEventArgs>(wsObtenerDatos_ObtenerTablaProductsCompleted);
            wsObtenerDatos.ObtenerTablaProductsAsync();
        }
    }

    // los siguientes manipuladores de evento son llamados al finalizar
    // la ejecución del método asíncrono; en ellos se obtienen las filas
    // de la tabla en forma de colección, la cual es pasada al DataGrid
    // como fuente de datos
    void wsObtenerDatos_ObtenerTablaShippersCompleted(object sender, SLDataGridSQL_CS.RefWSDatos.ObtenerTablaShippersCompletedEventArgs e)
    {
        this.grdDatos.ItemsSource = e.Result;
    }

    void wsObtenerDatos_ObtenerTablaProductsCompleted(object sender, SLDataGridSQL_CS.RefWSDatos.ObtenerTablaProductsCompletedEventArgs e)
    {
        this.grdDatos.ItemsSource = e.Result;
    }
}

El resultado, en tiempo de ejecución, podemos apreciarlo en la siguiente figura, donde el DataGrid visualiza los registros de la tabla seleccionada.

Y con esto concluimos el presente artículo, que hemos dedicado a ilustrar el modo de conectar desde Silverlight con una base de datos SQL Server, para poblar un control DataGrid con una de sus tablas. Las técnicas aquí mostradas están basadas en la documentación de los siguientes enlaces publicados por Jesse Liberty y el Swiss MSDN Team Blog.

Espero que resulte de utilidad.

Un saludo.

Publicado por Luis Miguel Blanco | 50 comment(s)
Archivado en: ,