jueves, 14 de agosto de 2014

El Demo del Día: Plantilla Jerárquica con el Control Web Repeater

Plantilla Jerárquica con el Control Web Repeater

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);
        }
    }
}

Nota: La clase "brCategoriaProducto" hereda de la clase "brGeneral" la cadena de conexión para no pasarla como parámetro en cada método de la clase o en el constructor de cada clase se hace una sola vez.

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:

body {
    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%>);"/>
                                    &nbsp;&nbsp;
                                    <%#((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




El Libro del Día: Pro Silverlight 5 in C#

El Libro del Día: 2014-08-14

Titulo: Pro Silverlight 5 in C# (4th Edition)
Autor: Matthew MacDonald
Editorial: Apress
Nro Paginas: 970

Capítulos:
Chapter 1: Introducing Silverlight
Chapter 2: XAML
Chapter 3: Layout
Chapter 4: Dependency Properties and Routed Events
Chapter 5: Elements
Chapter 6: The Application Model
Chapter 7: Navigation
Chapter 8: Shapes and Transforms
Chapter 9: Brushes, Bitmaps, and Printing
Chapter 10: Animation Basics
Chapter 11: Advanced Animation
Chapter 12: Sound, Video, and Deep Zoom
Chapter 13: Silverlight 3D
Chapter 14: Styles and Behaviors
Chapter 15: Control Templates
Chapter 16: Multithreading
Chapter 17: Browser Integration
Chapter 18: Out-of-Browser Applications
Chapter 19: ASP.NET Web Services
Chapter 20: Data Binding
Chapter 21: Data Controls
Chapter 22: File Access
Chapter 23: Networking

Descarga:
Pro_Silverlight_5_C#