Exportar (0) Imprimir
Expandir todo

Tutorial: Crear controles Web ASP.NET enlazados a datos personalizados para ASP.NET 2.0

En este tutorial se muestra cómo crear un control de servidor Web simple enlazado a datos en ASP.NET versión 2.0. En ASP.NET 2.0, un nuevo modelo de origen de datos permite enlazar controles enlazados a datos a controles de origen de datos, de forma que las operaciones comunes con los datos, como paginar, ordenar y eliminar, se puedan realizar fuera del propio control enlazado a datos. Este modelo de datos produce un control enlazado a datos más flexible para los programadores de páginas y aumenta el nivel de reutilización. El modelo de origen de datos de ASP.NET 2.0 admite igualmente el enlace directo a un objeto de colección de datos. Para obtener más información sobre cómo desarrollar controles enlazados a datos personalizados para el modelo de datos de ASP.NET 2.0, consulte Desarrollar controles de servidor Web enlazados a datos personalizados para ASP.NET 2.0.

En este tutorial, creará un control enlazado a datos que se pueda enlazar a un control de origen de datos o a cualquier objeto que implemente la interfaz IEnumerable.

Entre las tareas ilustradas en este tutorial se incluyen las siguientes:

  • Crear un sitio Web para probar el control enlazado a datos personalizado con compilación dinámica.

  • Crear una clase de controles enlazados a datos que extienda la clase base DataBoundControl. Esta clase muestra una columna de tabla que representa los datos enlazados. A continuación, se incluye un descripción general de los elementos que la clase de controles enlazados a datos debe proporcionar:

    • Un método reemplazado del método PerformSelect de la clase base de controles enlazados a datos. Dentro de este método se realizan tareas para iniciar la recuperación de datos.

    • Un método con un parámetro único de tipo IEnumerable para recibir los datos vueltos. En este método se realizan todas las operaciones de procesamiento de datos necesarias. Como último paso, se llama al método PerformDataBinding para iniciar el enlace de datos.

    • Un método reemplazado del método PerformDataBinding. En este método, se muestran los datos recuperados y se agregan controles secundarios para representar los datos.

  • Registrar el control en el archivo Web.config.

  • Probar el control en una página Web ASP.NET.

  • Compilar el control para que pueda distribuirse como código binario.

  • Probar el control de servidor personalizado enlazado a datos compilado.

Puede utilizar la compilación dinámica de ASP.NET para probar el control en una página sin compilarlo en un ensamblado. ASP.NET compila dinámicamente el código colocado en el directorio App_Code bajo la raíz de un sitio Web ASP.NET. De este modo, es posible obtener acceso a las clases de los archivos de código fuente de este directorio desde las páginas sin necesidad de compilarlas manualmente en ensamblados.

NoteNota

El directorio App_Code es una nueva característica que no estaba disponible en ASP.NET 1.0 y 1.1. El uso del directorio App_Code para las pruebas iniciales del control es opcional. Los pasos principales para generar un control de servidor son los mismos que en las versiones anteriores, como verá en la sección siguiente, "Compilar el control en un ensamblado".

Para crear un sitio Web para probar los controles enlazados a datos personalizados

  1. Cree un sitio Web denominado ServerControlsTest.

    Puede crear el sitio en IIS como un directorio virtual denominado ServerControlsTest. Para obtener más información sobre la creación y configuración de un directorio virtual de IIS, vea Cómo: Crear y configurar directorios virtuales en IIS.

  2. Cree un directorio App_Code directamente bajo el directorio raíz del sitio Web (también denominado raíz de la aplicación Web).

Para crear la clase SimpleDataBoundColumn

  1. En la carpeta App_Code que creó en el paso anterior, cree una clase denominada SimpleDataBoundColumn.cs o SimpleDataBoundColumn.vb.

  2. Agregue el código siguiente al archivo de clase:

