viernes, 20 de febrero de 2015

El Demo del Día: Graficar Datos en el Cliente en ASP.NET MVC con JSON, Canvas y JavaScript

Graficar Datos en el Cliente en ASP.NET MVC con JSON, Canvas y JavaScript

Después de un par de semanas sin publicar un demo, les presento un post muy interesante de como trabajar en forma desconectada en el cliente para realizar consultas y gráficos de datos en ASP.NET MVC para lo cual usaremos jQuery, JSON, Canvas y JavaScript.

Requerimiento

Se desea crear una aplicación web que permita consultar los productos por categoría y mostrar los datos mas gráficos de diferentes tipos: Barras, Columnas, Lineas y Pie, pero el requisito es que sea desconectada tanto del Servidor de Datos como del Servidor Web, es decir, el filtro para la consulta y el gráfico se hará en el cliente.

Solución

- Crear una aplicación en ASP.NET MVC con un método de acción que se conecte a la BD una sola vez y devuelva un objeto con 2 listas: categorías y productos.
- Devolver una vista al cliente con la lista de categorías y la lista de tipos de gráficos.
- Ni bien carga la vista en el cliente hacer una llamada asíncrona usando $.ajax de jQuery para llamar a un método de acción que devuelva la lista de productos en formato JSON.
- Cuando el usuario seleccione una Categoría o seleccione un Tipo de Gráfico se llamará a una función JavaScript que filtrará los datos por la categoría seleccionada y los presentará en una tabla además lo dibujará en un Canvas.

Crear el Procedimiento Almacenado en la Base de Datos de SQL Server

Para el ejemplo usaremos la Base de Datos Northwind de SQL Server, en la cual crearemos el siguiente Procedimiento almacenado:

Create Procedure [dbo].[uspCategoriesProductsListar]
As
Select CategoryID,CategoryName From Categories
Select ProductID,ProductName,SupplierID,CategoryID,UnitPrice,UnitsInStock From Products

Crear la Librería de Clases Entidades del Negocio

Crear la librería de clases llamada: "Northwind.Librerias.EntidadesNegocio" y agregar la clase "beCategoria.cs":

namespace Northwind.Librerias.EntidadesNegocio
{
    public class beCategoria
    {
        public int IdCategoria { get; set; }
        public string Nombre { get; set; }
    }
}

Agregar otra clase a la librería llamada "beProducto.cs":

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

Finalmente, agregar una clase llamada "beCategoriaProducto.cs" que agrupe las 2 listas:

using System;
using System.Collections.Generic;

namespace Northwind.Librerias.EntidadesNegocio
{
    public class beCategoriaProducto
    {
        public List<beCategoria> ListaCategoria { get; set; }
        public List<beProducto> ListaProducto { get; set; }
    }
}

Grabar y compilar la Librería de Entidades del Negocio.

Crear la Librería de Acceso a Datos

Crear la librería de clases llamada: "Northwind.Librerias.AccesoDatos", referenciar a la librería de clases de entidades creada anteriormente y agregar la clase "daCategoriaProducto.cs" y escribir el siguiente código:

using System;
using System.Collections.Generic; //List
using System.Data; //CommandType, CommandBehavior
using System.Data.SqlClient; //SqlConnection, SqlCommand, SqlDataReader
using Northwind.Librerias.EntidadesNegocio; //beCategoriaProducto, beCategoria, beProducto

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();
            if (drd != null)
            {
                lbeCategoria = new List<beCategoria>();
                beCategoria obeCategoria;
                int posIdCat = drd.GetOrdinal("CategoryID");
                int posNomCat = drd.GetOrdinal("CategoryName");
                while (drd.Read())
                {
                    obeCategoria = new beCategoria();
                    obeCategoria.IdCategoria = drd.GetInt32(posIdCat);
                    obeCategoria.Nombre = drd.GetString(posNomCat);
                    lbeCategoria.Add(obeCategoria);
                }
                obeCategoriaProducto.ListaCategoria = lbeCategoria;
                if (drd.NextResult())
                {
                    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);
                    }
                    obeCategoriaProducto.ListaProducto = lbeProducto;
                }
                drd.Close();
            }
            return (obeCategoriaProducto);
        }
    }
}

