Mostrando las entradas con la etiqueta WinForms. Mostrar todas las entradas
Mostrando las entradas con la etiqueta WinForms. Mostrar todas las entradas

viernes, 31 de marzo de 2023

Entrenamiento - Curso de Programación para Principiantes en C#

 Curso de Programación para Principiantes en C#

1. Fecha de Inicio: Miércoles 3 de Mayo a las 7:00 pm

2. Modalidad: Virtual Online

3. Horario: Miércoles y Viernes de 7:00 pm a 11:00 pm

4. Pago Mensual: 100 soles (USD 30)

5. Duración: 6 meses

6. Contenido

- 2 meses de Aplicaciones de Consola

- 2 meses de Aplicaciones de WinForms

- 2 meses de Aplicaciones Web con ASP.NET

7. Primera Charla Gratuita Informativa: Miércoles 5 de Abril

8. Registro para la Charla Gratuita:

- Email: Luis.duenash@gmail.com

- Asunto: Charla del Curso de Programación para Principiantes en C#

- Contenido

  -) Nombre Completo

  -) DNI

  -) Ciudad / Pais

  -) Edad

  -) Estudios

  -) Ocupación

martes, 7 de diciembre de 2021

Entrenamiento - Programación de Cursos del 2022

Programación de Cursos del 2022

Para los interesados en llevar Cursos el próximo año, les dejo la Programación Anual 2022.

Curso: App Móviles con Android

Horario: Martes y Jueves de 7 a 11 pm

Meses: Enero - Febrero (2 meses)

Costo: 250 soles mensual (USD 70 para No Peruanos)

1. Actividades, Intentos y Navegación

2. Interfaz de usuario

3. Animaciones, Imágenes y Gráficos

4. Audio y Video

5. Tareas Asíncrona

6. Datos y Archivos

7. Ubicación de Usuario

8. Cámara


Curso: Integración de Aplicaciones en Tiempo Real con Servicios Web, Web Sockets y Clientes WinForms, Web MVC y Android

Horario: Martes y Jueves de 7 a 11 pm

Meses: Marzo - Abril (2 meses)

Costo: 250 soles mensual (USD 70 para No Peruanos)

Aplicaciones o Casos de Uso

1. Chat o Mensajes

2. Manejo de Documentos

3. Registro de Incidentes

4. Registro de Asistencias

5. Reservas de Mesas en Restaurantes

6. Streaming Video en Tiempo Real


Programa Anual Virtual de Desarrollo en Python (PAVDP)

Duración: 12 meses

Módulos: 3

Modulo 1: Desarrollo de Aplicaciones Comerciales en Python

Horario: Sábados de 4 a 11 pm

Meses: Enero - Abril (4 meses)

Costo: 250 soles mensual (USD 70 para No Peruanos)

1. Lenguaje Python & Librerías Estándar

2. Aplicaciones Windows con PyQT: PyOdbc, Pandas, PIL

3. Aplicaciones Web con Django

4. Servicios Web con Django

5. Web Sockets con Python


Modulo 2: Desarrollo de Aplicaciones de Procesamiento de Datos

Horario: Sábados de 4 a 11 pm

Meses: Mayo - Agosto (4 meses)

Costo: 250 soles mensual (USD 70 para No Peruanos)

1. Lenguaje Python & Librerías Estándar

2. Trabajo con Vectores: NumPy

3. Procesamiento de Datos: Pandas

4. Visualización de Datos: Matplotlib


Modulo 3: Desarrollo de Aplicaciones de Machine Learning con Python

Horario: Sábados de 4 a 11 pm

Meses: Setiembre - Diciembre (4 meses)

Costo: 250 soles mensual (USD 70 para No Peruanos)

1. Machine Learning con Python: Scikit-Learn, Tensorflow, Pytorch

2. Deep Learning con Python: Keras, Tensorflow, Pytorch

3. Computer Vision con Python: OpenCV, Tensorflow, Pytorch

4. Natural Language Processing: NLTK, Tensorflow, Pytorch


Los interesados deberán enviar un mail a Luis.duenash@gmail.com indicando lo siguiente:

Asunto: Curso de XXXXX (Android y/o Python)

Nombre Completo: XXXXXXXXXXXXX

Documento de Identidad: XXXXXX

Lugar de Procedencia: Ciudad / Pais

Ha llevado Cursos antes con Lduenas: Si / No


Nota: Los que lleven ambos cursos pagaran 400 soles (USD 120 para No Peruanos) en vez de 500 soles.

lunes, 13 de mayo de 2019

Entrenamiento - Segundo Curso de C# WinForms

Segundo Curso de C# WinForms con Arquitectura Sin Capas con Cadenas

Contenido del Curso
Parte 1: Estructuras de Datos y Algoritmos en .NET
01. Introducción a .NET Framework: BCL y CLR
02. El Lenguaje C#: Estructuras de Control de Flujo
03. Estructuras de Datos en .NET 1: Arreglos, Listas, Pilas y Colas
04. Estructuras de Datos en .NET 2: Enumeraciones, Diccionarios, Arboles
05. Algoritmos en .NET Parte 1: Búsqueda y Ordenación de Datos
06. Algoritmos en .NET Parte 2: Cifrado y Compresión de Datos
Parte 2: Acceso a Datos con ADO.NET en WinForms
07. ADO.NET: Conexión, Comandos y Readers
08. Consultas de Datos: Filtros y Búsquedas
09. Mantenimiento de Datos y Cabecera-Detalle
10. Reportes, Gráficos, Impresiones, Exportaciones (Texto, Word, Excel y PDF)
11. Creación de Librerías de Controles WinForms
Parte 3: Programación Distribuida en .NET
12. Creando un Servicio WCF y Clientes WinForms
13. Creando un Servicio MVC (HTTP) y Clientes WinForms
14. Creando un Servidor Web Sockets y Clientes WinForms
15. Creando un Servidor HTTP y Clientes WinForms
16. Creando un Servidor TCP y Clientes WinForms

Materiales del Curso
- Documentación en PDFs de Microsoft Docs
- Libros en PDF de C# y .NET
- Videos de Cursos y Eventos de Microsoft
- Demos y Videos del Curso

Horario
- Miércoles y Viernes de 7:00 pm a 10:00 pm

Fecha de Inicio
- Miércoles 22 de Mayo del 2019

Duración del Curso
- Total: 72 horas
- Tiempo: 3 meses
- Frecuencia: 6 horas semanales (24 horas mensuales)

Inversión
- Alumnos: 250 nuevos soles el mes
- Alumnos que actualmente estan llevando Sábados y/o Domingos: 200 soles el mes

Forma de Pago
- Depósito en el Banco BCP (se enviará el número de cuenta a los que envíen su correo)

Contacto
Los interesados enviar un mail a Luis.duenash@gmail.com con los siguientes datos:
Asunto: Curso C# Winforms Miércoles y Viernes
Contenido del Mail:
- Nombre Completo
- Lugar de Trabajo o Estudio
- Tipo de Alumno: Nuevo / Antiguo ISIL / Antiguo Talleres

Forma de Registro
- Enviar su mail indicando los datos solicitados
- Se le enviará el número de cuenta BCP
- Realizar el depósito del mes y enviar constancia (foto o voucher)
- Se le enviará la dirección exacta

Lugar del Curso
Será cerca a la Av Canada con Av Rosa Toro, se le enviará la dirección exacta a todos los registrados una vez realizado el pago al Banco.

viernes, 31 de marzo de 2017

Nuevos Demos de WinForms

Nuevos Demos de WinForms - Crear Utilitarios de Windows en .NET