using System;
using System.Collections;
using System.ComponentModel;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Samples.AspNet.Controls.CS
{
    [AspNetHostingPermission(SecurityAction.Demand,
       Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand,
        Level = AspNetHostingPermissionLevel.Minimal)]
    public class SimpleDataBoundColumn : DataBoundControl
    {
        public string DataTextField
        {
            get
            {
                object o = ViewState["DataTextField"];
                return ((o == null) ? string.Empty : (string)o);
            }
            set
            {
                ViewState["DataTextField"] = value;
                if (Initialized)
                {
                    OnDataPropertyChanged();
                }
            }
        }

        protected override void PerformSelect()
        {
            // Call OnDataBinding here if bound to a data source using the
            // DataSource property (instead of a DataSourceID), because the
            // databinding statement is evaluated before the call to GetData.       
            if (!IsBoundUsingDataSourceID)
            {
                this.OnDataBinding(EventArgs.Empty);
            }

            // The GetData method retrieves the DataSourceView object from  
            // the IDataSource associated with the data-bound control.            
            GetData().Select(CreateDataSourceSelectArguments(),
                this.OnDataSourceViewSelectCallback);

            // The PerformDataBinding method has completed.
            RequiresDataBinding = false;
            MarkAsDataBound();

            // Raise the DataBound event.
            OnDataBound(EventArgs.Empty);
        }

        private void OnDataSourceViewSelectCallback(IEnumerable retrievedData)
        {
            // Call OnDataBinding only if it has not already been 
            // called in the PerformSelect method.
            if (IsBoundUsingDataSourceID)
            {
                OnDataBinding(EventArgs.Empty);
            }
            // The PerformDataBinding method binds the data in the  
            // retrievedData collection to elements of the data-bound control.
            PerformDataBinding(retrievedData);
        }

        protected override void PerformDataBinding(IEnumerable retrievedData)
        {
            base.PerformDataBinding(retrievedData);

            // Verify data exists.
            if (retrievedData != null)
            {
                Table tbl = new Table();
                TableRow row;
                TableCell cell;
                string dataStr = String.Empty;

                foreach (object dataItem in retrievedData)
                {
                    // If the DataTextField was specified get the data
                    // from that field, otherwise get the data from the first field. 
                    if (DataTextField.Length > 0)
                    {
                        dataStr = DataBinder.GetPropertyValue(dataItem,
                            DataTextField, null);
                    }
                    else
                    {
                        PropertyDescriptorCollection props =
                                TypeDescriptor.GetProperties(dataItem);
                        if (props.Count >= 1)
                        {
                            if (null != props[0].GetValue(dataItem))
                            {
                                dataStr = props[0].GetValue(dataItem).ToString();
                            }
                        }
                    }

                    row = new TableRow();
                    tbl.Rows.Add(row);
                    cell = new TableCell();
                    cell.Text = dataStr;
                    row.Cells.Add(cell);
                }

                this.Controls.Add(tbl); 
            }
        }
    }
}

Descripción del código

La clase SimpleDataBoundColumn se deriva de la clase base DataBoundControl de enlace de datos. Al derivar de esta clase base, se proporcionan las propiedades DataSourceID, DataSource y DataMember de enlace de datos expuestas. Estas propiedades expuestas permiten al programador de páginas especificar el origen de datos y un miembro de datos específico para enlazarlo a este control personalizado.

Para mostrar cómo se agregan otras propiedades de enlace de datos personalizadas, se ha agregado una propiedad DataTextField a la clase SimpleDataBoundColumn. Cuando el programador de páginas establece la propiedad DataTextField, el nuevo valor se almacena en el estado de vista. También llama al método OnDataPropertyChanged si el control ya se ha inicializado. Esto obliga al control enlazado a datos a volver a enlazar los datos de forma que pueda utilizar la nueva configuración de la propiedad de enlace de datos.

Se requiere el método DataBind reemplazado, y se crea la lógica para enumerar el objeto en el origen de datos asociado, así como los controles secundarios. Los controles enlazados a datos de ASP.NET 2.0 requieren que se realicen las tareas siguientes dentro del método DataBind reemplazado, como se muestra en la clase SimpleDataBoundColumn:

  • Se comprueba si la propiedad IsBoundUsingDataSourceID tiene el valor false para determinar si el origen de datos se especifica en la propiedad DataSource.

  • Si los datos que se van a enlazar se especifican en la propiedad DataSource, se llama al método OnDataBinding para enlazar el miembro de datos especificado en la propiedad DataSource.

  • Se llama al método GetData para recuperar el objeto DataSourceView asociado al control enlazado a datos.

  • Se llama al método Select del objeto DataSourceView recuperado para iniciar la recuperación de datos y especificar el método de devolución de llamada OnDataSourceViewSelectCallback que controlará los datos recuperados.

  • Para indicar que las tareas de recuperación de datos del método PerformSelect han finalizado, la propiedad RequiresDataBinding se establece en false y, a continuación, se llama al método MarkAsDataBound.

  • Se provoca el evento OnDataBound.

