lunes, 27 de febrero de 2012

[ASP.NET] Mantener información al cambiar de pagina

 

Introducción

Cuando se desarrolla pantallas de captura de datos por lo general una característica deseable implica que no se pierda los datos cargados cuando se navega a otro sitio a buscar información y se vuelve.

Lamentablemente asp.net no conserva de forma automática el estado de esta información ingresada, por esa razón seremos nosotros quienes mediante código que volquemos la info ingresada para no perderla y facilitar al usuario al operación con la aplicación.

En el ejemplo planteado se cuenta con una lista de productos, con la posibilidad de ingresa una cantidad y opciones provistas para completar la operación.

 

 

Mantener el estado de los datos ingresados

Si bien esta funcionalidad es muy similar a la implementada para mantener el estado de los check, cuenta con algunas diferencias que hay que remarcar.

En este caso se conserva algo mas de información que una simple estado de true/false, es por eso que se crea una clase para mantener los datos que son útiles.

internal class ProductInfo
{
    public int Id { get; set; }
    public int Amount { get; set; }
    public int Shipper { get; set; }
}

Es desde y hacia esta clase que se realizaran las conversiones.

El mantener la información consta de varios pasos:

  1. se recuperan del gridview solo aquellas rows en donde se ingresara una cantidad y un medio de envió. Se arma en este caso una entidad ProductInfo representativa de los datos que se quiere conservar
  2. se recupera la información previamente registrada en la session
  3. se cruzan las listas para quitar las que están en la pagina actual
  4. se ingresa la nueva información para conservar en session

 

public static void KeepProductInfo(GridView grid)
{
    //
    // se obtienen la lista de producto con informacion proporcionada por el usuario
    //
    var listProd = from item in grid.Rows.Cast<GridViewRow>()
                        let amount = ((TextBox)item.FindControl("txtAmount")).Text
                        let shipper = ((DropDownList)item.FindControl("ddlShippers")).SelectedValue
                    where !(string.IsNullOrEmpty(amount) || shipper == "0")
                    select new ProductInfo()
                    {
                        Id = Convert.ToInt32(grid.DataKeys[item.RowIndex].Value),
                        Amount= Convert.ToInt32(amount),
                        Shipper = Convert.ToInt32(shipper)
                    };

    //
    // se recupera de session la lista de seleccionados previamente
    //
    List<ProductInfo> prodInfo = HttpContext.Current.Session["ProdInfo"] as List<ProductInfo>;

    if (prodInfo == null)
        prodInfo = new List<ProductInfo>();

    //
    // se cruzan todos los ingresados en la pagina actual, con los previamente conservados 
    // en Session, devolviendo solo aquellos donde no hay coincidencia
    //
    prodInfo = (from item in prodInfo
                 join item2 in listProd
                    on item.Id equals item2.Id into g
                 where !g.Any()
                 select item).ToList();

    //
    // se agregan la actualizacion realizada por el usuario
    //
    prodInfo.AddRange(listProd);

    HttpContext.Current.Session["ProdInfo"] = prodInfo;

}

 

Por supuesto esta misma técnica puede ser usada para conservar cualquier tipo de dato de cualquier pantalla quiere se quiera evitar perder lo ingresado, se podría haber mantenido la info de textbox simple, o de un checkboxlist, combos, etc.

En este caso se uso un gridview porque es un objeto representativo de información compleja para procesar.

 

Recuperar la info de los datos conservados en session

La acción de recuperar se podría resumir en tres paso

  1. recuperar la información de session
  2. cruzar las row del gridview de la pagina actual con la lista de productos existente
  3. volcar las coincidencias en los controles textbox y dropdownlist

 