Grabar y compilar la librería de acceso a datos creada.

Crear la Librería de Reglas del Negocio

Crear la librería de clases llamada: "Northwind.Librerias.ReglasNegocio", referenciar a la librería de clases de entidades y también a la de acceso a datos, luego agregar la clase "brCategoriaProducto.cs" y escribir el siguiente código:

using System;
using System.Configuration; //ConfigurationManager
using System.Data.SqlClient; //SqlConnection
using System.Collections.Generic; //List
using Northwind.Librerias.EntidadesNegocio; //beCategoriaProducto
using Northwind.Librerias.AccesoDatos; //daCategoriaProducto

namespace Northwind.Librerias.ReglasNegocio
{
    public class brCategoriaProducto:brGeneral
    {
        public beCategoriaProducto obtenerListas()
        {
            beCategoriaProducto obeCategoriaProducto = null;
            string CadenaConexion = ConfigurationManager.ConnectionStrings["conNW"]
                                                     .ConnectionString;
            using (SqlConnection con = new SqlConnection(CadenaConexion))
            {
                try
                {
                    con.Open();
                    daCategoriaProducto odaCategoriaProducto = new daCategoriaProducto();
                    obeCategoriaProducto = odaCategoriaProducto.obtenerListas(con);
                }
                catch (SqlException ex)
                {
                    //grabarLog(ex);
                }
                catch (Exception ex)
                {
                    //grabarLog(ex);
                }
            } //con.Close(); con.Dispose();
            return (obeCategoriaProducto);
        }
    }
}

Nota: También es necesario hacer una referencia a la librería "System.Configuration" para leer la cadena de conexión definida en la aplicación.

Grabar la librería de reglas de negocio creada y compilarla.

Crear una Aplicación Web de ASP.NET MVC4 en C#

Seleccionar un nuevo proyecto de tipo: "Aplicación web de ASP.NET MVC4" y escribir como nombre "Canvas_GraficosDatos", luego seleccionar la opción "Vacio" y como motor de vista "Razor" y "Aceptar".

Crear un Controlador para el Producto

Clic derecho a la carpeta Controllers y seleccionar "Agregar" y luego "Controlador", llamarle al archivo "ProductoController.cs" y en opciones de plantillas dejarlo en plantilla vacía (Vaciar Controlador MVC). Luego escribir el siguiente código:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Northwind.LibBusEntities;
using Northwind.LibBusRules;

namespace HT03_Canvas_GraficosDatos.Controllers
{
    public class ProductoController : Controller
    {
        public ActionResult Lista()
        {
            List<string> tipoGrafico = new List<string>();
            tipoGrafico.Add("Barras");
            tipoGrafico.Add("Columnas");
            tipoGrafico.Add("Lineas");
            tipoGrafico.Add("Pie");
            ViewBag.Tipo = tipoGrafico;
            brCategoriaProducto obrCategoriaProducto = new brCategoriaProducto();
            beCategoriaProducto obeCategoriaProducto = obrCategoriaProducto.Listar();
            Session["Productos"] = obeCategoriaProducto.ListaProducto;
            return View(obeCategoriaProducto.ListaCategoria);
        }

        public JsonResult Filtro()
        {
            JsonResult rpta;
            List<beProducto> lbeProducto = (List<beProducto>)Session["Productos"];
            rpta = Json(lbeProducto, JsonRequestBehavior.AllowGet);
            return (rpta);
        }
    }
}

Crear una Hoja de Estilos para la Vista

Primero crear una carpeta llamada "Content", luego clic derecho "Agregar" y luego "Hoja de estilos" y como nombre llamarle "ACME.css" y escribir el siguiente código:

body {
    background-color:lightgray;
}
.Titulo {
    background-color:black;
    color:white;
    font-size:x-large;
    text-transform:uppercase;
}
.Subtitulo {
    background-color:white;
    color:black;
    font-size:large;
    text-transform:capitalize;
    font-weight:bold;
}
.AnchoTotal {
    width:100%;
}
.FilaCabecera {
    background-color:gray;
    color:white;
}
.FilaDatos {
    background-color:white;
    color:black;
}
.Cuadro {
    background-color:white;
    border-style:double;
}

Crear la Vista Productos para mostrar los datos

Ir al controlador y ubicarse sobre el método "Lista" (acción), clic derecho y seleccionar "Agregar vista", seleccionar el check "Crear una vista fuertemente tipada" y escribir el siguiente código:

@using Northwind.LibBusEntities
@model List<beCategoria>
@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Lista</title>
    <link href="~/Content/Styles/ACME.css" rel="stylesheet" />
    <script src="~/Scripts/jquery-1.7.1.min.js"></script>
    <script src="~/Scripts/Rutinas.js"></script>
</head>
<body>
    <div>
        <table class="AnchoTotal">
            <tr class="Titulo">
                <td colspan="2">
                   Graficar Datos en el Cliente en ASP.NET MVC con JSON, Canvas y JavaScript
                </td>
            </tr>
            <tr class="Subtitulo">
                <td colspan="2">Gráfico de Precios de Productos x Categoria</td>
            </tr>
            <tr>
                <td style="width:40%">
                    Selecciona una Categoria: @Html.DropDownList("idCategoria",
                    new SelectList(Model,"Codigo","Nombre"))
                </td>
                <td style="width:60%">
                    Selecciona el Tipo de Grafico: @Html.DropDownList("tipo",
                    new SelectList(ViewBag.Tipo))
                </td>
            </tr>
            <tr>
                <td style="vertical-align:top">
                    <table class="AnchoTotal">
                        <thead>
                            <tr class="FilaCabecera">
                                <td style="width:80%">Nombre del Producto</td>
                                <td style="width:20%">Stock</td>
                            </tr>
                        </thead>
                        <tbody id="tbProducto">
                        </tbody>
                    </table>
                </td>
                <td>
                    <canvas id="canvas" width="600" height="400" class="Cuadro"/>
                </td>
            </tr>
        </table>
    </div>
</body>
</html>

Crear el archivo JavaScript con el código cliente

Antes que nada crear una carpeta llamada "Scripts" y arrastrar del explorador de Windows el archivo de jQuery: "jquery-1.7.1.min.js", luego agregar un archivo de JavaScript llamado "Rutinas.js" y escribir el siguiente código:

$(document).ready(function () {
    var rpta;
    var cboCategoria = document.getElementById("idCategoria");    
    cboCategoria.onchange = function () { crearTablaGrafico(); }
    var cboTipo = document.getElementById("tipo");
    cboTipo.onchange = function () { crearTablaGrafico(); }
    $.ajax({
        type: "post",
        url: "/Producto/Filtro",
        contentType: "application/json;charset=utf-8",
        dataType: "json",
        success: exito,
        error: error
    });

    function crearTablaGrafico() {
        var idCategoria = cboCategoria.value * 1;
        var tbProducto = document.getElementById("tbProducto");
        var tipo = cboTipo.value;
        var contenido = "";
        var canvas = document.getElementById("canvas");
        var contexto = canvas.getContext("2d");
        if (contexto != null) graficar(rpta);
        function graficar(rpta) {
            //Crear Degradado
            var deg = contexto.createLinearGradient(0, 0, canvas.width, canvas.height);
            deg.addColorStop(1, "aqua");
            deg.addColorStop(0.1, "blue");
            //Dibujar Rectangulo Degradado
            contexto.fillStyle = deg;
            contexto.fillRect(0, 0, canvas.width, canvas.height);
            //Variables para los calculos
            var x = 10;
            var y = 20;
            var valor = 0;
            var escala = 0;
            var maximo = 0;
            var total = 0;
            if (tipo != "Pie") {
                maximo = calcularMaximo(idCategoria);
                if (tipo == "Barras") escala = Math.abs((canvas.width - 180) / maximo);
                else {
                    escala = Math.abs((canvas.height - 120) / maximo);
                    x = 50;
                }
            }
            else total = calcularTotal(idCategoria);
            var centroX = Math.floor(canvas.width / 2);
            var centroY = Math.floor(canvas.height / 2);
            var radio = Math.floor(canvas.width / 4);
            var anguloInicio = 0;
            var arco = 0;
            var anguloFin = 0;
            //Dibujar el grafico de acuerdo al tipo
            for (i = 0; i < rpta.length; i++) {
                if (rpta[i].IdCategoria == idCategoria) {
                    contenido += "<tr class='FilaDatos'><td>" + rpta[i].Nombre + "</td>" +
                        "<td class='Derecha'>" + rpta[i].PrecioUnitario + "</td></tr>";
                    switch(tipo)
                    {
                        case "Barras":
                            x = 10;
                            contexto.fillStyle = "white";
                            contexto.font = "10px arial";
                            contexto.fillText(rpta[i].Nombre, x, y);
                            x = 150;
                            valor = rpta[i].PrecioUnitario * escala;
                            contexto.fillStyle = "yellow";
                            contexto.fillRect(x, y - 10, valor, 10);
                            contexto.fillStyle = "white";
                            contexto.fillText(rpta[i].PrecioUnitario, x + valor + 5, y);
                            y = y + 20;
                            break;
                        case "Columnas":
                            contexto.save();
                            contexto.translate(x, canvas.height - 10);
                            contexto.rotate(-(Math.PI / 3));
                            contexto.fillStyle = "white";
                            contexto.fillText(rpta[i].Nombre, 0, 0);
                            contexto.restore();
                            contexto.fillStyle = "yellow";
                            valor = (canvas.height - 100 - (rpta[i].PrecioUnitario*escala));
                            contexto.fillRect(x, valor, 10, rpta[i].PrecioUnitario * escala);
                            contexto.fillStyle = "white";
                            contexto.fillText(rpta[i].PrecioUnitario, x, valor - 10);
                            x += 40;
                            break;
                        case "Lineas":
                            contexto.save();
                            contexto.translate(x, canvas.height - 10);
                            contexto.rotate(-(Math.PI / 3));
                            contexto.fillStyle = "white";
                            contexto.fillText(rpta[i].Nombre, 0, 0);
                            contexto.restore();                            
                            contexto.strokeStyle = "yellow";
                            contexto.lineWidth = 3;
                            if (x== 50) {
                                contexto.beginPath();
                                valor = Math.floor(canvas.height - 100 - (rpta[i].PrecioUnitario) * escala);
                                contexto.moveTo(x, valor);
                            }
                            valor = Math.floor(canvas.height - 100 - (rpta[i].PrecioUnitario * escala));
                            contexto.lineTo(x, valor);
                            contexto.stroke();                
                            contexto.fillStyle = "white";
                            contexto.fillText(rpta[i].PrecioUnitario, x, valor - 10);
                            x += 40;
                            break;
                        case "Pie":
                            arco=(rpta[i].PrecioUnitario * 2 * Math.PI) / total;
                            anguloFin = anguloInicio + arco;
                            contexto.save();
                            contexto.beginPath();
                            contexto.moveTo(centroX, centroY);                            
                            contexto.arc(centroX, centroY, radio, anguloInicio, anguloFin, false);
                            contexto.fillStyle = '#' + Math.floor(Math.random() * 16777215).toString(16);
                            contexto.fill();
                            contexto.closePath();
                            contexto.restore();                            
                            contexto.save();
                            contexto.translate(centroX, centroY);
                            contexto.rotate(anguloInicio);
                            x = Math.floor(canvas.width * 0.15) - 10;
                            y = Math.floor(canvas.height * 0.05);
                            contexto.fillStyle = "white";
                            contexto.fillText(rpta[i].PrecioUnitario, x, y);
                            contexto.restore();
                            anguloInicio = anguloFin;
                            break;
                    }
                }
                tbProducto.innerHTML = contenido;
            }
        }
    }

    function calcularTotal(idCategoria) {
        var total = 0;
        for (i = 0; i < rpta.length; i++) {
            if (rpta[i].IdCategoria == idCategoria) {
                total += rpta[i].PrecioUnitario;
            }
        }
        return total;
    }

    function calcularMaximo(idCategoria) {
        var max = 0;
        for (i = 0; i < rpta.length; i++) {
            if (rpta[i].IdCategoria == idCategoria) {
                if (rpta[i].PrecioUnitario>max) max = rpta[i].PrecioUnitario;
            }
        }
        return max;
    }

    function exito(data) {
        rpta = data;
        crearTablaGrafico();
    }

    function error(data) {
        alert(data.status + " - " + data.statusText);
    }
});