El método de devolución de llamada OnDataSourceViewSelectCallback recibe los datos recuperados. Este método de devolución de llamada debe aceptar un solo parámetro del tipo IEnumerable. Todas las operaciones de procesamiento de datos se deben realizar aquí si así lo requiere el control personalizado. Este control personalizado utiliza los datos tal cual y, por tanto, en este ejemplo no se realiza ninguna operación adicional de procesamiento de datos. Como último paso, se llama al método PerformDataBinding para iniciar el proceso del enlace de datos.

En un método reemplazado del método PerformDataBinding, se crean todos los controles secundarios que van a representar los datos. Se enumera la colección de datos y se crea un nuevo TableCell para cada elemento de datos enumerado. Si la propiedad DataTextField está establecida, se utiliza para determinar qué campo de datos se enlazará a la propiedad Text de la celda; de lo contrario, se utiliza el primer campo.

El control Table principal se agrega a la colección Controls del control SimpleDataBoundColumn personalizado. Cualquier control que se agregue a la colección Controls del control se representa automáticamente mientras se ejecuta el método Render heredado.

Para obtener más información sobre las implementaciones necesarias de un control de servidor Web enlazado a datos, consulte Desarrollar controles de servidor Web enlazados a datos personalizados para ASP.NET 2.0.

Crear un prefijo de etiqueta

Un prefijo de etiqueta es el prefijo, como "asp" en <asp:Table />, que aparece antes del nombre de tipo del control cuando éste se crea mediante declaración en una página. Para habilitar el control a fin de que pueda utilizarse mediante declaración en una página, ASP.NET necesita que se asigne un prefijo de etiqueta al espacio de nombres del control. El desarrollador de páginas puede proporcionar una asignación de prefijo de etiqueta y espacio de nombres agregando una directiva @ Register en cada página que utiliza el control personalizado, como en el ejemplo siguiente:

<%@ Register TagPrefix="aspSample" 
    Namespace="Samples.AspNet.Controls.CS"%>
NoteNota

La directiva @ Register de ASP.NET 2.0 es la misma que en ASP.NET 1.0 y ASP.NET 1.1. Si está familiarizado con la directiva @ Register de versiones anteriores de ASP.NET, podrá observar que el atributo assembly que especifica el nombre de ensamblado del control no aparece en la directiva @ Register anterior. Cuando falta el atributo assembly, ASP.NET deduce que el ensamblado se ha compilado dinámicamente a partir de los archivos de código fuente del directorio App_Code.

Como alternativa al uso de la directiva @ Register en cada página .aspx, el programador de páginas puede especificar la asignación de prefijo de etiqueta y espacio de nombres en el archivo Web.config. Esto resulta útil si el control personalizado se utiliza en varias páginas de la aplicación Web. En el procedimiento siguiente se describe cómo especificar la asignación del prefijo de etiqueta en el archivo Web.config.

Para agregar una asignación de prefijo de etiqueta al archivo Web.config

  1. Si el sitio Web aún no tiene un archivo denominado Web.config, cree uno en la carpeta raíz del sitio Web.

  2. Si ha creado un archivo Web.config nuevo (vacío), copie el marcado XML siguiente en él y guarde el archivo. Si el sitio ya tiene un archivo Web.config, agréguele el elemento resaltado siguiente.

    NoteNota

    El prefijo de etiqueta debe ser un elemento secundario de la sección controls, que debe estar bajo la sección pages, que a su vez debe ser un elemento secundario de system.web.

    <?xml version="1.0"?>
    <configuration>
      <system.web>
        <pages>
          <controls>
            <add tagPrefix="aspSample"
              namespace="Samples.AspNet.Controls.CS">
            </add>
          </controls>
        </pages>
      </system.web>
    </configuration>
    

    <?xml version="1.0"?>
    <configuration>
      <system.web>
        <pages>
          <controls>
            <add tagPrefix="aspSample"
              namespace="Samples.AspNet.Controls.VB">
            </add>
          </controls>
        </pages>
      </system.web>
    </configuration>
    

    La sección resaltada muestra una entrada de prefijo de etiqueta que asigna el prefijo "aspSample" al espacio de nombres Samples.AspNet.Controls.CS o Samples.AspNet.Controls.VB.