public static void RestoreProductInfo(GridView grid)
{

    List<ProductInfo> prodInfo = HttpContext.Current.Session["ProdInfo"] as List<ProductInfo>;

    if (prodInfo == null)
        return;

    //
    // se comparan los registros de la pagina del grid con los recuperados de la Session
    // los coincidentes se devuelven para ser seleccionados
    //
    var result = (from item in grid.Rows.Cast<GridViewRow>()
                  join item2 in prodInfo
                      on Convert.ToInt32(grid.DataKeys[item.RowIndex].Value) equals item2.Id into g
                  where g.Any()
                  select new
                  {
                      gridrow = item,
                      prodonfo = g.First()
                  }).ToList();

    //
    // se recorre cada item para asignar la informacion 
    //
    result.ForEach(x =>
    {
        ((TextBox)x.gridrow.FindControl("txtAmount")).Text = Convert.ToString(x.prodonfo.Amount);
        ((DropDownList)x.gridrow.FindControl("ddlShippers")).SelectedValue = Convert.ToString(x.prodonfo.Shipper);
    });
    
}

 

Uso de la funcionalidad implementada

Si bien al momento de conservar los datos ante un paginado se contaba con eventos concretos pre y post para llevar la acción de conservar o recuperar los datos, en este caso también se deberá hallar momentos equivalentes para los cuales volcar los datos.

 

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        LoadGridProducts();
        
        //
        // lusgo de cargar el grid se asigna la info que el usuario habia ingresado
        //
        ProductsManager.RestoreProductInfo(gvProducts);

    }
}

private void LoadGridProducts()
{
    gvProducts.DataSource = NorthwindData.GetAllProducts();
    gvProducts.DataBind();
}


protected void gvProducts_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
{
    //
    // se persiste la informacion ingresada por el usuario
    //
    ProductsManager.KeepProductInfo((GridView)sender);

    //
    // se recupera el producto seleccionado
    //
    int productId= Convert.ToInt32(gvProducts.DataKeys[e.NewSelectedIndex].Value);

    Response.Redirect(string.Format("ProductDetails.aspx?productId={0}", productId));
}

Tanto en la carga del formulario, como al momento de seleccionar unos de los ítems del grid es que se invocaran la funcionalidad que interactúa con el medio que conserva los datos

 

Código


El ejemplo ha sido implementado con VS2008 y hace uso de una base de datos Sql Compact

 

[C#]
[C# Skydrive]

domingo, 19 de febrero de 2012

[GridView] Mantener checkbox durante la paginación

 

Introducción

Es bien sabido que asp.net no mantiene estado en muchas de sus operaciones, uno de estos casos podría ser al momento de paginas una lista o grid, es por eso que uno será el responsable de mantener la información que el usuario selecciona.

El artículo demuestra como linq no puede ayudar en esta tarea, para ello contaremos con una interfaz simple que permita paginar una lista de productos.

Diseño del GridView

La creación del GridView no tiene nada especial, solo el uso de una columna de témplate para definir el checkbox

<asp:GridView ID="gvProducts" runat="server" CellPadding="4" ForeColor="#333333" 
    GridLines="None" AllowPaging="True" AutoGenerateColumns="False"  DataKeyNames="Id"
    onpageindexchanging="gvProducts_PageIndexChanging" 
    onpageindexchanged="gvProducts_PageIndexChanged">
    <RowStyle BackColor="#EFF3FB" />
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:CheckBox ID="chkSelection" runat="server" />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="Id" HeaderText="Id" />
        <asp:BoundField DataField="Name" HeaderText="Nombre" />
        <asp:BoundField DataField="UnitPrice" HeaderText="Precio Unitario" />
        <asp:BoundField DataField="UnitsInStock" HeaderText="Unidades Stock" />
        <asp:BoundField DataField="SupplierName" HeaderText="Proveedor" />
        <asp:BoundField DataField="CategoryName" HeaderText="Categoria" />
    </Columns>
    <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
    <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" />
    <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
    <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
    <EditRowStyle BackColor="#2461BF" />
    <AlternatingRowStyle BackColor="White" />
</asp:GridView>

Es importante destacar en este punto la utilización de dos eventos claves para poder lograr el objetivo de mantener el estado de los check seleccionados por el usuario.

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        LoadGridProducts();
    }
}

private void LoadGridProducts()
{
    gvProducts.DataSource = NorthwindData.GetAllProducts();
    gvProducts.DataBind();
}

