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]

47 comentarios:

  1. Hola, nuevamente yo por acá, una preguntica, sólo mantienes información de la última página visitada? o mantienes todas las páginas visitadas?
    Gracias.

    ResponderEliminar
  2. hola Pierina Joplin

    puedes manter informacion de las paginas que quieras y sientas que aporta valor mantener estado, digo tampoco mantener estado de todo tendria sentido, pero donde la operacion del ir y volver es importante alli aplica

    en este caso esa clase Manager solo mantiene el de esa pagian en concreto

    pero si quieres mantener el de otras solo es cuestion de crearle su manager y eso es todo (aplciando esta misma tecnica)

    la informacion dentro del manager puede ser tan compleja como lo necesite, solo sera cuestion de modelar la entidad que guardas en la Session

    saludos

    ResponderEliminar
  3. Gracias.. Aunque no quede muy convencida.. :)

    ResponderEliminar
  4. Hola, he leido tus respuestas en foro de MSDN y veo que sabes bastante, yo publique uno recientemente no hayo que hacer, agradeceria mucho tus opiniones

    http://social.msdn.microsoft.com/Forums/es-ES/netfxwebes/thread/f4261091-d4e4-4a2d-8f7a-d8fc918a478e/#f4261091-d4e4-4a2d-8f7a-d8fc918a478e

    ResponderEliminar
  5. hola xxx

    he dado la respuesta en el foro, has probado lo que comento

    saludos

    ResponderEliminar
  6. Si Leando recien lo lei, ya conteste, gracias por el interes

    ResponderEliminar
  7. Una pregunta con respecto a las variables estáticas, en un principio esa solución me pareció práctica pero el problema me resultó que cuando mas de un usuario accedia a un Formulario con un grid X que se cargaba con los datos de estáticos, se mostraban en la computadora A y B lo que estaba visualizando la computadora C o en su defecto al inicializarla por la computadora A o B la computadora C perdia su información. No se si estaré haciendo algo mal o qué. Mi solución fue utilizar variables de sesión para no usar estas variables estáticas. Salu2 desde Ecuador

    ResponderEliminar
  8. hola Carlos Misael

    efectivamente definir una variable como static implica que esta actuara como el objeto Application de asp.net, o sea sera visible para el sitio completo y todos los usuario compatiran la misma info del mismo

    es por eso que si se quiere tener dato concretos por usuario se usa el objeto Session

    saludos

    ResponderEliminar
  9. Hola Leandro, tengo una aplicación en asp.net y utilizo variables de sesión pero después de cierto tiempo que deja la pagina abierta se pierde y marca error, para recuperar la información de las variables debo enviarlas a textos y después recuperarlas (session("variable")=me.textbox1.text)?

    ResponderEliminar
  10. hola HugoSoft

    eso es comun que suceda, las session tienen un tiempo de expiracion, el valor por defecto son 20 min, si durante este tiempo no realizaste ninguna accion que implique ir al servidor, la session se pierde

    deberias agregar validaciones para en caso de perder la seccion no falle la informacion que recuperas

    saludos

    ResponderEliminar
  11. Hola Leandro, el codigo esta muy bueno, pero tengo un problema que no consigo subsanar

    Tengo un area de carga de datos para el gridview, el cual posee un boton de consulta de articulos, cuando hago clic al bonton consulta de articulos me redirige a otra pagina, cuando vuelvo me trae los datos del articulo y los inserto en la grilla ingresando la cantidad y precio
    El problema es que no puedo aplicar tu codigo para mantener los datos ya ingresados a la grilla

    Agradeceria que me ayudes u orientes un poco en este caso

    ResponderEliminar
  12. hola Darkkatt

    no evaluaste en lugar de hacer un redirect a otra pagina para realizar la seleccion usar mejor un popup ?

    [ASP.NET] PopUp Edición - Usando Jquery UI Dialog

    no he implementado directo para completar campos pero la tecncia no deberia cambiar mucho

    con un popup evitarias perder la edicion del grid

    saludos

    ResponderEliminar
  13. Leandro, tendrías una versión de tu código pero para VB gracias

    ResponderEliminar
  14. hola

    la verdad no lo he implementado en vb.net

    aunque podrias ver de ayudarte con tool como estas

    Convert c#

    saludos

    ResponderEliminar
  15. Hola Leandro,
    era lo que estaba buscando pero me he dado cuenta de que si una vez tengo datos en la primera y segunda página si me voy a la primera y quito algún dato al volver a esa página me vuelve a poner el dato que he quitado.
    Que debería hacer?
    Un saludo,

    ResponderEliminar
  16. hola Encarni

    cuando cambias entre las paginas es que se realiza la operacion de volcado de los registros marcados, podrias poner en ese evento un breakpoint y evaluar si puntualmente se esta realziando esa actualizacion, y sino es que se conservan los datos marcados previamente

    verifica puntualmente donde se cruzan los datos usando linq y que esto sea efectivo
    pero para esto solo es cuestion de un breakpoint y ver que resuelve los datos inspeccionado las variables

    saludos

    ResponderEliminar
  17. hola, necesito hacer una agenda en asp.net, sin BD o sea temporal. Y tengo que dirigirme simpre entre las paginas de crear, buscar y visualizar contacto, lo que no sea es como y donde declarar una application o session. En que lugar y como

    ResponderEliminar
  18. hola Carolina

    si defines los datos en session estos solo seran vistos por el usuario que ingresa al sitio, cuando el usuario cierre el browser se perderan los datos

    en cambio si los declaras en el Application podran ser accedidos por diferentes usuario que ingresen, porque este objeto es comun al sitio

    el que mas se aproxima es el Application

    saludos

    ResponderEliminar
  19. Hola Leandro,
    Como se haria en el caso de tener mas de un DataKeysNames??

    mi caso es:
    DataKeyNames="IdOferta,CódigoArtículoProveedor,WebURL"
    siendo IdOferta la clave primaria...

    Saludos.

    ResponderEliminar
  20. hola Roberto

    analiza el final de este articulo

    [ASP.NET][GridView] - Como seleccionar una fila

    alli explico como lo harias

    saludos

    ResponderEliminar
  21. Hola Leandro, tengo una inquietud necesito mantener el contenido de una pagina que posee 4 cuadros de texto dos dropdownlist y dos checkbox como pasaría estos datos como parámetro a las funciones
    KeepProductInfo y RestoreProductInfo, ya que en tu ejemplo pasas el grid como parámetro y eso te permite manipular los objetos que el mismo encapsula pero en mi caso; te agradecería me ayudes con esta inquietud desde ya un abrazo y muchos exitos

    ResponderEliminar
  22. hola Omar

    podrias crear una clase que tenga las propeidades para recibir la info de estos controles
    entonces creas la instancia de esa clase le asignas los valores de los textbox

    esa clase podrias asignarla al objeto session para conservar los datos entre request

    si la idea es asignar los valores a los controles podrias pasar por parametro la instancia de la Page

    saludos

    ResponderEliminar
  23. Hola Leandro, consulta para poder obtener esta misma funcionalidad, pero no para una tabla, sino para un buscador, que cuenta con textbox y select....
    Lograr mantener los datos aunque se pinche en un link de una tabla ?

    Sldos ! espero me puedas ayudar.

    ResponderEliminar
  24. hola Nicky

    no se si entendi que involucra ese click en la tabla, o que relacion tiene ese textbox y select que mencionas

    pero si hay un gridview podrias mantener en session los datos para no perderlos cuando se aplique una nueva busqueda

    saludos

    ResponderEliminar
  25. Hola Leandro, de nuevo molestando:

    Apliqué tu código al pie de la letra para conservar el valor del ddl; al dar click en (x) número de paginación para así al regresar al número anterior (en el que me encontraba) se quedara el valor previamente elegido en el ddl, pero no aplicó tú código.

    Tienes alguna idea de cómo hacer esto?.

    De antemano, Gracias

    Saludos

    ResponderEliminar
  26. hola Aaron

    a que llamas que "no aplico mi codigo" ?
    si pones un breakpoint en el codigo no se detiene la ejecucion

    como que estaria conservando la seleccion sin tener que implementar ningun codigo adiconal?

    saludos

    ResponderEliminar
  27. me refiero a que tú código ya implementado en mi aplicación no funciona al querer actualizar el gridview de nuevo, no mantiene los valores elegidos anteriormente en los dropdownlist que existen dentro del gridview, por ejemplo:

    En la primera fila tengo un ddl en el cuál selecciono un valor "FRUTAS" por ejemplo. En el segundo selecciono "LEGUMINOZAS". Al cambiar de página (dentro del mismo gridView) no me guarda esos valores.

    Espero respuesta. Saludos

    ResponderEliminar
  28. hola Aaron

    pero si pones breakpoint e el codigo puedes evaluar que captura a deteccion del cambio de pagina y vuelca los valores seleccionados a la Session?

    sino los conservas como explico en el articulo esta claro que no va a funcionar
    para eso solo pon breakpoint en el codigo y valida por donde pasa la ejecucion

    saludos

    ResponderEliminar
  29. Creo que mi problema está en como cargo los valores del ddl, yo los cargo desde la propiedad del mismo dropdownlist que se encuentra dentro del gridView "ddl_load(object sender, eventargs e)".


    En qué difiere de la forma en la que tu lo haces?

    Gracias Maestro!

    ResponderEliminar
  30. hola Aaron

    no uses ningun load de ningun control

    usa el RowDataBound del gridview para acceder al combo que esta en esa row y asignarle los datos

    saludos

    ResponderEliminar
  31. Hola Leandro

    cada vez se torna más complejo, disculpa por la incertidumbre y por mi insistencia.

    me surgieron dos problemas;

    1. Al cargar los datos con el rowDataBound el ddl de la primera fila no carga los datos, a qué se debe?... este es el código

    protected void gridAcuerdos_RowDataBound(object sender, GridViewRowEventArgs e)
    {
    if (e.Row.RowType != DataControlRowType.DataRow)
    return;

    // SE CARGA EL COMBO DE ESTADOS

    DropDownList ddlAcuerdos = (DropDownList)e.Row.FindControl("ddlAcuerdos");

    String str = "SELECT * FROM comboBoxAcuerdos";
    SqlCommand comando = new SqlCommand(str, Conexion);
    SqlDataAdapter Da = new SqlDataAdapter();
    DataSet Ds = new DataSet();

    try
    {
    Conexion.Open();
    comando.ExecuteNonQuery();
    Da.SelectCommand = comando;
    Da.Fill(Ds, "estadoAcuerdo");
    ddlAcuerdos.DataSource = Ds;
    ddlAcuerdos.DataBind();
    }
    catch (SqlException sqlEx) // CAPTURA TODAS LAS EXCEPCIONES DE SQL
    {
    for (int i = 0; i < sqlEx.Errors.Count; i++)
    {
    errorMessages.Append("Index #" + i + "\n" +
    "Message: " + sqlEx.Errors[i].Message + "\n" +
    "Line Number: " + sqlEx.Errors[i].LineNumber + "\n" +
    "Source: " + sqlEx.Errors[i].Source + "\n" +
    "Procedure: " + sqlEx.Errors[i].Procedure + "\n");
    }
    Console.WriteLine(errorMessages.ToString());
    }
    catch (InvalidOperationException ex) // CAPTURA LA EXCEPCIÓN DE CONEXIÓN (SQLCONNECTION EXCEPTION)
    {
    Console.WriteLine("Excepción de conexión: " + ex.Message);
    }
    finally
    {
    Conexion.Close();
    }
    }




    2. Cuando cambio de paginación del gridView, me invoca un error el cual es el siguiente


    LA CADENA DE ENTRADA NO TIENE EL FORMATO CORRECTO.


    Línea 16: let valor = ((DropDownList)item.FindControl("ddlAcuerdos")).SelectedValue
    Línea 17: where !(valor == "0")
    Línea 18: select new ValorInfo()
    Línea 19: {
    Línea 20: Id = Convert.ToInt32(grid.DataKeys[item.RowIndex].Value),




    Este es el código del PageIndexChanging


    protected void gridAcuerdos_PageIndexChanging(object sender, GridViewPageEventArgs e)
    {
    Clases.ValorManager.KeepValorInfo((GridView)(sender));

    gridAcuerdos.PageIndex = e.NewPageIndex;
    //gridAcuerdos.DataSource = Session["acuerdos"];
    //gridAcuerdos.DataBind();
    cargarAcuerdos();
    }


    Gracias.

    Saludos

    ResponderEliminar
  32. hola Aaron

    1- has puesto un breakpoint en el evento rowDatabound para validar si ingresa con la primer row y si es asi que camino toma el codigo ?

    entiendo que esa primer row es un datarow por eso el if deberia pasarlo

    2- en esa paete del linq que muestras no veo ningun problema
    estas seguro que solo es es l linq que usas? porque alli falta definir

    podrias probar de hacer mas simple el linq a ver si pasa, por ejemplo solo dejar el id sin los demas campos, y luego al contrario quitar el id he ir poniendo los otros asi para determinar cual trae el problema

    saludos

    ResponderEliminar
  33. excelente, funciona!!

    muchas gracias por tu paciencia y sabiduría.

    Saludos

    ResponderEliminar
  34. Hola Leandro,

    hay una duda que me consterna demasiado, no he encontrado tema al respecto pero este es similar.

    Mi duda es la siguiente: habrá alguna manera de realizar una actualización a los datos de una celda de un gridview (agregar datos(texto) a una celda) pero sin borrar los datos que ya existe en esa celda?


    Te cuento que la modificación la realizo con un stored procedure que manda llamar a la función UPDATE, pero esta me cambia por completo el texto anterior por el texto nuevo y lo que deseo es agregar el texto nuevo al texto anterior.

    Nota: no se deben realizar los cambios desde el mismo gridview.


    Gracias.

    Saludos.

    ResponderEliminar
  35. hola Aaron

    pero podrias usar en el select

    UPDATE Tabla SET campo = campo + @param WHERE id = @id

    o sea desde el procedure unes lo que tenias a lo nuevo
    y si quieres despues actualizas el grid para reflejar la nueva info de esa celda

    saludos

    ResponderEliminar
  36. Estimado Leandro;
    me gustaría saber como puedo recuperar los datos de una lista(de una clase) ejemplo:
    List(Producto) LstProductos = new List(Productos)();
    (creado fuera del ambito del button)
    protected void Button3_Click(object sender, EventArgs e)
    {
    Productos prod = new Productos();
    prod.NombreProd = TextBox1.Text;
    prod.Categ = TextBox2.Text;
    LstProductos.Add(prod);
    }

    ...pero cuando quiero recuperar los elemntos de la lista solo muestra el ultimo

    Alguna sugerencia? gracias de antemano

    ResponderEliminar
  37. hola Axel

    es que en un entorno web no tienes estados por lo que al terminar el request la lista se destruye
    deberias usar el objeto session para conservar la lista

    protected void Button3_Click(object sender, EventArgs e)
    {
    var lista = Session["productos"] as List<Productos>;

    if(lista == null){
    lista = new List<Productos>();
    }

    var prod = new Productos(){
    NombreProd = TextBox1.Text;
    Categ = TextBox2.Text;
    };
    lista.Add(prod);

    Session["productos"] = lista;
    }

    saludos

    ResponderEliminar
  38. Estimado Leandro;

    Tenias toda la razón, me funcionó....
    te agradezco tu tiempo y disposición para responder...
    Muchas Gracias de verdad!!

    Axel

    ResponderEliminar
  39. hola leandro tengo un problema como le puedo hacer para que mi pagina de asp.net donde meto mis datos no se borren de la caja de texto cada vez que yo vaya a buscar un dato de otra pagina aspp.net

    ResponderEliminar
    Respuestas
    1. hola

      Si vas a nevagar de una pagina atra necesitara persistir los datos de forma temporal, podrias hacer usar el objeto Session

      Podrias crear una List<> de uan clase que definas (o quizas un datatable) para volcar los datos que quieres mantener, entonces navegas realizas alguna accion y al volver si detectas que la session tiene info vuelves a cargar la informacion

      Es lo mismo que planteo en este mismo articulos

      saludos

      Eliminar
  40. Hola Leandro.

    Tengo una pagina web en c# donde utilizó clases, con propiedades publicas y publicas staticas, donde guardo los valores que los usuarios vas ingresando y los conservo atravez de la navegación de paginas y seguir guardando información donde lo usuarios siguen ingresando información.

    Me surgió la luda al ver que tú guardas tus valores en session, cosa que yo no hago. ¿Esto me puede dar problemas por concurrencia a la pagina y mezclar la información de cada objeto creado por casa usuario a la pagina, o se crean objetos independientes para cada usuario, y no dar problemas que se cruce informacion de usuarios que se encuentren en la pagina al mismo tiempo?

    Agradecería mucho tu ayuda y comentarios.

    Saludos

    ResponderEliminar
    Respuestas
    1. hola
      En un ambiente web no deberias usar variables definida como static ya que estas se comportan como el objeto Application o sea la informacion que asignes sera accesible a nivel del sitio web, o sea por todos los usuario, es por eso que si queres conservar los datos individuales para cada usuario de us el objeto Session
      saludos

      Eliminar
    2. Hola Leandro.

      Cambien todo lo que utilizaba en variablesestaticas por el objeto Session, y una función que valida que la session siga viva, para evitar errores.

      Muchas gracias. Saludos

      Eliminar
  41. me











    Hola que tal, llevo dias intentando mantener el estado de mis checkbox en un gridview estoy usando vb.net, el resultado esque al paginar mi gridview se pierde la seleccion fde mis checkbox.
    Me puedes ayudar??

    ResponderEliminar
    Respuestas
    1. hola
      pero no puedes aplicar la tecnica que planteo en el articulo ? cuando paginas deberias mantener en session los valores de la paginas previas y restaurarlas cuando vuelvas a la pagina
      saludos

      Eliminar
  42. Hola quisiera realizar el mismo proceso de guardar información en la sesión, pero en mi caso seria de la siguiente manera:

    realizo la lectura de los datos de un archivos Excel, cualquier registro del archivo que no cumpla con las especificaciones se captura y lo guardo en memoria. mi pregunta es, yo puedo realizar los mismos pasos, crear una clase con los datos a guardar y luego asignar los objetos a la variable sesión para después recuperar esa información y mostrarlos en una grilla

    Muchas Gracias

    Agradezco sus respuestas

    ResponderEliminar
  43. Buenos días,
    Ojalá alguien pueda ayudarme.
    Estoy teniendo problemas a la hora de que el usuario se loguea en mi página, usé Server.Transfer("Consulta.aspx") que es la página que quiero se muestre luego de que la autenticación se realice y si se habilita la otra página pero cuando deseo consultar una información en esa pagina(es un reporte, uso un sp), me vuelve a enviar a la página de login

    ResponderEliminar