Tras haber especificado la asignación del prefijo de etiqueta en el archivo de configuración, puede utilizar el control SimpleDataBoundColumn mediante declaración (como <aspSample:SimpleDataBoundColumn />) en cualquier página del sitio Web.

NoteNota

La entrada de configuración para el prefijo de etiqueta es una característica nueva de ASP.NET 2.0. En ASP.NET 1.0 y 1.1 la asignación del prefijo de etiqueta se especificaba en la directiva @ Register de cada página que utilizaba el control personalizado.

Crear una página que utilice el control enlazado a datos personalizado

En esta sección del tutorial creará el marcado de la página que le permitirá probar el control enlazado a datos personalizado.

Para crear una página que utilice el control enlazado a datos personalizado

  1. Cree un archivo denominado TestSimpleDataBoundColumn.aspx en el sitio Web.

  2. Copie el marcado siguiente en el archivo TestSimpleDataBoundColumn.aspx y guarde el archivo.

<%@ Page Language="C#" Trace="true"%>
<%@ Register TagPrefix="aspSample" Namespace="Samples.AspNet.Controls.CS" %>
<%@ Import Namespace="System.Data" %>

<!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">
<script runat="server">
 
      ICollection CreateDataSource() 
      {
         DataTable dt = new DataTable();
         DataRow dr;
 
         dt.Columns.Add(new DataColumn("IntegerValue", typeof(Int32)));
         dt.Columns.Add(new DataColumn("StringValue", typeof(string)));
         dt.Columns.Add(new DataColumn("CurrencyValue", typeof(double)));
 
         for (int i = 0; i < 9; i++) 
         {
            dr = dt.NewRow();
 
            dr[0] = i;
            dr[1] = "Item " + i.ToString();
            dr[2] = 1.23 * (i + 1);
 
            dt.Rows.Add(dr);
         }
 
         DataView dv = new DataView(dt);
         return dv;
      }
    
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            simpleDataBoundColumn1.DataSource = CreateDataSource();
            simpleDataBoundColumn1.DataBind();
        }
    }
</script>

<head runat="server">
    <title>SimpleDataBoundColumn test page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <aspSample:SimpleDataBoundColumn runat="server" id="simpleDataBoundColumn1" DataTextField="CurrencyValue" BorderStyle="Solid"></aspSample:SimpleDataBoundColumn>
    </div>
    </form>
</body>
</html>

  1. Ejecute la página SimpleDataBoundColumnTest.aspx.

  2. Realice algunos cambios en el código fuente del control personalizado. Por ejemplo, escriba una cadena adicional agregando esta línea de código al final del método RenderContents:

    writer.Write("<br />Testing how the App_Code directory works.");
    

    writer.Write("<br />Testing how the App_Code directory works.")
    
  3. Actualice la página SimpleDataBoundColumnTest.aspx en el explorador.

    Verá que los cambios realizados en el control se reflejan en la página aunque no haya compilado el control.

Aunque el directorio App_Code permite probar el control sin compilarlo, si desea distribuirlo a otros desarrolladores como código de objeto, debe compilarlo. Por otra parte, el control no se puede agregar al cuadro de herramientas del diseñador visual a menos que se compile en un ensamblado.