Nota: La función asociada al $(document).ready se ejecuta ni bien carga la pagina y se llama en forma asíncrona al método de acción "Filtro" que devuelve un JSON con la lista de productos y por JavaScript se crea el filtro, se muestra la tabla y el gráfico de acuerdo a la categoría seleccionada y al tipo de gráfico.

Modificar el archivo web.config para incluir la cadena de conexión

<configuration>
  <connectionStrings>
    <add name="conNW" providerName="SQLServer"
         connectionString="uid=UsuarioNW;pwd=123456;
         data source=DSOFT\Sqlexpress;database=Northwind"/>
  </connectionStrings>
  .....
</configuration>

Configurar el inicio y ejecutar la aplicación web

Para probar la aplicación web debemos configurar el inicio para lo cual nos vamos a la carpeta "App_Start" y abrimos el archivo "RouteConfig.cs" cambiando el nombre del controlador y la acción tal como sigue: controller = "Producto", action = "Lista".

Ejecución y Pruebas de la Aplicación Web ASP.NET MVC

Finalmente, grabar y pulsar F5 para ejecutar la aplicación, mostrándose el resultado similar a la siguiente figura:


Por defecto se muestra la categoría "Bebidas" y en el tipo de gráfico "Barras", cambiar el tipo de gráfico a "Columnas" y se verá la siguiente figura:


Probar ahora con el gráfico de "Lineas" y se mostrará algo similar a la siguiente figura:


Finalmente, cambiar el tipo de gráfico a "Pie" y se verá lo siguiente:


Comentario Final

En este post hemos visto como trabajar en forma desconectada, y también hemos aprendido a crear grillas (tablas) y gráficos (canvas) sin usar controles, sino mediante código JavaScript.

Además hemos hecho llamada asíncrona mediante Ajax de jQuery y retornado los datos como un arreglo en notación JavaScript (JSON).

Esta forma de trabajo (desconectada) es muy útil en aplicaciones móviles donde la conexión al servidor web es limitada, por lo cual una sola vez se trae los datos y todo se maneja en el cliente (Browser).

Este demo es uno de los tantos que se realiza en el programa Web Developer. Espero les sirva.

Descarga
DemoDia_GraficoDatosCliente

Entrenamiento - Nuevos Inicios y Charlas ISILTECH

Nuevos Inicios y Charlas ISILTECH

Nuevos Inicios ISILTECH

A los interesados en capacitarse en .NET y otros cursos de Desarrollo, ISILTECH está aperturando nuevos inicios entre ellos los de .NET que yo dictaré son:

1. Visual Studio 2013 Developer

Duración: 24 semanas (144 horas)
Horario: Martes y Jueves de 19:00 a 22:00 horas (Bloque B)
Inicio: Martes 7 de Abril
Lugar: ISIL San Isidro – Av. Salaverry 2625

