Requerimiento
Se desea mostrar una consulta jerárquica de productos por categoría en una grilla en ASP .NET. La consulta debe ser desconectada.
Solución
No existe un control Jerárquico que muestre varias columnas tipo TreeGrid. El control TreeView muestra solo un nodo y el control GridView no soporta jerarquía.
La solución es usar plantillas jerárquicas en cualquiera de los controles enlazados a datos: GridView, DataList o Repeater.
Nosotros elegimos el Repeater y crearemos una plantilla jerárquica, es decir un control Repeater con otro control Repeater dentro.
Nota: En este caso quitaremos el ViewState en los dos controles Repeater para disminuir el HTML enviado al cliente ya que los filtros se harán en el servidor pero se mostraran en el cliente con Javascript.
Crear el Procedimiento Almacenado en la Base de Datos de SQL Server
Create Procedure [dbo].[uspCategoriesProductsListar]
As
Select CategoryID,CategoryName From Categories
Select ProductID,ProductName,SupplierID,CategoryID,UnitPrice,UnitsInStock From Products
Crear una Librería de Clases con las Entidades del Negocio
Crear un proyecto de tipo Librería de Clases en C# llamado: "Northwind.Librerias.EntidadesNegocio" y modificar el código de la clase como sigue:
using System;
namespace Northwind.Librerias.EntidadesNegocio
{
public class beCategoria
{
public int IdCategoria { get; set; }
public string Nombre { get; set; }
}
}
Agregar una nueva clase al proyecto y llamarla "beProducto":
using System;
namespace Northwind.Librerias.EntidadesNegocio
{
public class beProducto
{
public int IdProducto { get; set; }
public string Nombre { get; set; }
public int IdProveedor { get; set; }
public int IdCategoria { get; set; }
public decimal PrecioUnitario { get; set; }
public short Stock { get; set; }
}
}
Agregar otra clase al proyecto y llamarla "beCategoriaProducto":
using System;
using System.Collections.Generic;
namespace Northwind.Librerias.EntidadesNegocio
{
public class beCategoriaProducto
{
public List<beCategoria> ListaCategorias { get; set; }
public List<beProducto> ListaProductos { get; set; }
}
}
Crear una Librería de Acceso a Datos
Crear un proyecto de tipo Librería de Clases en C# llamado: "Northwind.Librerias.AccesoDatos", primero hacer una referencia a la Librería de Entidades del Negocio y luego modificar el código de la clase como sigue:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using Northwind.Librerias.EntidadesNegocio;
namespace Northwind.Librerias.AccesoDatos
{
public class daCategoriaProducto
{
public beCategoriaProducto obtenerListas(SqlConnection con)
{
beCategoriaProducto obeCategoriaProducto = new beCategoriaProducto();
List<beCategoria> lbeCategoria = null;
List<beProducto> lbeProducto = null;
SqlCommand cmd = new SqlCommand("uspCategoriesProductsListar", con);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataReader drd = cmd.ExecuteReader(); //Varios Selects
if (drd != null)
{
//Llenar las Categorias
lbeCategoria = new List<beCategoria>();
int posIdCat = drd.GetOrdinal("CategoryID");
int posNomCat = drd.GetOrdinal("CategoryName");
beCategoria obeCategoria;
while (drd.Read())
{
obeCategoria = new beCategoria();
obeCategoria.IdCategoria = drd.GetInt32(posIdCat);
obeCategoria.Nombre = drd.GetString(posNomCat);
lbeCategoria.Add(obeCategoria);
}
//Avanzar al siguiente Select
if (drd.NextResult())
{
//Llenar los Productos
lbeProducto = new List<beProducto>();
int posIdProducto = drd.GetOrdinal("ProductID");
int posNombre = drd.GetOrdinal("ProductName");
int posIdProveedor = drd.GetOrdinal("SupplierID");
int posIdCategoria = drd.GetOrdinal("CategoryID");
int posPrecioUnitario = drd.GetOrdinal("UnitPrice");
int posStock = drd.GetOrdinal("UnitsInStock");
beProducto obeProducto;
while (drd.Read())
{
obeProducto = new beProducto();
obeProducto.IdProducto = drd.GetInt32(posIdProducto);
obeProducto.Nombre = drd.GetString(posNombre);
obeProducto.IdProveedor = drd.GetInt32(posIdProveedor);
obeProducto.IdCategoria = drd.GetInt32(posIdCategoria);
obeProducto.PrecioUnitario = drd.GetDecimal(posPrecioUnitario);
obeProducto.Stock = drd.GetInt16(posStock);
lbeProducto.Add(obeProducto);
}
}
drd.Close();
obeCategoriaProducto.ListaCategorias = lbeCategoria;
obeCategoriaProducto.ListaProductos = lbeProducto;
}
return (obeCategoriaProducto);
}
}
}
Crear una Librería de Reglas del Negocio
Crear un proyecto de tipo Librería de Clases en C# llamado: "Northwind.Librerias.ReglasNegocio", hacer una referencia a la Librería de Entidades del Negocio, a la Librería de Acceso a Datos y a System.Configuration y luego modificar el código de la clase como sigue:
using System;
using System.Configuration; //ConfigurationManager
namespace Northwind.Librerias.ReglasNegocio
{
public class brGeneral
{
//Propiedad
public string Conexion { get; set; }
//Constructor
public brGeneral()
{
Conexion = ConfigurationManager.ConnectionStrings["conNW"].ConnectionString;
}
}
}
Agregar otra clase llamada: "brCategoriaProducto:" y escribir el siguiente código:
using System;
using System.Collections.Generic; //List
using System.Data.SqlClient; //SqlConnection
using Northwind.Librerias.EntidadesNegocio; //beCategoriaProducto
using Northwind.Librerias.AccesoDatos; //daCategoriaProducto
namespace Northwind.Librerias.ReglasNegocio
{
public class brCategoriaProducto:brGeneral
{
public beCategoriaProducto obtenerListas()
{
beCategoriaProducto obeCategoriaProducto = null;
using (SqlConnection con = new SqlConnection(Conexion))
{
try
{
con.Open();
daCategoriaProducto odaCategoriaProducto = new daCategoriaProducto();
obeCategoriaProducto = odaCategoriaProducto.obtenerListas(con);
}
catch (SqlException ex)
{
//Capturar el error y grabar un Log
}
} //con.Close(); con.Dispose(); con = null;
return (obeCategoriaProducto);
}
}
}
Crear la Aplicación Web en ASP .NET Web Form
Crear un "Nuevo Sitio web vacío de ASP .NET" en C# llamado "Demo15" y crear las siguientes carpetas:
- Estilos: Para el archivo de hoja de estilo (CSS).
- Imagenes: Conteniendo 2 archivos: Contraer.png y Expandir.png
- Paginas: Para contener la pagina de lista de empleados.
Crear el Archivo de Hoja de Estilo (CSS)
Seleccionar la carpeta "Estilos" y agregar un archivo de hoja de estilos con el nombre de: "ACME.css" y modificar el código como sigue:
background-color:aqua;
}
.Titulo {
background-color:black;
color:white;
text-transform:uppercase;
font-size:xx-large;
font-weight:bold;
}
.Subtitulo {
background-color:white;
color:blue;
text-transform:capitalize;
font-size:x-large;
font-weight:bold;
}
Crear la Pagina ASP .NET como un Formulario Web Form
Seleccionar la carpeta "Paginas" y agregar un Formulario Web Form llamado: "CategoriaProductos.aspx", para empezar a diseñar la pagina hay que hacer referencia a las Librerías de Negocio (que copia todas sus dependencias).
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CategoriaProductos.aspx.cs" Inherits="Paginas_CategoriaProducto" %>
<%@ Import Namespace="Northwind.Librerias.EntidadesNegocio" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<link href="../Estilos/ACME.css" rel="stylesheet" type="text/css" />
<script>
function mostrarProductos(img, indice) {
var fila = document.getElementById("fila" + indice);
if (img.title == "Expandir") {
fila.style.display = "table-row";
img.src = "../Imagenes/Contraer.png";
img.title = "Contraer";
}
else {
fila.style.display = "none";
img.src = "../Imagenes/Expandir.png";
img.title = "Expandir";
}
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<table style="width:100%">
<tr>
<td class="Titulo">Plantillas Jerarquicas en el Repeater</td>
</tr>
<tr>
<td class="Subtitulo">Consulta de Productos x Categoria</td>
</tr>
<tr>
<td>
<asp:Repeater ID="rpCategoria" EnableViewState="false" runat="server">
<HeaderTemplate>
<table style="width:100%">
<tr style="background-color:blue;color:white">
<td style="width:10%">Código</td>
<td style="width:90%">Nombre de la Categoria</td>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr style="background-color:white;color:blue">
<td>
<img id="imgSimbolo" alt="" title="Expandir"
src="../Imagenes/Expandir.png"
width="20" height="20" style="cursor:pointer"
onclick="mostrarProductos(this,<%#Container.ItemIndex%>);"/>
<%#((beCategoria)Container.DataItem).IdCategoria%>
</td>
<td>
<%#((beCategoria)Container.DataItem).Nombre%>
</td>
</tr>
<tr id="fila<%#Container.ItemIndex%>" style="display:none">
<td></td>
<td>
<asp:Repeater ID="rpProducto" DataSource="<%#filtrarProductos
(((beCategoria)Container.DataItem).IdCategoria)%>"
EnableViewState="false" runat="server">
<HeaderTemplate>
<table style="width:100%">
<tr style="background-color:red;color:white">
<td style="width:20%">Código</td>
<td style="width:40%">Nombre del Producto</td>
<td style="width:20%">Precio</td>
<td style="width:20%">Stock</td>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr style="background-color:white;color:red">
<td><%#((beProducto)Container.DataItem).IdProducto%></td>
<td><%#((beProducto)Container.DataItem).Nombre%></td>
<td><%#((beProducto)Container.DataItem).
PrecioUnitario.ToString("n2")%></td>
<td><%#((beProducto)Container.DataItem).Stock%></td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
</td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
</td>
</tr>
</table>
</div>
</form>
</body>
</html>
Nota: Inicialmente los controles Repeaters de productos creados por cada categoría están ocultos (display:none), pero por código Javascript lo mostramos usando: display:"table-row". No usamos display:inline ya que no se vería bien en algunos navegadores, en cambio, usando "table-row" se muestra sin problemas en todos los navegadores.
El diseño de la pagina se mostrará como en la siguiente figura:
Escribir el siguiente código C# en la página:
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Northwind.Librerias.EntidadesNegocio;
using Northwind.Librerias.ReglasNegocio;
public partial class Paginas_CategoriaProducto : System.Web.UI.Page
{
private List<beProducto> lbeProducto;
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
brCategoriaProducto obrCategoriaProducto = new brCategoriaProducto();
beCategoriaProducto obeCategoriaProducto = obrCategoriaProducto.obtenerListas();
lbeProducto = obeCategoriaProducto.ListaProductos;
rpCategoria.DataSource = obeCategoriaProducto.ListaCategorias;
rpCategoria.DataBind();
}
}
protected List<beProducto> filtrarProductos(int idCategoria)
{
return (lbeProducto.FindAll(x => x.IdCategoria.Equals(idCategoria)));
}
}
Nota: En el evento load se carga las 2 listas de categorías y productos, luego se enlaza las categorías al primer control Repeater y el segundo se enlaza a una función "filtrarProductos" que solo selecciona las productos de cada categoría.
Modificar el Archivo Web.Config para especificar la cadena de conexión a Northwind
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="conNW" providerName="SQLServer" connectionString="uid=UsuarioNW;pwd=123456;
data source=DSOFT\Sqlexpress; database=Northwind"/>
</connectionStrings>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
</configuration>
Probar la Pagina Web
Guardar el Sitio Web, clic derecho a la Pagina "CategoriaProductos.aspx" y seleccionar "Ver en el explorador".
Desplegar cada código de categoría dando clic a la imagen de Expandir (+) y se mostrarán los productos de dicha categoría y para ocultarla usar la imagen de Contraer (-).
Comentario Final
En este post hemos visto como crear una presentación jerárquica usando plantillas en el control Repeater. Hemos trabajado solo con 2 niveles pero podría ser de varios niveles. Como comentario final, si aprendemos bien a usar plantillas de datos (Data Templates) en cualquier control web (enlazado a datos) podemos darle la apariencia que deseemos, sin necesidad de tener un control especifico para cada funcionalidad.
Espero les sirva y será hasta la siguiente semana que veremos como trabajar con el GridView: Paginar, Ordenar, Filtrar en las cabeceras, Pies, etc.
Descarga:
Demo15_Plantilla_Jerarquica_Repeater