Para compilar el control en un ensamblado

  1. Establezca la variable PATH del entorno de Windows del equipo para que incluya la ruta de acceso a la instalación de .NET Framework siguiendo estos pasos:

    1. En Windows, haga clic con el botón secundario del mouse (ratón) en Mi PC, seleccione Propiedades, haga clic en la ficha Avanzadas y, a continuación, en el botón Variables de entorno.

    2. En la lista Variables del sistema, haga doble clic en la variable Path.

    3. En el cuadro de texto Valor de variable, agregue un punto y coma (;) al final de los valores existentes en el cuadro de texto y, a continuación, escriba la ruta de acceso de su instalación de .NET Framework. Normalmente, .NET Framework está instalado en el directorio de instalación de Windows en \Microsoft.NET\Framework\númeroVersión.

    4. Haga clic en Aceptar para cerrar todos los cuadros de diálogo.

  2. Ejecute el comando siguiente desde el directorio App_Code que contiene los archivos de código fuente creados en un procedimiento anterior de este tutorial. Se generará un ensamblado denominado Samples.AspNet.Controls.CS.dll o Samples.AspNet.Controls.VB.dll en el mismo directorio App_Code.

    csc /t:library /out:Samples.AspNet.Controls.CS.dll /r:System.dll /r:System.Web.dll *.cs
    

    vbc /t:library /out:Samples.AspNet.Controls.VB.dll /r:System.dll /r:System.Web.dll *.vb
    

    La opción del compilador /t:library indica al compilador que debe crear una biblioteca en lugar de un ensamblado ejecutable. La opción /out proporciona un nombre para el ensamblado y la opción /r muestra los ensamblados que están vinculados al ensamblado.

    NoteNota

    Para mantener el ejemplo independiente, este tutorial le pide que cree un ensamblado con un solo control. En general, las directrices de diseño de .NET Framework le recomiendan que no cree ensamblados que contengan sólo unas cuantas clases. Para facilitar la implementación, debe crear el menor número de ensamblados posible.

Utilizar TagPrefixAttribute para proporcionar una asignación de prefijo de etiqueta y espacio de nombres

Anteriormente se mostró cómo un desarrollador de páginas puede especificar un prefijo de etiqueta en la página o en el archivo Web.config. Cuando compile el control, también puede sugerir un prefijo de etiqueta predeterminado que el diseñador visual deba utilizar para el control, incluyendo el atributo System.Web.UI.TagPrefixAttribute del nivel de ensamblado. El atributo TagPrefixAttribute es útil porque proporciona un prefijo de etiqueta para que el diseñador visual pueda utilizarlo si no encuentra una asignación de prefijo de etiqueta en el archivo Web.config o en una directiva @ Register de la página. El prefijo de etiqueta se registra con la página la primera vez que se hace doble clic en el control en el cuadro de herramientas o cuando el control se arrastra del cuadro de herramientas a la página.

Si decide usar el atributo TagPrefixAttribute, puede especificarlo en un archivo independiente que se compila con los controles. Por convención, el archivo se denomina AssemblyInfo.extensiónLenguaje, por ejemplo AssemblyInfo.cs o AssemblyInfo.vb. En el procedimiento siguiente se describe cómo especificar los metadatos de TagPrefixAttribute.

NoteNota

Si no especifica el atributo TagPrefixAttribute en el ensamblado del control y el desarrollador de páginas no especifica la asignación de prefijo de etiqueta y espacio de nombres en la página o en el archivo Web.config, el diseñador visual podría crear un prefijo de etiqueta predeterminado. Por ejemplo, Visual Studio 2005 creará su propia etiqueta (como cc1) para el control cuando éste se arrastre desde el cuadro de herramientas.

Para agregar una asignación de prefijo de etiqueta mediante TagPrefixAttribute

  1. Cree un archivo denominado AssemblyInfo.cs o AssemblyInfo.vb en el directorio del código fuente y agregue el código siguiente al archivo.

    using System;
    using System.Web.UI;
    [assembly: TagPrefix("Samples.AspNet.Controls.CS", "aspSample")]
    

    Imports System
    Imports System.Web.UI
    <Assembly: TagPrefix("Samples.AspNet.Controls.VB", "aspSample")> 
    

    El atributo de prefijo de etiqueta crea una asignación entre el espacio de nombres Samples.AspNet.Controls.CS o Samples.AspNet.Controls.VB y el prefijo aspSample.

  2. Vuelva a compilar todos los archivos de código fuente utilizando el comando de compilación que empleó anteriormente (con o sin el recurso incrustado).

Para probar la versión compilada del control personalizado es necesario que las páginas del sitio Web puedan tener acceso al ensamblado del control.