A partir de la próxima semana estaré publicando en este Blog una Serie de Demos llamada: "Crear Utilitarios de Windows en .NET" donde explicaré como crear los utilitarios mas populares de Windows en C#, entre ellos:

- El Bloc de Notas (Notepad)
- El Explorador de Windows (Explorer)
- El MS Paint
- La Calculadora (Calc)
- El Registro de Windows (RegEdit)
- Un Chat con Archivos Compartidos (en tiempo real)
- Un Explorador de SQL Server
- Un Generador de Código MVC, etc

Cada Utilitario vendrá con lo siguiente:
1. La explicación del código
2. El código para descargar
3. El Video (en Youtube) para ver paso a paso cómo fue construido (en menos de 30 minutos)

Los que desean cualquier otro Programa (Utilitario) popular pueden enviar sus sugerencias a: Luis.duenash@gmail.com

Esta publicación será una vez por semana, pero además volveremos a subir mas Libros, publicar mas Personajes importantes del mundo del desarrollo, etc.

Saludos a todos los visitantes.

viernes, 25 de septiembre de 2015

Entrenamiento - Segundo Modulo del .NET Developer

Segundo Modulo del .NET Developer

Estimados Visitantes del Blog y Alumnos, queria comunicar a los interesados el inicio del Segundo Módulo del Programa .NET Developer que se realiza los días Sábados de 3:00 pm a 10:00 pm, en el cual se ha realizado los siguientes cambios:

Modulo 1: Windows Developer (48 horas en 2 meses)
Curso 01: .NET Framework (18 horas)
Curso 02: ADO .NET (18 horas)
Curso 03: WinForms (18 horas)

Modulo 2: Web Developer (96 horas en 4 meses)
Curso 04: ASP.NET Web Forms + JavaScript (24 horas)
Curso 05: ASP.NET MVC + JavaScript (24 horas)
Curso 06: HTML5 + JavaScript (24 horas)
Curso 07: Servicios (WCF y Web API) (24 horas)

Modulo 3: Mobile Developer (72 horas en 3 meses)
Curso 08: Responsive Web Designe (CSS3+HTML5+MVC) (18 horas)
Curso 09: jQuery Mobile (18 horas)
Curso 10: Xamarin Forms iOS (18 horas)
Curso 11: Xamarin Forms Android (18 horas)

Mañana Sábado 26 de Setiembre estaremos finalizando el primer módulo y el Sábado 10 de Octubre estaremos iniciando el segundo módulo con WebForms pero visto de forma diferente, sin usar Formularios ni Controles WebForms, solo HTML y JavaScript, mediante llamadas asíncronas con XHR y usando Serialización de Cadenas.


Ya saben que el costo es de 360 soles mensuales y los interesados tienen que enviar un mail de confirmación a Luis.duenash@gmail.com para enviarles la dirección exacta.

En la parte Web enseñaremos como optimizar el envio de imagenes, subir archivos grandes al servidor, hacer todas las operaciones con los datos del lado del cliente: filtro, paginación, ordenación, exportación, etc. Además de mostrar cambios en tiempo real usando web sockets, HTML5, etc.

viernes, 19 de junio de 2015

El Demo del Día: Aumentar Funcionalidad en una Aplicación WinForms usando Reflection (Sin Recompilar)

Aumentar Funcionalidad en una Aplicación WinForms usando Reflection (Sin Recompilar)

El Problema de la Actualización de Aplicaciones

Cada vez que se crea una Aplicación Windows se distribuye el ejecutable en las diferentes PCs de los usuarios que van a usar la aplicación.

Solución Actual al Problema de la Actualización de Aplicaciones

Si deseamos realizar cambios, sobre todo aumentar nueva funcionalidad, existen 2 soluciones comunes:

1. Recuperar los fuentes (código fuente), aumentar la funcionalidad, recompilar y volver a distribuir.

Problema: Si no se tiene los fuentes, porque muchas veces se compra solo el ejecutable (por dismunir costos) no se podría usar esta técnica.

2. Ejecutar desde el Módulo Principal los demás Módulos, es decir una aplicación preparada para ejecutar cualquier exe (los módulos)

Problema: No hay un control interno de las ventanas de los módulos que se ejecuten, por ejemplo que aparezcan dentro del MDI Principal, se puedan organizar y sobre todo el intercambio de datos.

Solución Propuesta al Problema de la Actualización de Aplicaciones

Construir un Sistema Principal que pueda ejecutar cualquier módulo, ya sea una aplicación o una librería, para eso usaremos Reflection.

Las 3 tareas a realizar desde el menú principal del sistema serían:
1. Ejecutar formularios (clases) desde cualquier ensamblado
2. Ejecutar procesos (exe) como por ejemplo utilitarios del sistema
3. Ejecutar métodos (funciones públicas) de cualquier clase de cualquier ensamblado

Con esta aproximación, para aumentar nueva funcionalidad solo basta hacer 2 cosas:
1. Aumentar en tablas (en nuestro caso un archivo de texto) las nuevas opciones del menú
2. Copiar el o los archivos exe (módulos de procesos, usables también independientemente) y archivos dll (módulos de librerías, usables solo por el módulo principal u otras aplicaciones).

Componentes del Demo

Este demo tiene los siguientes componentes (archivos):
1. Menus.txt: Archivo de texto con las opciones del menú principal a cargar.
2. SistemaPrincipal.exe: La aplicación principal (WinForms) que tiene un MDI con el código que permite cargar los menús y a través de este abrir cualquier formulario, ejecutar procesos y ejecutar métodos.
3. ModuloProcesos.exe: Una aplicación WinForms que contiene 2 formularios que van a aumentarse al Sistema Principal. También puede ejecutarse por si sola y muestra un formulario con dos opciones.
4. ModuloReportes.dll: Una librería conteniendo 2 formularios y una clase Rutinas con un método.

Crear la Aplicación Windows Forms con el Sistema Principal

Abrir Visual Studio y crear una aplicación Windows Forms en C# llamada "SistemaPrincipal", cambiarle de nombre al formulario por "frmPrincipal", configurar su propiedad IsMdiContainer en true y realizar el diseño similar a la figura mostrada:


Agregar 4 formularios al proyecto (configurar su propiedad BackColor en Red), con los siguientes nombres:
- frmConProdCat.cs: Propiedad Text en "Consulta de Productos por Categoria"
- frmConProdProv.cs: Propiedad Text en "Consulta de Productos Proveedor"
- frmManCliente.cs: Propiedad Text en "Mantenimiento de Clientes"
- frmManEmpleado.cs: Propiedad Text en "Mantenimiento de Empleados"

Crear una Clase Entidad de Negocio para el Menú

Agregar una clase al proyecto llamada: "beMenu" y escribir el siguiente código:

using System;
namespace SistemaPrincipal
{
    public class beMenu
    {
        public string IdMenu { get; set; }
        public string Titulo { get; set; }
        public string Ensamblado { get; set; }
        public string Accion { get; set; }
        public string IdPadre { get; set; }
    }
}

Crear una Clase Agente de Servicio para el Menú

Agregar una clase al proyecto llamada: "saMenu" y escribir el siguiente código:

using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
namespace SistemaPrincipal
{
    public class saMenu
    {
        public List<beMenu> Listar(string NombreArchivo)
        {
            List<beMenu> lbeMenu = null;
            if (File.Exists(NombreArchivo))
            {
                using (StreamReader sr = new StreamReader(NombreArchivo, Encoding.Default))
                {
                    string[] menu;
                    lbeMenu = new List<beMenu>();
                    beMenu obeMenu;
                    while (!sr.EndOfStream)
                    {
                        menu = sr.ReadLine().Split(',');
                        obeMenu = new beMenu();
                        obeMenu.IdMenu = menu[0];
                        obeMenu.Titulo = menu[1];
                        obeMenu.Ensamblado = menu[2];
                        obeMenu.Accion = menu[3];
                        obeMenu.IdPadre = menu[4];
                        lbeMenu.Add(obeMenu);
                    }
                }
            }
            return (lbeMenu);
        }
    }
}

Nota: Para los que no conocen la terminología distribuida, si la clase es de datos es da: DataAccess, pero si trabajamos con recursos que no son bases de datos, como archivos de texto, la clase es sa: ServiceAgents (Agentes de Servicio).

Crear una Clase Regla de Negocio para el Menú

Agregar una clase al proyecto llamada: "brMenu" y escribir el siguiente código:

using System;
using System.Collections.Generic;
namespace SistemaPrincipal
{
    public class brMenu
    {
        public List<beMenu> Listar()
        {
            List<beMenu> lbeMenu = null;
            saMenu osaMenu = new saMenu();
            lbeMenu = osaMenu.Listar("Menus.txt");
            return (lbeMenu);
        }
    }
}

Escribir Código para el Formulario frmPrincipal

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Reflection;
using System.IO;

namespace SistemaPrincipal
{
    public partial class frmPrincipal : Form
    {
        List<beMenu> lbeMenu;

        public frmPrincipal()
        {
            InitializeComponent();
        }

        private void llenarMenus(object sender, EventArgs e)
        {
            brMenu obrMenu = new brMenu();
            lbeMenu = obrMenu.Listar();
            List<beMenu> lbeRaiz = lbeMenu.FindAll(x=>x.IdPadre.Equals("0"));
            ToolStripMenuItem mnuRaiz;
            foreach(beMenu obeMenu in lbeRaiz)
            {
                mnuRaiz=new ToolStripMenuItem(obeMenu.Titulo);
                mnuRaiz.Tag = String.Format("{0}|{1}", obeMenu.Accion, obeMenu.Ensamblado);
                if (!obeMenu.Accion.Equals("")) mnuRaiz.Click += new EventHandler(ejecutarAccion);
                mnuPrincipal.Items.Add(mnuRaiz);
                crearSubMenu(obeMenu, mnuRaiz);
            }
        }

        private void crearSubMenu(beMenu obeMenuPadre,ToolStripMenuItem mnuPadre)
        {
            List<beMenu> lbeMenuHijos =
            lbeMenu.FindAll(x=>x.IdPadre.Equals(obeMenuPadre.IdMenu));
            ToolStripMenuItem mnuHijo;
            foreach (beMenu obeMenuHijo in lbeMenuHijos)
            {
                mnuHijo = new ToolStripMenuItem(obeMenuHijo.Titulo);
                mnuHijo.Tag = String.Format("{0}|{1}", obeMenuHijo.Accion,
                obeMenuHijo.Ensamblado);
                if (!obeMenuHijo.Accion.Equals("")) mnuHijo.Click +=
                new EventHandler(ejecutarAccion);
                mnuPadre.DropDownItems.Add(mnuHijo);
                crearSubMenu(obeMenuHijo, mnuHijo);
            }
        }

        private void ejecutarAccion(object sender,EventArgs e)
        {
            ToolStripMenuItem mnu = (ToolStripMenuItem)sender;
            string[] opciones = mnu.Tag.ToString().Split('|');
            string accion = opciones[0];
            string nombreEnsamblado = opciones[1];
            if (accion.StartsWith("frm"))
            {
                Assembly ensamblado = Assembly.LoadFrom(nombreEnsamblado);
                if (ensamblado != null)
                {
                    Type tipo = ensamblado.GetType(String.Format("{0}.{1}",
                    Path.GetFileNameWithoutExtension(nombreEnsamblado),accion));
                    if (tipo != null)
                    {
                        object obj = Activator.CreateInstance(tipo);
                        if (obj != null)
                        {
                            Form frm = (Form)obj;
                            frm.MdiParent = this;
                            frm.Show();
                        }
                    }
                }
            }
            else
            {
                if (accion.EndsWith(".exe"))
                {
                    Process.Start(accion);
                }
                else
                {
                    opciones = accion.Split('.');
                    accion = opciones[0];
                    string clase = opciones[1];
                    if (nombreEnsamblado.Equals("SistemaPrincipal.exe") &&
                    clase.Equals("frmPrincipal"))
                    {
                        MethodInfo mi = this.GetType().GetMethod(accion);
                        var rpta = mi.Invoke(this, null);
                        if (rpta != null) MessageBox.Show(rpta.ToString());
                    }
                    else
                    {
                        Assembly ensamblado = Assembly.LoadFrom(nombreEnsamblado);
                        if (ensamblado != null)
                        {
                            Type tipo = ensamblado.GetType(String.Format("{0}.{1}",
                            Path.GetFileNameWithoutExtension(nombreEnsamblado), clase));
                            if (tipo != null)
                            {
                                object obj = Activator.CreateInstance(tipo);
                                if (obj != null)
                                {
                                    MethodInfo mi = tipo.GetMethod(accion);
                                    if (mi != null)
                                    {
                                        var rpta = mi.Invoke(obj, null);
                                        if (rpta != null) MessageBox.Show(rpta.ToString());
                                    }
                                }
                            }
                        }

                    }
                }
            }
        }

        public void salirApp()
        {
            this.Close();
        }
    }
}

Nota: Asociar al evento "Load" del formulario "frmPrincipal" el método: "llenarMenus", el cual lista las opciones de la raíz o primer nivel, luego la función "crearSubMenu" recursivamente llena los demás niveles.

Crear el Archivo de Texto con las opciones del menu principal

Antes de crear el archivo de texto, hay que grabar y compilar la aplicación y luego ir a la carpeta "bin", "debug" y crear el archivo de texto "Menus.txt" similar a la figura mostrada:


Primera Prueba de la Aplicación Sistema Principal

Si se ejecuta la aplicación se mostrará el MDI con los menús cargados y si abrimos los formularios se mostrará similar a la siguiente figura:


Nota: La primera prueba consiste en mostrar solo las opciones que estan dentro del propio sistema (sin integración).

Crear otra Aplicación Windows Forms con el Módulo de Procesos

Crear una nueva aplicación Windows Forms en C# llamada "ModuloProcesos", cambiarle de nombre al formulario por "frmPrincipal" y configurar su propiedad BackColor en Green, y realizar el diseño similar a la figura mostrada:


Agregar 2 formularios al proyecto (configurar su propiedad BackColor en Green), con los siguientes nombres:
- frmNuevaOrden.cs: Propiedad Text en "Modulo de Procesos - Nueva Orden"
- frmNuevaFactura.cs: Propiedad Text en "Modulo de Procesos - Nueva Factura"

Escribir el siguiente código en el formulario "frmPrincipal":

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ModuloProcesos
{
    public partial class frmPrincipal : Form
    {
        public frmPrincipal()
        {
            InitializeComponent();
        }

        private void btnNuevaOrden_Click(object sender, EventArgs e)
        {
            frmNuevaOrden frm = new frmNuevaOrden();
            frm.WindowState = FormWindowState.Maximized;
            frm.ShowDialog();
        }

        private void btnNuevaFactura_Click(object sender, EventArgs e)
        {
            frmNuevaFactura frm = new frmNuevaFactura();
            frm.WindowState = FormWindowState.Maximized;
            frm.ShowDialog();
        }

        private void btnSalirApp_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    }
}