protected void gvProducts_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    ProductsSelectionManager.KeepSelection((GridView)sender);

    gvProducts.PageIndex = e.NewPageIndex;
    LoadGridProducts();

}

protected void gvProducts_PageIndexChanged(object sender, EventArgs e)
{
    ProductsSelectionManager.RestoreSelection((GridView)sender);
}

Es justamente en el evento previo a la paginación PageIndexChanging que se guarda el estado actual de los check de la pagina en curso.

Mientras que en el evento posterior a la paginación, PageIndexChenged, o sea cuando se cargo los registros de la pagina a la cual se navega, es que se restablece las marcas que pudiera haber para la misma.

 

Mantener y Restablecer estado checkbox

La implementación de esta funcionalidad esta centrada en la clase ProductsSelectionManager, esta tiene por objetivo no solo contener la lógica que se requiere, sino también encapsular el uso del objeto Session, vital para mantener el estado la información. La idea es que sea algo transparente si se usa el objeto Session o algún otro para mantener el estado.

Los pasos para mantener el estado de los check implican:

  1. recuperar los check marcados de la pagina actual
  2. obtener el estado previo conservado en el objeto Session
  3. comparar los id de producto de la pagina actual con los previos que se tenían en el objeto Session, si alguno esta presente se descarta (esto es necesario porque se conservara el estado actual de la selección para la página, descartando los anterior que estén presente en la lista)
  4. se asignan los id seleccionado a la colección, que se conservara en Session

 

public static void KeepSelection(GridView grid)
{
    //
    // se obtienen los id de producto checkeados de la pagina actual
    //
    List<int> checkedProd = (from item in grid.Rows.Cast<GridViewRow>()
                             let check = (CheckBox)item.FindControl("chkSelection")
                            where check.Checked
                            select Convert.ToInt32(grid.DataKeys[item.RowIndex].Value)).ToList();

    //
    // se recupera de session la lista de seleccionados previamente
    //
    List<int> productsIdSel =  HttpContext.Current.Session["ProdSelection"] as List<int>;

    if (productsIdSel == null)
        productsIdSel = new List<int>();

    //
    // se cruzan todos los registros de la pagina actual del gridview con la lista de seleccionados,
    // si algun item de esa pagina fue marcado previamente no se devuelve
    //
    productsIdSel = (from item in productsIdSel
                     join item2 in grid.Rows.Cast<GridViewRow>()
                        on item equals Convert.ToInt32(grid.DataKeys[item2.RowIndex].Value) into g
                     where !g.Any()
                     select item).ToList();

    //
    // se agregan los seleccionados
    //
    productsIdSel.AddRange(checkedProd);

    HttpContext.Current.Session["ProdSelection"] = productsIdSel;

}

Los pasos para restablecer la selección en la pagina que se visualiza implican:

  1. obtener el estado previo conservado en el objeto Session
  2. comparar los registros de la pagina con los seleccionado, se devuelve las coincidencias
  3. recorrer cada fila del grid que resultara del paso anterior seleccionando el checkbox

 

public static void RestoreSelection(GridView grid)
{

    List<int> productsIdSel = HttpContext.Current.Session["ProdSelection"] as List<int>;

    if (productsIdSel == null)
        return;

    //
    // se comparan los registros de la pagina del grid con los recuperados de la Session
    // los coincidentes se devuelven para ser seleccionados
    //
    List<GridViewRow> result = (from item in grid.Rows.Cast<GridViewRow>()
                                    join item2 in productsIdSel
                                    on Convert.ToInt32(grid.DataKeys[item.RowIndex].Value) equals item2 into g
                                 where g.Any()
                                 select item).ToList();

    //
    // se recorre cada item para marcarlo
    //
    result.ForEach(x => ((CheckBox)x.FindControl("chkSelection")).Checked = true);
    
}

Código

El ejemplo hace uso de una base de datos de Sql Compact 3.5, por eso del archivo .sdf en la carpeta App_Data.

Fue desarrollado usando Visual Studio 2008.

[C#]
[C# SkyDrive]