Para hacer que el sitio Web tenga acceso al ensamblado del control

  1. Cree un directorio Bin bajo la raíz del sitio Web.

  2. Arrastre el ensamblado del control (Samples.AspNet.Controls.CS.dll o Samples.AspNet.Controls.VB.dll) del directorio App_Code hasta el directorio Bin.

  3. Elimine el archivo de código fuente del control del directorio App_Code.

    Si no elimina los archivos de código fuente, el tipo del control existirá tanto en el ensamblado compilado como en el ensamblado generado dinámicamente creado por ASP.NET. Esto creará una referencia ambigua al cargar el control y las páginas en las que se utilice dicho control producirán un error de compilador.

El ensamblado que ha creado en este procedimiento se denomina ensamblado privado porque debe estar colocado en un directorio Bin del sitio Web ASP.NET para permitir que las páginas del sitio utilicen el control. No es posible tener acceso al ensamblado desde otras aplicaciones a menos que también se instale una copia con dichas aplicaciones. Si crea controles para aplicaciones de alojamiento Web compartidas, normalmente los empaquetará en un ensamblado privado. No obstante, si crea controles para su uso en un entorno de alojamiento dedicado o crea un conjunto de controles que un ISP pone a disposición de todos sus clientes, podría necesitar empaquetar los controles en un ensamblado compartido (con nombre seguro) que esté instalado en la Caché de ensamblados global. Para obtener más información, consulte Trabajar con ensamblados y la Caché de ensamblados global.

A continuación, debe modificar la asignación del prefijo de etiqueta que ha creado en Web.config para especificar el nombre de ensamblado del control.

Para modificar la asignación del prefijo de etiqueta de Web.config

  • Edite el archivo Web.config para agregar un atributo assembly al elemento add tagPrefix:

    <controls>
      <add tagPrefix="aspSample"
        namespace="Samples.AspNet.Controls.CS" 
        assembly="Samples.AspNet.Controls.CS">
      </add>
    </controls>
    

    <controls>
      <add tagPrefix="aspSample"   
        namespace="Samples.AspNet.Controls.VB" 
        assembly="Samples.AspNet.Controls.VB">
      </add>
    </controls>
    

El atributo assembly especifica el nombre del ensamblado en el que está el control. El elemento add tagPrefix asigna un prefijo de etiqueta a una combinación de espacio de nombres y ensamblado. Cuando ASP.NET genera dinámicamente el ensamblado a partir de los archivos de código fuente del directorio App_Code, el atributo assembly no es necesario. Cuando no se utiliza el atributo assembly, ASP.NET carga el tipo del control de los ensamblados generados dinámicamente desde el directorio App_Code.

Para ver la página que utiliza el control personalizado

  • Muestre la página SimpleDataBoundColumnTest.aspx en el explorador escribiendo la dirección URL siguiente en la barra de direcciones:

    http://localhost/ServerControlsTest/ SimpleDataBoundColumnTest.aspx
    

Si utiliza el control en un diseñador visual como Visual Studio 2005, podrá agregar el control al cuadro de herramientas, arrastrarlo del cuadro de herramientas a la superficie de diseño y tener acceso a sus propiedades y eventos en el examinador de propiedades. Además, en Visual Studio 2005, el control es totalmente compatible con IntelliSense en la vista de código fuente del diseñador de páginas y en el editor de código. Esto incluye la finalización de instrucciones en un bloque script así como la compatibilidad con el examinador de propiedades cuando el desarrollador de páginas hace clic en la etiqueta del control.

NoteNota

En muchos diseñadores visuales, es posible agregar controles personalizados al cuadro de herramientas del diseñador. Para obtener información detallada, consulte la documentación del diseñador.

Este control de servidor simple enlazado a datos personalizado muestra los pasos fundamentales utilizados para crear un control personalizado que proporcione a los programadores de páginas un modo flexible, eficaz y estándar de enlazarlo a un origen de datos externo. Puede utilizarlo como punto de partida para explorar el marco proporcionado en Visual Studio para la creación de controles de servidor personalizados más complejos. Si desea obtener más información, le ofrecemos las sugerencias siguientes:

Adiciones de comunidad

AGREGAR
Mostrar:
© 2014 Microsoft