Grabar y probar la aplicación ModuloProcesos.

Crear una Librería de Clases con el Módulo de Reportes

Crear una nueva Librería de Clases en C# llamada "ModuloReportes", cambiarle de nombre a la clase por "Rutinas.cs" y escribir el siguiente código:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ModuloReportes
{
    public class Rutinas
    {
        public string procesar()
        {
            return "Procesando";
        }
    }
}

Hacer referencias a 2 librerías: System.Drawing.dll y System.Windows.Forms.dll

Agregar 2 formularios al proyecto (configurar su propiedad BackColor en Blue), con los siguientes nombres:
- frmRepCliente.cs: Propiedad Text en "Modulo de Reportes - Clientes"
- frmRepOrden.cs: Propiedad Text en "Modulo de Reportes - Ordenes"

Grabar y compilar la librería.

Nota: Esta No se puede ejecutar porque es una librería de clases, solo funciona desde una aplicación que la llame.

Segunda Prueba de la Aplicación Sistema Principal

Recuperar la aplicación "SistemaPrincipal", sacar una copia al archivo "Menus.txt" (carpeta bin, debug) y aumentar mas opciones al archivo, tal como se muestra en la siguiente figura:


Copiar los archivos ModuloProcesos.exe y ModuloReportes.dll hacia la carpeta bin, debug donde se encuentra la aplicación SistemaPrincipal.exe y el archivo Menus.txt.

Ejecutar nuevamnete la aplicación y se mostrará el MDI con los menús cargados (los cuales han aumentado) y si abrimos los formularios se mostrará similar a la siguiente figura:


Observe que cada color diferencia el ensamblado donde se encuentra el formulario:
- Rojo: Formularios de la Aplicación Sistema Principal
- Verde: Formularios de la Aplicación Modulo Procesos
- Azul: Formularios de la Librería de Clases Modulo Reportes

También observe que al seleccionar "Reportes", "Graficos", "Estadistica" se ejecuta un método externo "procesar" encontrado en la Librería de Clases "ModuloReportes.dll" y cuando se elige la opción "Salir" se ejecuta el método "salirApp" ubicado en la misma clase del formulario MDI.

Comentario Final

En este post, hemos visto como aumentar funcionalidad a una Aplicación Windows sin Recompilar la Aplicación usando Reflection. Los menús se han cargado usando un archivo de texto, pero en vez de este puede ser una tabla de una base de datos.

Si bien es cierto, el uso intensivo de Reflection (sobre todo llamadas en bucles), no es recomendable, existen muchos casos como en este, que nos sirven de gran ayuda.

Muchos desarrolladores, desconecen la importancia y la gran cantidad de usos que se pueden dar a Reflection, pero en este Blog siempre estamos dando a conocer su importancia para darle "inteligencia" a tus soluciones.

Video del Demo

Aquí les dejo, un video parecido al Demo posteado, con la diferencia que la aplicación SistemaPrincipal se llama Demo12, la aplicación ModuloProcesos se llama Demo13 y la librería ModuloReportes se llama Demo14, pero igual les puede servir de guia.


Descarga del Código

2015_06_19_DemoDia_SistemaPrincipal

jueves, 4 de junio de 2015

El Demo del Día: Performace entre Serializadores .NET

Performace entre Serializadores .NET

El Problema de la Performance

Uno de los problemas principales de los Sistemas Informáticos Modernos es la Performance, sea Windows o Web la Aplicación, con el crecimiento del uso (aumento de usuarios) dentro (Intranet) y fuera de la empresa (Internet) las aplicaciones cada vez son mas lentas.

Esta lentitud es tratada de resolver aumentando Hardware como mas Servidores, mas Memorias, mas CPUs, mas Ancho de Banda, etc. Todo menos hacer una reingeniería del desarrollo que es la principal causa, es decir el problema es la forma cómo se programa (Software) donde lo común es que se realizan muchas conexiones al Servidor de Base de Datos, se usan estructuras de datos pesadas (por ejemplo en .NET los DataSets), se hacen bucles expensivos, etc.

Solución al Problema de la Performance

Aprender las Buenas Prácticas de Programación que permitan consumir la menor cantidad de memoria con el menor tiempo de procesamiento, para lo cual hay diferentes las cuales compartiremos a partir de hoy, empezando por una Comparación de Serializadores para elegir el mejor.

Definición de Serialización

Según Wikipedia, la Serialización (o Marshalling en inglés) es el proceso de codificación de un objeto en un medio de almacenamiento (como puede ser un archivo, o un buffer de memoria) con el fin de transmitirlo a través de una conexión en red como una serie de bytes o en un formato humanamente más legible como XML o JSON, entre otros.

La serie de bytes o el formato pueden ser usados para crear un nuevo objeto que es idéntico en todo al original, incluido su estado interno (por tanto, el nuevo objeto es un clon del original).

La Serialización es un mecanismo ampliamente usado para transportar objetos a través de una red, para hacer persistente un objeto en un archivo o base de datos, o para distribuir objetos idénticos a varias aplicaciones o localizaciones.

Importancia de elegir un Serializador

Es necesario saber elegir adecuadamente un Serializador, ya que cada vez que enviamos datos entre dos procesos se usa la serialización. 

A continuación listamos los Serializadores .NET junto con los tipos de aplicaciones o tecnologías distribuidas que las usan por defecto:

- Serializador Binario: Usado en Sockets y .NET Remoting (dll)
- Serializador SOAP: Usado por Servicios Web XML (asmx)
- Serializador XML: Usado por los Servicios WCF (svc y dll)
- Serializador JSON: Usado en Web API
- Serializador Personalizado: Hay que crearlo uno mismo.

La importancia de seleccionar bien el Serializador es que este es el factor principal de performance al momento de usar un Servicio o una Aplicación Distibuida, es decir, depende de este el tiempo de serialización deserealización y sobre todo el formato y el tamaño de los datos que se pasan por la red.

En este post, comprobaremos cual es el mejor, sin embargo la mayoría siempre usa el peor y después quiere que sus Servicios sean rápidos, es decir, quieren una cosa (Performance) pero hacen otra (Estándar).

Crear una Aplicación Windows Forms en C#

Abrir Visual Studio y crear una aplicación Windows Forms en C# llamada "Comparacion_Serializacion", cambiarle de nombre al formulario por "frmListaProductos" y realizar el diseño similar a la figura mostrada:


Nota: En el diseño hay un DataGridView llamado "dgvProducto", un menú contextual llamado "mnuContextual" con 6 opciones: mnuCargarDatos, mnuBinario, mnuSOAP, mnuXML, mnuJSON y mnuPersonalizado.

Crear el Procedimiento Almacenado en SQL Server

Crear el Procedimiento llamado "uspProductsListar" en SQL Server:

Create Procedure uspProductsListar
As
Select ProductID, ProductName, SupplierID, 
CategoryID,UnitPrice, UnitsInStock
From Products

Crear la Clase Entidad del Negocio

Agregar una clase al proyecto llamada: "beProducto" y escribir el siguiente código:

using System;
using System.Runtime.Serialization;

namespace Comparacion_Serializacion
{
    //[Serializable]
    //[DataContract]
    public class beProducto
    {
        //[DataMember]
        public int IdProducto { get; set; }
        //[DataMember]        
        public string Nombre { get; set; }
        //[DataMember]
        public int IdProveedor { get; set; }
        //[DataMember]
        public int IdCategoria { get; set; }
        //[DataMember]
        public decimal PrecioUnitario { get; set; }
        //[DataMember]
        public short Stock { get; set; }
    }
}