Cursos del Programa
1. Desarrollo de Aplicaciones con .NET Framework 4.5.1.
2. Desarrollo de Aplicaciones con ADO .NET y POO
3. Desarrollo de Aplicaciones Windows Forms.
4. Desarrollo de Aplicaciones Web con ASP .NET Web Forms.
5. Desarrollo de Aplicaciones Web con ASP .NET MVC.
6. Desarrollo de Aplicaciones con Windows Presentation Foundation (WPF).
7. Desarrollo de Servicios con Windows Communication Foundation (WCF).
8. Desarrollo de Aplicaciones de Flujos de Trabajo con Windows Workflow Foundation (WWF).

Nota: Es recomendable que para llevar llevar este programa se tenga conocimientos básicos de .NET

2. NET Web Applications Developer

Duración: 20 semanas (120 horas)
Horario: Sábado de 08:00 a 14:00 horas
Inicio: 11 de Abril
Lugar: ISIL San Isidro – Av. Salaverry 2625

Cursos del Programa
Curso 1: Desarrollando Aplicaciones Web con ASP .NET MVC 5
Curso 2: Programando en el Cliente con Javascript, jQuery, AJAX y CSS3
Curso 3: Programando en el Cliente con HTML5
Curso 4: Desarrollando Aplicaciones Orientadas a Servicios con WCF y Web API
Curso 5: Desarrollando Aplicaciones Web Móviles con ASP .NET, HTML5 y jQuery

Nota: Es recomendable que para llevar este programa primero se haya llevado el PECI.NET

Mayor información sobre estos programas u otros inicios, les paso el Link
Calendario_PECI_2015_01

Charlas en ISILTECH para Desarrolladores

Además, para promocionar los inicios esta confirmada un día de charlas técnicas gratuitas, para lo cual tendrán que confirmar su participación con Marketing. El día será el Martes 3 de Marzo de 3:30 pm a 8:30 pm y los temas serán:

1. Tema: JavaScript en el cliente y en el servidor con Node.js
    Hora: 3:30 pm
    Expositor: David Chura Olazabal

2. Tema: Desarrollo de Software con enfoque en el valor de negocio y calidad usando métodos ágiles
     Hora: 4:30 pm
     Expositor: Guino Henostroza Macedo

3.  Tema: Desarrollo de Aplicaciones Web en C# con ASP.NET MVC, Razor, jQuery y AJAX
     Hora: 5:30 pm
     Expositor: Luis Dueñas Huaroto

4.  Tema: Cloud Computing con Amazon Web Services (AWS)
     Hora: 6:30 pm
    Expositor: Carlos Chávez Villafuerte

5. Tema: Desarrollo en Android
    Hora: 7:30 pm
    Expositor: David Chura Olazabal

El día Lunes estaré publicando el Banner y el mail a donde tienen que confirmar. La capacidad es limitada y será en el Auditorio de ISIL de San Isidro (Pershing con Salaverry).

Ya saben que ISILTECH tiene una oferta variada de programas para Desarrolladores, Buenos Instructores y lo mejor a un Precio Sin Competencia (con respecto al nivel y calidad).

Los esperamos.

PD: Los que ya llevaron algún programa PECI.NET conmigo pueden hacer sus comentarios para animar a las personas que todavía dudan donde inscribirse para capacitarse.

El Libro del Día: AngularJS Essentials

El Libro del Día: 2015-02-20

Titulo: AngularJS Essentials
Autor: Rodrigo Branas
Editorial: Packt
Nro Paginas: 180

Capítulos:
Chapter 1: Getting Started with AngularJS
Chapter 2: Creating Reusable Components with Directives
Chapter 3: Data Handling
Chapter 4: Dependency Injection and Services
Chapter 5: Scope
Chapter 6: Modules
Chapter 7: Unit Testing
Chapter 8: Automating the Workflow

Descarga:
AngularJS_Essentials