Nota: Los atributos de serialización (Serializable, DataContract y DataMember) han sido comentados inicialmente para ver que sucede cuando se intenta serializar con cada técnica.

Crear la Clase de Acceso a Datos

Agregar una clase al proyecto llamada: "daProducto" 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

namespace Comparacion_Serializacion
{
    public class daProducto
    {
        public List<beProducto> listar(SqlConnection con)
        {
            List<beProducto> lbeProducto = null;

            SqlCommand cmd = new SqlCommand("uspProductsListar", con);
            cmd.CommandType = CommandType.StoredProcedure;
            SqlDataReader drd = cmd.ExecuteReader(CommandBehavior.SingleResult);
            if (drd != null)
            {
                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();
            }

            return (lbeProducto);
        }
    }
}

Crear la Clase de Reglas del Negocio

Agregar una clase al proyecto llamada: "brProducto" y escribir el siguiente código:

using System;
using System.Configuration; //ConfigurationManager
using System.Data.SqlClient; //SqlConnection
using System.Collections.Generic; //List

namespace Comparacion_Serializacion
{
    public class brProducto
    {
        public string CadenaConexion { get; set; }
        private string archivoLog;

        public brProducto()
        {
            CadenaConexion = 
                ConfigurationManager.ConnectionStrings["conNW"].ConnectionString;
        }

        public List<beProducto> listar()
        {
            List<beProducto> lbeProducto = null;
            using (SqlConnection con = new SqlConnection(CadenaConexion))
            {
                try
                {
                    con.Open();
                    daProducto odaProducto = new daProducto();
                    lbeProducto = odaProducto.listar(con);
                }
                catch (SqlException ex)
                {
                    //grabarLog(ex);
                }
                catch (Exception ex)
                {
                    //grabarLog(ex);
                }
            } //con.Close(); con.Dispose();
            return (lbeProducto);
        }
    }
}

Nota: Para usar la clase "ConfigurationManager", primero hay que hacer referencia a la librería "System.Configuration.dll"

Crear una Clase para la Serialización Personalizada de Strings

Agregar una clase al proyecto llamada: "CustomSerializer" y escribir el siguiente código:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.IO;

namespace Comparacion_Serializacion
{
    public class CustomSerializer
    {
        public static void Serializar<T>(List<T> lista, string archivo, char separadorCampo, 
        char separadorRegistro)
        {
            StringBuilder sb = new StringBuilder();
            PropertyInfo[] propiedades = lista[0].GetType().GetProperties();
            for (int i = 0; i < propiedades.Length; i++)
            {
                sb.Append(propiedades[i].Name);
                if (i < propiedades.Length - 1) sb.Append(separadorCampo);
            }
            sb.Append(separadorRegistro);
            for (int j = 0; j < lista.Count; j++)
            {
                propiedades = lista[j].GetType().GetProperties();
                for (int i = 0; i < propiedades.Length; i++)
                {
                    if (propiedades[i].GetValue(lista[j], null) != null) 
                    sb.Append(propiedades[i].GetValue(lista[j], null).ToString());
                    else sb.Append("");
                    if (i < propiedades.Length - 1) sb.Append(separadorCampo);
                }
                if (j < lista.Count - 1) sb.Append(separadorRegistro);
            }
            File.WriteAllText(archivo,sb.ToString());
        }

        public static List<T> Deserializar<T>(string archivo, char separadorCampo, 
        char separadorRegistro)
        {
            List<T> lista = new List<T>();
            if (File.Exists(archivo))
            {
                string contenido = File.ReadAllText(archivo);
                string[] registros = contenido.Split(separadorRegistro);
                string[] campos;
                string[] cabecera = registros[0].Split(separadorCampo);
                string registro;
                Type tipoObj;
                T obj;
                dynamic valor;
                Type tipoCampo;
                for (int i = 1; i < registros.Length; i++)
                {
                    registro = registros[i];
                    tipoObj = typeof(T);
                    obj = (T)Activator.CreateInstance(tipoObj);
                    campos = registro.Split(separadorCampo);
                    for (int j = 0; j < campos.Length; j++)
                    {
                        tipoCampo = obj.GetType().GetProperty(cabecera[j]).PropertyType;
                        valor = Convert.ChangeType(campos[j],tipoCampo);
                        obj.GetType().GetProperty(cabecera[j]).SetValue(obj, valor);
                    }
                    lista.Add(obj);
                }
            }
            return (lista);
        }
    }
}

Nota: Para crear mi propio Serializador uso Reflection y Listas Genéricas. 
Una de las claves para hacer la Deserealización es crear una instancia del objeto dinámicamente con Activator.CreateInstance y luego escribir el valor desde la cadena al objeto para lo cual uso Convert.ChangeType.

Escribir el Código del Formulario frmListaProducto

Antes de escribir el código de los Serializadores hay que hacer las siguientes Referencias:
- System.Runtime.Serialization.Formatters.Soap.dll para la Serialización SOAP
- System.Runtime.Serialization para la Serialización JSON

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO; //FileStream
using System.Linq; //ToList
using System.Diagnostics; //Stopwatch
using System.Runtime.Serialization.Formatters.Binary; //BinaryFormatter
using System.Runtime.Serialization.Formatters.Soap; //SoapFormatter
using System.Xml.Serialization; //XmlSerializer
using System.Runtime.Serialization.Json; //DataContractJsonSerializer

namespace Comparacion_Serializacion
{
    public partial class frmListaProducto : Form
    {
        private List<beProducto> lbeProducto;

        public frmListaProducto()
        {
            InitializeComponent();
        }

        private void cargarDatos(object sender, EventArgs e)
        {
            brProducto obrProducto = new brProducto();
            lbeProducto = obrProducto.listar();
            dgvProducto.DataSource = lbeProducto;
        }

        private void medirTiempo(Action metodo)
        {
            Stopwatch cronometro = new Stopwatch();
            cronometro.Start();
            metodo();
            MessageBox.Show(String.Format("Tiempo de Duración: {0:n0} msg", 
            cronometro.Elapsed.TotalMilliseconds));
            cronometro.Stop();
        }

        private void serializarBinario()
        {
            BinaryFormatter bf = new BinaryFormatter();
            using (FileStream fs = new FileStream("Binario.txt",
                FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
            {
                bf.Serialize(fs, lbeProducto);
            }
        }

        private void deserializarBinario()
        {
            BinaryFormatter bf = new BinaryFormatter();
            using (FileStream fs = new FileStream("Binario.txt",
                FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                lbeProducto=(List<beProducto>)bf.Deserialize(fs);
            }
        }

        private void serializarSOAP()
        {
            SoapFormatter sf = new SoapFormatter();
            using (FileStream fs = new FileStream("SOAP.txt",
                FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
            {
                sf.Serialize(fs, lbeProducto.ToArray());
            }
        }

        private void deserializarSOAP()
        {
            SoapFormatter sf = new SoapFormatter();
            using (FileStream fs = new FileStream("SOAP.txt",
                FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                lbeProducto=((beProducto[])sf.Deserialize(fs)).ToList();
            }
        }

        private void serializarXML()
        {
            XmlSerializer xs = new XmlSerializer(typeof(List<beProducto>));
            using (FileStream fs = new FileStream("XML.txt",
                FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
            {
                xs.Serialize(fs, lbeProducto);
            }
        }

        private void deserializarXML()
        {
            XmlSerializer xs = new XmlSerializer(typeof(List<beProducto>));
            using (FileStream fs = new FileStream("XML.txt",
                FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                lbeProducto = (List<beProducto>)xs.Deserialize(fs);
            }
        }

        private void serializarJSON()
        {
            DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(List<beProducto>));
            using (FileStream fs = new FileStream("JSON.txt",
                FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
            {
                js.WriteObject(fs, lbeProducto);
            }
        }

        private void deserializarJSON()
        {
            DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(List<beProducto>));
            using (FileStream fs = new FileStream("JSON.txt",
                FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                lbeProducto=(List<beProducto>)js.ReadObject(fs);
            }
        }

        private void serializarString()
        {
            CustomSerializer.Serializar(lbeProducto, "Strings.txt", ',', '|');
        }

        private void deserializarString()
        {
            lbeProducto=CustomSerializer.Deserializar<beProducto>("Strings.txt", ',', '|');
        }

        private void mnuSerializarBinario_Click(object sender, EventArgs e)
        {
            medirTiempo(serializarBinario);
        }

        private void mnuDeserializarBinario_Click(object sender, EventArgs e)
        {
            medirTiempo(deserializarBinario);
            dgvProducto.DataSource = lbeProducto;
        }

        private void mnuSerializarSOAP_Click(object sender, EventArgs e)
        {
            medirTiempo(serializarSOAP);
        }

        private void mnuDeserializarSOAP_Click(object sender, EventArgs e)
        {
            medirTiempo(deserializarSOAP);
            dgvProducto.DataSource = lbeProducto;
        }

        private void mnuSerializarXML_Click(object sender, EventArgs e)
        {
            medirTiempo(serializarXML);
        }

        private void mnuDeserializarXML_Click(object sender, EventArgs e)
        {
            medirTiempo(deserializarXML);
            dgvProducto.DataSource = lbeProducto;
        }

        private void mnuSerealizarJSON_Click(object sender, EventArgs e)
        {
            medirTiempo(serializarJSON);
        }

        private void mnuDeserealizarJSON_Click(object sender, EventArgs e)
        {
            medirTiempo(deserializarJSON);
            dgvProducto.DataSource = lbeProducto;
        }

        private void mnuSerializarPersonalizado_Click(object sender, EventArgs e)
        {
            medirTiempo(serializarString);
        }

        private void mnuDeserializarPersonalizado_Click(object sender, EventArgs e)
        {
            medirTiempo(deserializarString);
            dgvProducto.DataSource = lbeProducto;
        }
    }
}

Nota: Todos las funciones manejadoras de eventos llaman a la función "medirTiempo" para ejecutar la Serialización y Deserealización.

Modificar el Archivo de Configuración

Abrir el archivo app.Config y aumentar la cadena de conexión:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="conNW" providerName="SQLServer"
      connectionString="uid=UsuarioNW;pwd=123456;
      server=DSOFT\Sqlexpress;database=Northwind"/>
  </connectionStrings>
  <startup> 
      <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
</configuration>

Ejecutar y Probar la Aplicación Windows Forms

Grabar la aplicación y pulsar F5 para ejecutarla. Aparecerá una ventana similar a la siguiente figura:


Clic derecho y se mostrará el menú contextual, del cual seleccionar la primera opción: "Cargar Datos" y se listarán los productos.


Serialización Binaria

Clic derecho y del menú contextual seleccionar la opción "Binario" y luego "Serializar", se mostrará el tiempo de la serialización, en mi caso 11 msg y se creará un archivo llamado "Binario.txt" con el siguiente contenido:


Notas
- Si se intenta Serializar el archivo a Binario se caerá ya que la Entidad (clase beProducto) no es serializable, proceder a descomentar el atributo [Serializable].
- Observar que el tamaño del archivo Binario.txt es de 5756 bytes (en mi caso).

Detener la ejecución y probar nuevamente, pero esta vez seleccionar directamente del menú contextual "Binario" y "Deserealizar", se mostrará el tiempo de deserealización, en mi caso 8 msg y se cargarán los datos desde el archivo "Binario.txt" en la lista de objetos y se mostrará en la grilla.

Serialización SOAP

Detener la ejecución y probar nuevamente, seleccionando la opción "Cargar Datos", luego del menú contextual seleccionar la opción "SOAP" y luego "Serializar", se mostrará el tiempo de la serialización, en mi caso 30 msg y se creará un archivo llamado "SOAP.txt" con el siguiente contenido:


Nota: Observar que el tamaño del archivo SOAP.txt es de 73586 bytes (en mi caso)

Detener la ejecución y probar nuevamente, pero esta vez seleccionar directamente del menú contextual "Binario" y "Deserealizar", se mostrará el tiempo de deserealización, en mi caso 51 msg y se cargarán los datos desde el archivo "SOAP.txt" en la lista de objetos y se mostrará en la grilla.

Serialización XML

Detener la ejecución y probar nuevamente, seleccionando la opción "Cargar Datos", luego del menú contextual seleccionar la opción "XML" y luego "Serializar", se mostrará el tiempo de la serialización, en mi caso 192 msg y se creará un archivo llamado "XML.txt" con el siguiente contenido:


Nota: Observar que el tamaño del archivo XML.txt es de 22270 bytes (en mi caso)

Detener la ejecución y probar nuevamente, pero esta vez seleccionar directamente del menú contextual "Binario" y "Deserealizar", se mostrará el tiempo de deserealización, en mi caso 209 msg y se cargarán los datos desde el archivo "XML.txt" en la lista de objetos y se mostrará en la grilla.

Serialización JSON sin atributos

Detener la ejecución y probar nuevamente, seleccionando la opción "Cargar Datos", luego del menú contextual seleccionar la opción "JSON" y luego "Serializar", se mostrará el tiempo de la serialización, en mi caso 474 msg y se creará un archivo llamado "JSON.txt" con el siguiente contenido:


Nota: Observar que el tamaño del archivo JSON.txt es de 19716 bytes (en mi caso)

Detener la ejecución y probar nuevamente, pero esta vez seleccionar directamente del menú contextual "Binario" y "Deserealizar", se mostrará el tiempo de deserealización, en mi caso 347 msg y se cargarán los datos desde el archivo "JSON.txt" en la lista de objetos y se mostrará en la grilla.

Serialización JSON con atributos (DataContract y DataMember)

Detener la ejecución, decomentar los atributos [DataContract] y [DataMamber] de la entidad beProducto, además renombrar el archivo JSON.txt por JSON2.txt y probar nuevamente, seleccionando la opción "Cargar Datos", luego del menú contextual seleccionar la opción "JSON" y luego "Serializar", se mostrará el tiempo de la serialización, en mi caso 27 msg y se creará un archivo llamado "JSON.txt" con el siguiente contenido:



Nota: Observar que el tamaño del archivo JSON.txt es tan solo de 10332 bytes (en mi caso)

Detener la ejecución y probar nuevamente, pero esta vez seleccionar directamente del menú contextual "Binario" y "Deserealizar", se mostrará el tiempo de deserealización, en mi caso 92 msg y se cargarán los datos desde el archivo "JSON.txt" en la lista de objetos y se mostrará en la grilla.

Serialización Personalizada con Strings

Detener la ejecución y probar nuevamente, seleccionando la opción "Cargar Datos", luego del menú contextual seleccionar la opción "Personalizado" y luego "Serializar", se mostrará el tiempo de la serialización, en mi caso 15 msg y se creará un archivo llamado "Strings.txt" con el siguiente contenido:



Nota: Observar que el tamaño del archivo Strings.txt es de 3125 bytes (en mi caso)

Detener la ejecución y probar nuevamente, pero esta vez seleccionar directamente del menú contextual "Personalizado" y "Deserealizar", se mostrará el tiempo de deserealización, en mi caso 53 msg y se cargarán los datos desde el archivo "Strings.txt" en la lista de objetos y se mostrará en la grilla.

Resumen Comparativo

A continuación una figura con un cuadro resumen de tiempos y tamaños por serializador (formato):



Esta resaltado los que mejor rendimiento tienen, es decir el tiempo de Serializado el ganador es el Binario, pero en tamaño (ancho de banda) el ganador es el Strings (Personalizado), además para Servicios Web el Binario no es aplicable por tanto, el mejor Serializador en Performance sería el String.

Sin embrago el comportamiento de los desarrolladores ha sido:
- Web Services XML con SOAP (2002-2007)
- WCF Services con XML (2008-2011)
- Web API con JSON (2012-2015)

Comentario Final

Después de haber tratado detalladamente los Serializadores .NET y analizado su tiempo y tamaño, la recomendación final es que si desean consumir el mínimo ancho de banda, hay que crear su propio serializador (yo le estoy dejando el mio, que poco a poco lo voy a optimizar) ya que es el que mejor rendimiento tiene en velocidad, tamaño e inter-operabilidad.

Claro, muchos dirán que No es Estandar el formato creado, y otros dirán y como hago para consumirlos desde los clientes, es decir como muestro estos datos, así como hay Templates para JSON yo tambien tengo mis propias rutinas que muestran en JavaScript cualquier Strings separados por caracteres, los cuales serán motivos de otros post.

Descarga del Código


miércoles, 3 de junio de 2015

Videos - Tercer Taller .NET Domingos (Parte 1)

Videos del Tercer Taller .NET Domingos (Parte 1)

Comparto con los visitantes del Blog los Videos (sin audio) del Tercer Taller de los Domingos, con temas variados como .NET Remoting, Programación de Tareas, etc.

Nota: El Demo 01 es un Chat en .NET Remoting pero no fue grabado.

Demo 02 - Aplicación con NET Remoting de Login con Captcha

En este video se muestra como crear una aplicación distribuida con .NET Remoting para el CAPTCHA.

Primero se crea la librería remota, luego el servidor remoto que es una aplicación de consola y finalmente una aplicación cliente en Windows Forms.

En la aplicación cliente se llama en forma asíncrona 1000 veces el método remoto que crea el Captcha.


Demo 03 - Aplicación con NET Remoting de Actualización de Archivos

En este video se enseña como crear una aplicación remota con .NET Remoting que permita descargar y actualizar una aplicación desde un servidor a los clientes.

Para esto se crea una aplicación Windows Forms Editor que es la que se va a descargar y actualizar sus versiones. Luego creamos una Librería Remota que devuelva los bytes de la aplicación actualizada.

También creamos una aplicación de Consola que sirve de Servidor Remoto (aloja a la Librería de Actualización) y finalmente la Aplicación Cliente Windows (oculta en el Systray) que permite invocar a la librería remota y actualizar la aplicación.

Finalmente, un problema que se tuvo fue el bloqueo del archivo al usar Reflection para averiguar el Nombre y la Versión del Ensamblado para hacer la actualización, pero este se resolvió no usando la Url sino los bytes del archivo.


Demo 04 - Generador de Código .NET y Performance con Session

En este video muestro un poco sobre mi Generador de Código .NET que crea Procedimientos Almacenados, Entidades de Negocio, Capa de Acceso a Datos, Capa de Reglas del Negocio y Formularios Web Forms de forma eficiente.

También se muestra un proyecto Web de Prueba y se analiza un formulario con una grilla paginada con una tabla de mas de 250,000 registros y como al usar Session el procesador se eleva al 100%.

Se hace un análisis de la performance reduciendo los datos del Procedimiento Almacenado a 100,000 luego a 50000, después a 20,000 y 10,000 observándose que el procesador ya no se eleva.

La conclusión es que cualquier operación de lectura o escritura de un objeto Session con una Lista de Objetos que tiene mas de 20,000 registros ocasiona que el procesador se eleve demasiado, es decir, usar como límite de registros 20,000 para el Session sería lo mas adecuado.


Demo 05 - Copiador de Archivos incluyendo Subdirectorios

En este video enseño como crear una aplicación de consola que permita copiar todos los archivos junto con todos los directorios de una ruta a otra.

Luego se crea una Tarea Programada en Windows usando el Task Scheduler que permita ejecutar dicha aplicación a diario.


Demo 06 - Automatizar Programación de Tareas en .NET (Parte 1)

En este video se muestra la primera parte de una aplicación de Windows Forms que permite Automatizar la Programación de Tareas, es decir, lo que hace el Task Scheduler.

La primera parte consiste en listar todas las tareas programadas en un ListView mostrando el nombre de la tarea, su folder y su estado: en ejecución, detenido, deshabilitado, etc.

Las Tareas que pertenecen al folder DSOFT y las que están en ejecución se muestran con un color de fondo y texto diferente.


Demo 06 - Automatizar Programación de Tareas en .NET (Parte 2)

En este video se muestra la segunda parte de una aplicación de Windows Forms que permite Automatizar la Programación de Tareas, es decir, lo que hace el Task Scheduler.

En esta segunda parte se enseña como crear una nueva tarea, la cual se muestra en la lista de tareas.


Demo 07 - Optimizar Web API con Serializacion de String

Este video sin duda es uno de los mejores, ya que enseño como Optimizar un Servicio Web API usando mi propio Serializador que convierte una Lista de Objetos en cadenas separadas por un caracter los campos y otro los registros.

La técnica tradicional es usar JSON, pero en este video se observa como se reduce a mas de la mitad el tamaño de los bytes enviados desde el servidor web al cliente usando Strings.

En el cliente usamos una función de JavaScript que realiza la presentación de los datos en una tabla.

Además se muestra como hacer para devolver solo las columnas que se desean pintar es decir, sin necesidad de estar creando varias entidades una para cada vista, al serializar indicamos que columnas deseamos enviar, reduciendo mas aún el tamaño del mensaje.

Sin dudas,  un video muy interesante para cualquier desarrollador que quiere optimizar el envío de datos del servidor al cliente, ya que no solo sirve para Web API, sino para cualquier método en MVC o WebForms.


Nota: A partir de mañana voy a publicar varios Demos relacionados a Performance, incluyendo el código de este último Demo de Web API con Serialización Personalizada (mejor que JSON).

lunes, 1 de junio de 2015

El Demo del Día: Automatizar la Programación de Tareas de Windows en .NET

Automatizar la Programación de Tareas de Windows en .NET

Windows ofrece un servicio muy útil que permite ejecutar tareas programadas por fechas (Task Scheduler), este un gran complemento para ejecutar aplicaciones de consola como por ejemplo para actualización de aplicaciones y/o archivos, envío de notificaciones y/o mensajes de correos, backups, etc.

Requerimiento

Se desea crear una aplicación que permita automatizar la programación de tareas para no hacerla manualmente a través de la aplicación de consola taskschd.msc. Sino tener una aplicación personalizada que permita listar las tareas, crear una nueva tarea y eliminar una tarea existente.

Solución

Crearemos una aplicación Windows Forms .NET en C# que use la clase TaskService de la librería manejada Microsoft.Win32.TaskScheduler.dll que la podemos descargar libremente de la siguiente dirección: Task Scheduler Managed Wrapper

Crear una Aplicación Windows Forms en C#

Abrir Visual Studio y crear una aplicación Windows Forms en C# llamada "ProgramacionTareas", cambiarle de nombre al formulario por "frmListaTareas" y realizar el diseño similar a la figura mostrada:


Nota: En el diseño hay un ListView llamado "lvwTarea", un menú contextual llamado "mnuTarea" con 2 opciones: mnuNuevaTarea y mnuEliminarTarea.

Hacer una referencia a la librería: "Microsoft.Win32.TaskScheduler.dll" y escribir el siguiente código en el formulario "frmListaTareas":

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using net=System.Threading.Tasks;
using Microsoft.Win32.TaskScheduler; //TaskService

namespace ProgramacionTareas
{
    public partial class frmListaTareas : Form
    {
        public frmListaTareas()
        {
            InitializeComponent();
        }

        private void configurarListView()
        {
            lvwTarea.Columns.Add("Nombre de la Tarea", 400);
            lvwTarea.Columns.Add("Folder", 300);
            lvwTarea.Columns.Add("Status", 100);
            lvwTarea.View = View.Details;          
            lvwTarea.MultiSelect = false;
            lvwTarea.GridLines = true;
            lvwTarea.FullRowSelect = true;
            lvwTarea.HotTracking = true;
        }

        private void listarTareas(object sender, EventArgs e)
        {
            configurarListView();
            IEnumerable<Task> tareas = null;
            net.TaskScheduler ui =
                net.TaskScheduler.FromCurrentSynchronizationContext();
            System.Threading.Tasks.Task.Run(() =>
            {
                TaskService prog = new TaskService();
                tareas = prog.AllTasks;
            }).ContinueWith(x => mostrarTareas(tareas),ui);
        }

        private void seleccionarTareas(ListViewItem fila,Task tarea,string estado)
        {
            if (estado.Equals("Running"))
            {
                fila.ForeColor = Color.White;
                fila.BackColor = Color.Red;
            }
            if (tarea.Folder.Name.Equals("DSOFT"))
            {
                fila.ForeColor = Color.Yellow;
                fila.BackColor = Color.Green;
            }
        }

        private void mostrarTareas(IEnumerable<Task> tareas)
        {
            ListViewItem fila;
            string estado;
            foreach (Task tarea in tareas)
            {
                estado = tarea.State.ToString();
                fila = lvwTarea.Items.Add(tarea.Name);
                fila.SubItems.Add(tarea.Folder.Name);
                fila.SubItems.Add(estado);
                seleccionarTareas(fila, tarea, estado);
            }
        }

        private void crearNuevaTarea(object sender, EventArgs e)
        {
            frmNuevaTarea frm = new frmNuevaTarea();
            if (frm.ShowDialog().Equals(DialogResult.OK))
            {
                TaskService programador = new TaskService();
                Trigger evento = Trigger.CreateTrigger(TaskTriggerType.Daily);
                evento.StartBoundary = frm.dtpFechaInicio.Value.AddMinutes(1);
                evento.EndBoundary = frm.dtpFechaFin.Value;
                ExecAction accion = new ExecAction();
                accion.Path = frm.txtPrograma.Text;
                Task tarea = programador.AddTask
                    (@"\DSOFT\" + frm.txtNombre.Text, evento, accion);
                if (tarea != null)
                {
                    string estado = tarea.State.ToString();
                    ListViewItem fila = lvwTarea.Items.Add(tarea.Name);
                    fila.SubItems.Add(tarea.Folder.Name);
                    fila.SubItems.Add(estado);
                    seleccionarTareas(fila, tarea, estado);
                }
            }
        }

        private void eliminarTarea(object sender, EventArgs e)
        {
            if (lvwTarea.SelectedItems != null && lvwTarea.SelectedItems.Count > 0)
            {              
                ListViewItem fila =lvwTarea.SelectedItems[0];
                string nombreCarpeta = fila.SubItems[1].Text;
                TaskService programador = new TaskService();
                TaskFolder carpeta = programador.GetFolder(nombreCarpeta);
                if (carpeta != null)
                {
                    string nombreTarea = fila.SubItems[0].Text;
                    Task tarea = programador.GetTask
                        (String.Format(@"\{0}\{1}", nombreCarpeta, nombreTarea));
                    if (tarea != null)
                    {
                        if (tarea.State.Equals(TaskState.Running)) tarea.Stop();
                        try
                        {
                            carpeta.DeleteTask(nombreTarea, true);
                            fila.Remove();
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(ex.Message, "No se pudo eliminar la tarea");
                        }
                    }
                }
            }
        }
    }
}

Nota: Para el ejemplo, se ha creado en el Task Scheduler de Windows una carpeta llamada "DSOFT"

Agregar otro formulario al proyecto y ponerle como nombre "frmNuevaTarea" y diseñarlo similar a como se ve en la siguiente figura:


Nota: En el diseño hay 2 TextBoxs llamados txtNombre y txtPrograma, además 2 DateTimePicker llamados: dtpFechaInicio y dtpFechaFin, finalmente 3 botones: "btnAbrirPrograma", "btnAceptar" y "btnCancelar". El botón "btnAceptar" tiene configurado la propiedad DialogResult en OK y el botón "btnCancelar" tiene configurado la propiedad DialogResult en Cancel.

Escribir el siguiente código en el formulario "frmNuevaTarea":

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ProgramacionTareas
{
    public partial class frmNuevaTarea : Form
    {
        public frmNuevaTarea()
        {
            InitializeComponent();
        }

        private void mostrarDialogoAbrirPrograma(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Title = "Selecciona el Programa a Abrir";
            ofd.Filter = "Archivos de programas|*.exe";
            if (ofd.ShowDialog().Equals(DialogResult.OK))
            {
                txtPrograma.Text = ofd.FileName;
            }
        }
    }
}

Ejecutar y Probar la Aplicación Windows Forms

Antes de ejecutar la aplicación, para poder comprobar el listado de tareas, la creación de una nueva tarea y la eliminación de una tarea existente hay que abrir el Task Scheduler, para esto pulsar Ctrl + R y escribir el siguiente comando: taskschd.msc

Se mostrará la consola del Task Scheduler similar al mostrado en la siguiente figura:


Nota: Crear una carpeta personalizada para guardar nuestras tareas programadas llamada "DSOFT" y crear un par de tareas básicas dentro de esta carpeta.

Grabar la aplicación y pulsar F5 para ejecutarla, inmediatamente se listaran las tareas programadas similar a la siguiente figura:


En la ventana las tareas de la carpeta personalizada creada "DSOFT" se mostrarán con fondo verde y color amarillo y las tareas que están ejecutándose se mostrarán con fondo rojo y color blanco.

Dar clic derecho sobre la lista (ListView) y aparecerá un menú contextual con 2 opciones, seleccionar la primera opción: "Nueva Tarea" y aparecerá el siguiente cuadro de diálogo:


Llenar los datos de la nueva tarea: nombre, fecha de inicio, fecha de fin y seleccionar el programa a ejecutar, clic al botón "Aceptar" y la tarea se agregará al final de la lista, similar a la siguiente figura:


Comprobar que la tarea aparece en la consola del Task Scheduler de Windows (clic derecho y refrescar), además se ha programado para que después de un minuto del "Aceptar" se ejecute.

Clic derecho a la tarea creada recientemente y seleccionar la opción: "Eliminar Tarea", e inmediatamente se eliminará la tarea, también comprobarlo en la consola del Task Scheduler de Windows (clic derecho y refrescar).

Comentario

En este post hemos aprendido como crear nuestro propio Administrador de Tareas Programadas usando la clase TaskService de la librería manejada: Microsoft.Win32.TaskScheduler.dll.

Esto es de mucha utilidad cuando a veces nos restringen el acceso a las Herramientas de Windows y necesitamos crear una Nueva Tarea para ejecutar una aplicación, sobre todo para programas en Batch.

Descarga del Código
DemoDia_ProgramacionTareas