miércoles, 24 de septiembre de 2014

Programación en 3 capas

Hola a todos:

Después de tantos meses de tener abandonado el Blog por fin hoy se libera un espacio en mi agenda, tiempo que he decidido compartir con todos y cada uno de ustedes.

En este articulo hablare y tratare de explicar con los detalles mas mínimos que es la arquitectura 3 capas, cuales son sus ventajas, como empezar un proyecto 3 capas, cuales son las diferencias entre cada una de ellas, como comunicarlas y como crear un proyecto con la arquitectura 3 capas utilizando Visual Studio 2012.

Antes de comenzar a leer este articulo recuerde que: “El objetivo no es otro mas que el de orientar a los Parvulos .Net sobre la arquitectura de software 3 capas, todo lo escrito en este articulo no es ensayado, no es revisado por nadie mas, por lo cual podría contener errores gramaticales y sintácticos, el articulo y sus conceptos no pretenden ser la verdad absoluta del tema, siéntase en confianza de dejar sus comentarios y opiniones en la sección de comentarios al final del mismo y si lo considera prudente envíeme un correo electrónico por medio del formulario de contacto y por ultimo si el articulo le es de utilidad por favor considere dejar un comentario de agradecimiento.

Requisitos: Visual Studio 2012, Framework 4.0, el proveedor de datos SqlCompact 4.0 instalado en su equipo y muchas ganas de aprender.

Como siempre recomiendo encarecidamente que antes de descargar los proyectos de ejemplo (que les pondré al final de articulo), traten de hacerlo ustedes mismos siguiendo paso a paso todo lo que se mencionara aquí, si tienen dudas en uno en especifico no duden en contactarme.

Dicho todo lo anterior, comencemos…

Arquitectura 3 capas en .Net

¿Ha creado usted software?…¿Si?, entonces sabe lo complejo que resulta crear rutinas y funciones para cada uno de los formularios, importar todas las referencias del motor de base de datos en cada uno de los formularios, cambiar código aquí y allá (porque tiene copias del mismo código en muchos lugares), escribir la lógica de validación de campos dentro del evento, corregir los bugs que pudieran presentarse y no se diga de implementarles mejoras al software, etc., ¿No? entonces no se preocupe este es un buen manual de como evitarse muchos dolores de cabeza en diseño de su arquitectura a seguir.

Para que se de una mejor idea de que hablo, por favor descargue este proyecto de ejemplo, ejecútelo, analice el código, observe como para realizar una misma funcionalidad en dos lugares diferentes tuvimos que escribir casi las mismas líneas de código.

A partir de aquí en adelante estaremos trabajando con el proyecto descargado, aplicándole la Arquitectura 3 capas para demostrar como esta Arquitectura de Diseño nos ayuda a:
  • Separar responsabilidades, cada capa tiene una función especifica y no interviene con la de las demás.
  • Reutilizar código
  • La separación de roles en tres capas, hace mas fácil reemplazar o modificar a una, sin afectar a los módulos restantes
  • El código de la capa intermedia puede ser reutilizado por múltiples
  • Capacidad de migrar nuestro motor de Base de Datos sin grandes impactos al resto del proyecto.
  • Poder cambiar el Front de nuestra aplicación sin afectar a la lógica de nuestra aplicación ni a la Base de datos
Bien como ya hemos mencionado La Arquitectura de diseño 3 capas, consiste en dividir el diseño del software en sus tres principales componentes:
  1. La Interfaz o UI (User interface): Esta Capa es la encargada de interactuar con el usuario, es decir, son aquellas ventanas, mensajes, cuadros de diálogos o paginas web (en el caso del desarrollo web), que el usuario final utiliza para comunicarse con la aplicación, por medio de esta capa el usuario solicita que se ejecuten las tareas proporcionando parámetros de entrada y recibiendo datos como respuesta. Esta capa se comunica con la capa de Lógica de Negocio, enviando y solicitando información y con la capa de Entidades usando sus objetos para enviar y recibir esta información.
  2. La lógica de negocio o Business Logic: Se encarga de implementar, como su nombre lo dice, la lógica del negocio, es decir, todo lo que el Software debe de considerar antes de realizar una acción o el proceso que debe de seguir después de realizar una acción. Por ejemplo: Antes de solicitar a la capa de Datos la inserción de un grupo de registros en una tabla, valida que vayan todos los campos mandatorios dentro de esa solicitud si esta condición no se cumple entonces rechaza la inserción e informa del usuario del status de su solicitud; otro ejemplo podria ser, solicitar a la base de datos que valide la presencia de un registro antes de insertar el siguiente, validar los tipos de datos, etc. esos ejemplos por mencionar los mas básicos y generales. Esta capa recibe de la Capa de Presentación las solicitudes, valida que las condiciones que establece el negocio se cumplan antes de realizar dicha acción o de hacer la respectiva solicitud a la Capa de Acceso a Datos
  3. El acceso a Datos o Data Access: Esta capa es la encargada de la comunicación con la base de datos, en esta capa descansaran todas nuestras acciones CRUD (Create, Read, Update y Delete), será la única que “sabrá” que motor de base de datos se esta utilizando pero le será completamente desconocido el “front”, es decir, jamás sabrá si nuestra aplicación es una aplicación web o desktop. Se encarga de recibir las peticiones de la Capa de Lógica de Negocio, ejecutar dichas acciones y devolver el resultado a la misma capa. 
  4. Capa de Entidades o Entity Layer: Aunque aparentemente es una cuarta capa realmente no lo es, esta capa se encarga de contener todos aquellos objetos (clases) que representan al negocio, y esta es la única que puede ser instanciada en las 3 capas anteriores, es decir, solo ella puede tener comunicación con el resto pero su función se limita a únicamente ser un puente de transporte de datos. Esta capa complementa a la Capa de Negocio
Para una mejor compresión de la comunicación de las 3 capas:
tres capas

Hasta aquí, hemos visto la teoría de lo que representa la Arquitectura 3 Capas, pero…

 ¿Como es que debo representar la Arquitectura 3 Capas en un proyecto de Visual Studio?

Para haya es a donde vamos.

1. Abra el Visual Studio 2012 y cree un proyecto Vacío y nómbrelo “CSharp-3Capas-Primer Entrega
image

2. Diríjase al “Explorador de soluciones” y haga click derecho sobre el proyecto que acabamos de crear:
image

3. Del menú desplegado seleccione Agregar->Nuevo Proyecto:
image

4. Seleccione, Instalado –> Visual C# –> Windows –> Aplicación de Windows Forms –> En el campo Nombre escriba, “Tienda-Presentacion” y presione el botón “Aceptar”.
image

5. Repita el paso 2, 3 y en el 4°, seleccione un tipo de proyecto “Biblioteca de Clases” y establezca como nombre “Tienda-LogicaNegocio

6.Repita el paso 2, 3 y en el 4°, seleccione un tipo de proyecto “Biblioteca de Clases”utilice el nombre “Tienda-AccesoDatos

7. Repita el paso 2, 3 y en el 4°, seleccione un tipo de proyecto “Biblioteca de Clases” utilice el nombre “Tienda-Entidades

¿Como va nuestro diseño’'?
image

Observe que cuando creamos el proyecto principal en automático se creo un proyecto vacío, eliminemos ese proyecto para dejar la siguiente estructura:
image

Ahora ya tenemos nuestra estructura completa, observe que creamos 4 proyectos dentro del principal (que lo iniciamos como proyecto vacío), recuerde que el proyecto de Entidades en este caso “Tienda-Entidades” no es una capa, mas bien, complementa a la capa de Lógica de Negocio.

Continuemos con nuestro diseño…

Capa de Entidades

Recuerde que en esta capa estarán alojados los objetos (clases) con los cuales estaremos trabajando, con estos objetos estaremos persistiendo las tablas de la base de datos que utilizaremos.

La base de datos que usaremos contiene una sola tabla llamada “Producto” la cual contiene 5 campos (Id ‘int’, Descripción ‘nvarchar’, Marca ‘nvarchar’,  Precio ‘nvarchar’), por lo cual la Capa de Entidades contendrá un Objeto llamado Producto con 5 propiedades publicas cada uno de ellos respetando el tipo de dato con el cual esta declarado en la base de datos.

Para declarar nuestra Entidad Producto:

1. Agregue una clase al proyecto “Tienda-Entidades” y llámela “EProducto” (La letra ‘E’ es por conveción, es decir, todos los objetos de nuestra capa de Entidades llevaran la letra ‘E’ al principio para que desde donde las lleguemos a utilizar sepamos que ese Objeto es una Entidad evitando con esto confusiones con otros objetos), dentro agregue las siguientes líneas de código:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tienda_Entidades
{
    public class EProducto
    {
        public int Id { get; set; }
        public string Descripcion { get; set; }
        public string Marca { get; set; }
        public decimal Precio { get; set; }
    }
}

Si tuviéramos mas tablas en nuestra base de datos tendríamos que crear la misma cantidad de objetos en nuestra capa de Entidades y dentro contendrían la misma cantidad de propiedades como campos tiene la tabla siempre respetando el tipo de dato con lo que estos campos están declarados.

Capa de acceso a Datos

Recordemos que la capa de datos es usada por la capa de lógica y la capa de lógica es llamada por la capa de presentación, así que usaremos ese mismo orden para crear nuestras configuraciones y nuestras líneas de código.

1. Agreguemos un archivo de configuración (App.Config) a nuestra capa de datos recuerde que una de las características principales del archivo de configuraciones es que podemos establecer una cadena de conexión la cual accederemos fácilmente desde cualquier lugar del proyecto que lo contiene, que es lo que en esta ocasión nos interesa.

Nuestro archivo de configuraciones lo tenemos que agregar en nuestro proyecto de presentación, y no porque lo vaya a utilizar sino que nuestro software cada vez que arranque buscara este archivo en el directorio desde donde se este ejecutando la aplicación, para efectos de prueba será en la carpeta “..bin\debug” pero cuando nuestro proyecto este instalado, el archivo quedara desplegado justo en la carpeta raíz de nuestra instalación junto con el “.exe” por tal motivo deberá de estar en el mismo proyecto que esta marcado como proyecto de inicio.

Para agregar un archivo de configuraciones siga estos pasos:

- Click derecho sobre el proyecto “Tienda-AccesoDatos –> Agregar –> Nuevo elemento
image

2. En el proyecto que descargaron anteriormente si observaron viene embebida un archivo de base de datos llamado DataBase1.sdf que noes otra cosa mas que un archivo de base de datos propios del motor SQlCompact, cree una carpeta en la unidad C y llámela “Proyecto-3Capas”, después copien este archivo en ese path.

3. Abran el archivo de configuraciones desde el Explorador de soluciones, Copien las siguientes líneas de código, borren el contenido del App.Config  y péguenlas en su lugar:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
    </configSections>
    <connectionStrings>
        <add name="cnnString"
            connectionString="Data Source=C:\Proyecto-3Capas\Database1.sdf"
            providerName="Microsoft.SqlServerCe.4.0" />
    </connectionStrings>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
</configuration>

Observen que el  DataSource apunta a la carpeta que acabamos de crear en la unidad C “Data Source=C:\Proyecto-3Capas\Database1.sdf", esta cadena en proyetos reales normalmente apuntara a un servidor de datos, pero para fines ilustrativos, este directorio nos servirá muy bien.

Volvamos a nuestra Capa de Datos

4. Agreguemos las referencias a las librerías System.Configuration para esto, Click sobre nuestra capa de datos (proyecto “Tienda-AccesoDatos”) –> Agregar Referencia
image

5. Agregue una segunda referencia ahora a System.Data.SqlServeCe (esta librería no siempre aparece, si este es su caso presione el botón Examinar localice la Dll dentro de archivos de programa y selecciónela), presione Aceptar.
image

6. Ya tenemos nuestro App.Config(en el proyecto de arranque) apuntando a nuestra base de datos y tenemos las dos referencias que necesitamos, System.Configuration para poder accesar y leer el archivo de configuraciones y el System.Data.SqlServerCe para poder usar los objetos de acceso a datos propios del motor SQLCOMPACT en nuestra Capa de Datos.

Como nuestra capa de Acceso a Datos, debe de tener la capacidad de Insertar, Leer, Actualizar y Eliminar registros de la tabla Productos, requiere de una comunicación directa con la capa de Entidades, para por ejemplo, al momento de que se le solicite la inserción de un Producto en lugar de enviarle 5 parámetros con los datos del producto se le envié un Objeto y ¿Cual será este objeto? nada mas y nada menos que nuestro objeto EProducto creado en la capa de Entidades, de esta manera ya no trabajaremos con datos sino con objetos llenando, enviando y leyendo propiedades.

Para lograr la comunicación entre la Capa de Datos y la Capa de Entidades se requiere de la referencia de una en la otra, en este caso la referencia de la Capa de Entidades dentro de la Capa de Datos, para ello:

7. Click derecho sobre nuestro proyecto “Tienda-AccesoDatos” –> Agregar Referencia
image

8. Solución –> Proyectos –> Seleccione “Tienda-Entidades”
image

Observe como se a creado un nuevo elemento en nuestra carpeta de Referencias del proyecto “Tienda-AccesoDatos
image

9. Inserte una clase nueva llamada “ProductoDal”  de donde Dal vendrá de Data Access Layer, dentro de la clase que acaba de crear tiene que declarar el espacio de nombres System.Configuration, System.Data.SqlServerCe y del proyecto de Entidades, además tiene que declarar la clase como publica (para que la Capa de Lógica de Negocio pueda tener acceso a ella)
image

Ya tenemos el puente de comunicación entre nuestras Entidades y nuestra Capa de Datos. Solo resta comenzar a codificar las acciones que querramos hacer con nuestra tabla Producto, recuerde que el objeto ProductoDal únicamente tiene como responsabilidad trabajar con todo aquello relacionado con Producto, así que comencemos haciendo la codificación para Insertar, Traer todos los registros existentes en nuestra tabla Producto, Traer, Actualizar y Eliminar por Id.

Para hacer esta tarea mas sencilla le proporcionare el código que deberá de poner en la clase “ProductoDal”, pero, trate de escribir el código para vaya analizando la estructura del mismo.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//nuestras importaciones del Espacio de nombres que estaremos utilizando, 
//recuerde que estas son las referencias que realizamos hace unos momentos...
using System.Configuration;
using System.Data.SqlServerCe;
using Tienda_Entidades;
namespace Tienda_AccesoDatos
{
    //Definimos el acceso de nuestra clase como public, asegurando con esto su accesibilidad desde
    //otros proyectos.
    public class ProductoDal
    {
        //Primero y siguiendo el orden de las acciones CRUD
        //Crearemos un Método que se encarga de insertar un nuevo Producto es nuestra tabla Producto
        /// <summary>
        /// Inserta un nuevo Producto en la tabla Producto
        /// </summary>
        /// <param name="producto">Entidad contenedora de los valores a insertar</param>
        /// <autor>José Luis García Bautista</autor>
        public void Insert(EProducto producto)
        {
            //Creamos nuestro objeto de conexion usando nuestro archivo de configuraciones
            using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].ToString()))
            {
                cnx.Open();
                //Declaramos nuestra consulta de Acción Sql parametrizada
                const string sqlQuery =
                    "INSERT INTO Producto (Descripcion, Marca, Precio) VALUES (@descripcion, @marca, @precio)";
                using (SqlCeCommand cmd = new SqlCeCommand(sqlQuery, cnx))
                {
                    //El primero de los cambios significativos con respecto al ejemplo descargado es que aqui...
                    //ya no leeremos controles sino usaremos las propiedades del Objeto EProducto de nuestra capa
                    //de entidades...
                    cmd.Parameters.AddWithValue("@descripcion", producto.Descripcion);
                    cmd.Parameters.AddWithValue("@marca", producto.Marca);
                    cmd.Parameters.AddWithValue("@precio", producto.Precio);

                    cmd.ExecuteNonQuery();
                }
            }
        }

        /// <summary>
        /// Devuelve una lista de Productos ordenados por el campo Id de manera Ascendente
        /// </summary>
        /// <returns>Lista de productos</returns>
        /// <autor>José Luis García Bautista</autor>
        public List<EProducto> GetAll()
        {
            //Declaramos una lista del objeto EProducto la cual será la encargada de
            //regresar una colección de los elementos que se obtengan de la BD
            //
            //La lista substituye a DataTable utilizado en el proyecto de ejemplo
            List<EProducto> productos = new List<EProducto>();

            using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].ToString()))
            {
                cnx.Open();

                const string sqlQuery = "SELECT * FROM Producto ORDER BY Id ASC";
                using (SqlCeCommand cmd = new SqlCeCommand(sqlQuery, cnx))
                {
                    SqlCeDataReader dataReader = cmd.ExecuteReader();
                    //
                    //Preguntamos si el DataReader fue devuelto con datos
                    while (dataReader.Read())
                    {
                        //
                        //Instanciamos al objeto Eproducto para llenar sus propiedades
                        EProducto producto = new EProducto
                                                 {
                                                     Id = Convert.ToInt32(dataReader["Id"]),
                                                     Descripcion = Convert.ToString(dataReader["Descripcion"]),
                                                     Marca = Convert.ToString(dataReader["Marca"]),
                                                     Precio = Convert.ToDecimal(dataReader["Precio"])
                                                 };
                        //
                        //Insertamos el objeto Producto dentro de la lista Productos
                        productos.Add(producto);
                    }
                }
            }
            return productos;
        }

        /// <summary>
        /// Devuelve un Objeto Producto
        /// </summary>
        /// <param name="idProducto">Id del producto a buscar</param>
        /// <returns>Un registro con los valores del Producto</returns>
        /// <autor>José Luis García Bautista</autor>
        public EProducto GetByid(int idProducto)
        {
            using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].ToString()))
            {
                cnx.Open();

                const string sqlGetById = "SELECT * FROM Producto WHERE Id = @id";
                using (SqlCeCommand cmd = new SqlCeCommand(sqlGetById, cnx))
                {
                    //
                    //Utilizamos el valor del parámetro idProducto para enviarlo al parámetro declarado en la consulta
                    //de selección SQL
                    cmd.Parameters.AddWithValue("@id", idProducto);
                    SqlCeDataReader dataReader = cmd.ExecuteReader();
                    if (dataReader.Read())
                    {
                        EProducto producto = new EProducto
                        {
                            Id = Convert.ToInt32(dataReader["Id"]),
                            Descripcion = Convert.ToString(dataReader["Descripcion"]),
                            Marca = Convert.ToString(dataReader["Marca"]),
                            Precio = Convert.ToDecimal(dataReader["Precio"])
                        };

                        return producto;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Actualiza el Producto correspondiente al Id proporcionado
        /// </summary>
        /// <param name="producto">Valores utilizados para hacer el Update al registro</param>
        /// <autor>José Luis García Bautista</autor>
        public void Update(EProducto producto)
        {
            using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].ToString()))
            {
                cnx.Open();
                const string sqlQuery =
                    "UPDATE Producto SET Descripcion = @descripcion, Marca = @marca, Precio = @precio WHERE Id = @id";
                using (SqlCeCommand cmd = new SqlCeCommand(sqlQuery, cnx))
                {
                    cmd.Parameters.AddWithValue("@descripcion", producto.Descripcion);
                    cmd.Parameters.AddWithValue("@marca", producto.Marca);
                    cmd.Parameters.AddWithValue("@precio", producto.Precio);
                    cmd.Parameters.AddWithValue("@id", producto.Id);

                    cmd.ExecuteNonQuery();
                }
            }
        }

        /// <summary>
        /// Elimina un registro coincidente con el Id Proporcionado
        /// </summary>
        /// <param name="idproducto">Id del registro a Eliminar</param>
        /// <autor>José Luis García Bautista</autor>
        public void Delete(int idproducto)
        {
            using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].ToString()))
            {
                cnx.Open();
                const string sqlQuery = "DELETE FROM Producto WHERE Id = @id";
                using (SqlCeCommand cmd = new SqlCeCommand(sqlQuery, cnx))
                {
                    cmd.Parameters.AddWithValue("@id", idproducto);

                    cmd.ExecuteNonQuery();
                }
            }
        }
    }
}

Observe como desde nuestros diferentes Métodos y funciones estamos haciendo uso del objeto EProducto declarado en la capa de Entidades…Observe también como nuestra clase ProductosDal no valida que el valor de las propiedades de EProducto contengan datos o sean del tipo de dato correcto porque ese ¿es trabajo de?…La Capa de Lógica de Negocio

Capa de Lógica de Negocio

Recuerde que la capa de Lógica es la encargada de establecer toda la lógica que el negocio establece para llevar a cabo una acción o después de haber realizado un proceso, y esta se comunica directamente con la Capa de Acceso a Datos, por lo cual tenemos que hacer la referencia al Proyecto “Tienda-AccesoDatos” y para ello:

1. Click derecho sobre nuestro proyecto “Tienda-LogicaNegocio” –> Agregar Referencia
image

8. Solución –> Proyectos –> Seleccione “Tienda-AccesoDatos” y “Tienda-Entidades”
image

Observe como se acaban de agregar nuestras referencias:
image

9. Agregue una nueva clase y llámela “ProductoBol”, haga los using de las referencias que acabamos de crear, establezca el nivel de acceso como public y copie la siguiente estructura de código:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//
//Hacemos las importaciones del espacio de nombres de los dos proyectos que referenciamos
//observe como esta capa solo referencio a Tienda-AccessData y no a Tienda-Presentacion
//observe también como aquí no es requerida la referencia a System.Data.SqlServerCe
using Tienda_AccesoDatos;
using Tienda_Entidades;
namespace Tienda_LogicaNegocio
{
    public class ProductoBol
    {
        //Instanciamos nuestra clase ProductoDal para poder utilizar sus miembros
        private ProductoDal _productoDal = new ProductoDal();
        //
        //El uso de la clase StringBuilder nos ayudara a devolver los mensajes de las validaciones
       public readonly StringBuilder stringBuilder = new StringBuilder();

        //
        //Creamos nuestro método para Insertar un nuevo Producto, observe como este método tampoco valida los el contenido
        //de las propiedades, sino que manda a llamar a una Función que tiene como tarea única hacer esta validación
        //
        public void Registrar(EProducto producto)
        {
            if(ValidarProducto(producto))
            {
                if (_productoDal.GetByid(producto.Id) == null)
                {
                    _productoDal.Insert(producto);
                }
                else
                    _productoDal.Update(producto);
                
            }
        }

        public List<EProducto>  Todos()
        {
            return _productoDal.GetAll();
        }

        public EProducto TraerPorId(int idProduct)
        {
            stringBuilder.Clear();

            if (idProduct == 0) stringBuilder.Append("Por favor proporcione un valor de Id valido");

            if(stringBuilder.Length == 0)
            {
                return _productoDal.GetByid(idProduct);
            }
            return null;
        }
        
        public void Eliminar(int idProduct)
        {
            stringBuilder.Clear();

            if (idProduct == 0) stringBuilder.Append("Por favor proporcione un valor de Id valido");

            if (stringBuilder.Length == 0)
            {
                _productoDal.Delete(idProduct);
            }
        }

        private bool ValidarProducto(EProducto producto)
        {
            stringBuilder.Clear();

            if (string.IsNullOrEmpty(producto.Descripcion)) stringBuilder.Append("El campo Descripción es obligatorio");
            if (string.IsNullOrEmpty(producto.Marca)) stringBuilder.Append(Environment.NewLine + "El campo Marca es obligatorio");
            if (producto.Precio <= 0) stringBuilder.Append(Environment.NewLine + "El campo Precio es obligatorio");

            return stringBuilder.Length == 0;
        }
    }
}

Analice cada uno de los métodos y funciones que creamos en nuestro primer objeto de nuestra capa de Negocio, observe:
  • Cada uno de ellos tiene una sola responsabilidad
  • La capa de negocio cumple otras tareas y no solo la de ser un puente entre nuestra Capa de Datos  y nuestra Capa de Presentación
  • Como es que estamos usando únicamente objetos y no controles, recuerde que esta capa tampoco sabe que tipo de proyecto es el que la estará usando.
  • Observe que esta capa no tiene referencias a System.Configuration ni mucho menos a System.Data.SqlServerCe.
  • Observe que en esta capa también se utiliza la Capa de Entidades.
Capa de Presentación o User Interface

Le toca el turno a la Capa de Presentación entrar en escena, esta capa será la encargada de interactuar con el usuario, la vista de todo nuestro sistema, la encargada de recoger las peticiones del usuario y de pasar esta misma a la capa de Lógica de Negocio, todo lo que el usuario requiera se la solicitara a esta y todo lo que Lógica de Negocio devuelva esta se la mostrar al usuario en forma de datos.

Para configurar nuestra Capa de Presentación.

10. Haga las referencias a los proyecto “Tienda-LogicaNegocio” y “Tienda-Entidades
image

11. En el formulario que tenemos por default llamado “Form1” diseñe una interfaz como la siguiente:
image

12. Observe las siguientes líneas de código, genere los eventos involucrados y copie el código de ejemplo:

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;
//
//Hacemos las importaciones del espacio de nombres de los dos proyectos que referenciamos
//observe como esta capa solo referencio a Tienda-LogicNegocio y a Tienda-Entidades
//observe como no se referencia a la clase de acceso a Datos
using Tienda_LogicaNegocio;
using Tienda_Entidades;
namespace Tienda_Presentacion
{
    public partial class Form1 : Form
    {
        //
        //
        //Creamos las instancias de la clase Eproducto y ProductoBol
        private EProducto _producto;
        private readonly ProductoBol _productoBol = new ProductoBol();

        public Form1()
        {
            InitializeComponent();
        }

        //
        //Creamos los métodos generales llenando y leyendo objetos
        //
        private void Guardar()
        {
            try
            {
                if (_producto == null) _producto = new EProducto();

                _producto.Id = Convert.ToInt32(txtId.Text);
                _producto.Descripcion = txtDescripcion.Text;
                _producto.Marca = txtMarca.Text;
                _producto.Precio = Convert.ToDecimal(txtPrecio.Text);

                _productoBol.Registrar(_producto);

                if (_productoBol.stringBuilder.Length != 0)
                {
                    MessageBox.Show(_productoBol.stringBuilder.ToString(), "Para continuar:");
                }
                else
                {
                    MessageBox.Show("Producto registrado/actualizado con éxito");

                    TraerTodos();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("Error: {0}", ex.Message), "Error inesperado");
            }
        }

        private void TraerTodos()
        {
            List<EProducto> productos = _productoBol.Todos();

            if (productos.Count > 0)
            {
                dgvDatos.AutoGenerateColumns = false;
                dgvDatos.DataSource = productos;
                dgvDatos.Columns["columnId"].DataPropertyName = "Id";
                dgvDatos.Columns["columnDescripcion"].DataPropertyName = "Descripcion";
                dgvDatos.Columns["columnMarca"].DataPropertyName = "Marca";
                dgvDatos.Columns["columnPrecio"].DataPropertyName = "Precio";
            }
            else
                MessageBox.Show("No existen producto Registrado");
        }

        private void TraerPorId(int id)
        {
            try
            {
                _producto = _productoBol.TraerPorId(id);

                if (_producto != null)
                {
                    txtId.Text = Convert.ToString(_producto.Id);
                    txtDescripcion.Text = _producto.Descripcion;
                    txtMarca.Text = _producto.Marca;
                    txtPrecio.Text = Convert.ToString(_producto.Precio);
                }
                else
                    MessageBox.Show("El Producto solicitado no existe");
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("Error: {0}", ex.Message), "Error inesperado");
            }
        }

        private void Eliminar(int id)
        {
            try
            {
                _productoBol.Eliminar(id);

                MessageBox.Show("Producto eliminado satisfactoriamente");

                TraerTodos();
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("Error: {0}", ex.Message), "Error inesperado");
            }
        }

        //
        //
        //Usamos nuestros metodos y funciones generales, observe como no hemos repetido codigo en ningun lado
        //haciendo con esto que nuestras tareas de actualizacion sean mas sencillas para nosotros o para
        //al asignado en realizarlas...
        private void btnAgregar_Click(object sender, EventArgs e)
        {
            Guardar();
        }

        private void txtId_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Enter && !string.IsNullOrWhiteSpace(txtId.Text))
            {
                e.SuppressKeyPress = true;

                TraerPorId(Convert.ToInt32(txtId.Text));
            }
        }

        private void txtPrecio_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Enter)
            {
                e.SuppressKeyPress = true;

                Guardar();
            }
        }

        private void btbnBuscar_Click(object sender, EventArgs e)
        {
            if (!string.IsNullOrWhiteSpace(txtId.Text))
            {
                TraerPorId(Convert.ToInt32(txtId.Text));
            }
        }

        private void btnEliminar_Click(object sender, EventArgs e)
        {
            if (!string.IsNullOrWhiteSpace(txtId.Text))
            {
                Eliminar(Convert.ToInt32(txtId.Text));
            }
        }
    }
}

De nuevo observe como esta capa desconoce si existe una capa de Datos y mucho menos que motor de base de datos se utiliza, tampoco se encarga de implementar las reglas de validación ni de lógica de negocio, su tarea es interactuar con el usuario pidiendo y desplegando información.

¿Que es lo que falta?

Solo nos resta probar el proyecto…pero eso, será tarea suya…Ejecute el proyecto, inserte un nuevo registro, busque un registro por id, edite su información, elimine un producto, depure el código línea a línea para viva de paso a paso como es que va pasando de capa de capa enviando y trayendo información.

Aquí termina nuestro articulo sobre Arquitectura 3 Capas, espero haya sido de su agrado y que la explicación haya sido lo bastante clara, en caso de que tenga alguna duda por favor deje su pregunta en la sección de comentarios o escríbame por medio del formulario de contacto.

Escribir este articulo me llevo mas de 4 horas, dejar un comentario de agradecimiento le tomara 5 minutos.

Saludos desde Monterrey, Nuevo León México!

Ejemplo C#
Ejemplo Vb.Net

147 comentarios:

  1. Hola José Luis
    Excelente articulo felicitaciones.

    ResponderEliminar
  2. Felicidades, por causualidad no lo tienes para vb .net pues no manejo muy bien el c# o algun link en el que lo expliquen con claridad parecida

    ResponderEliminar
    Respuestas
    1. Hola Arismedy:

      En la parte final del articulo, poco antes de la sección de comentarios podrás encontrar el Link de descarga del proyecto desarrollado en Visual Basic.Net, los conceptos son los mismos para ambos lenguajes, la manera de crear las referencias son exactamente las mismas...el código cambia un poco pero podrás guiarte de los comentarios.

      Si tienes alguna duda puedes regresar y preguntar, tratare de responder lo mas pronto posible.

      Eliminar
    2. Ahhh ok, inmensamene agradecido, es que no trabajaba en capas y estoy poco a poco nadando en esas aguas. Bendiciones

      Eliminar
  3. Buenas jose luis... me sale el siguiente error (Referencia a objeto no establecida como instancia de un objeto).. yo no use SqlCeConnection , yo use SQlConeetion.. me gustaría que me ayudes en esto

    ResponderEliminar
    Respuestas
    1. Hola Alemar:

      Recuerda que solo tu estas viendo el código y tienes todos el contexto de tu aplicación por lo cual si mencionas que te sale cualquier error sin proporcionar la linea de código que te marca el error es como si me mandaras a adivinar el origen de tu problema.

      Bien, tratare de adivinar. Muy seguramente el error por lo poco que mencionas de tus objetos de conexión proviene del nombre de la cadena de conexión establecida en tu archivo App.Config, hablo de esta linea <add name="cnnString" posiblemente tu nombraste tu cadena de conexión de diferente manera y estas llamándolo con otro nombres...

      Valida esa parte si no es la solución compartenos la linea de código y vemos como poderte ayudar con mas certeza.

      Eliminar
    2. tengo el mismo error no se como solucionarlo
      te agradecería tu ayuda

      Eliminar
    3. Como no usamos las extensiones del SqlCE, entonces hay que crear una clase llamada Configuracion, ahi definimos nuestra cadena static string cadenaConexion=@"Data Source, luego la instanciamos en ProductoDAL como un string, y este string lo colocamos en SqlConnection(_cadenaConexion)

      Eliminar
  4. Hola deberías usar los namespace respectivos como using system.Data
    Using system.Data.SqlClient

    ResponderEliminar
  5. Muy buen tutorial amigo, Saludos desde El Salvador.

    ResponderEliminar
  6. Hector
    Se te agradece amigo muy bien explicados los pasos me ayudaste un chingo

    ResponderEliminar
  7. Excelente tutorial, Gracias Colega por brindarnos tan buen material.

    ResponderEliminar
    Respuestas
    1. Hola Julio:
      Muchas gracias por tus comentarios. Estoy preparando uno para la validación de las reglas de negocio usando FluendValidation, muy pronto lo subiré.

      Saludos

      Eliminar
    2. Muchas gracias estimado, por el material de reglas de negocio,

      Eliminar
  8. Estimado seria bueno un tutorial utilizando el modelo de capas, donde la capa de presentación se ha una aplicación WEB, utilizando DEV-Express, esto por que la mayoría de las desarrolladoras de Software están trabajando con base a esto, seria un gran aporte. En este caso un CRUD.

    ResponderEliminar
    Respuestas
    1. Hola:

      El tema es que esos controles requieren de un conocimiento del que siéndote sincero tengo muy poco, sobre todo por el costo de las licencias...

      Eliminar
  9. Revisando tu código de Inserción:

    lo adapte a un procedimiento almacenado:

    Public Sub Insert_factura(ByVal factura As Efactura)
    Try
    Dim cmd As New SqlCommand
    cmd.Connection = cn.conexion
    cmd.CommandType = CommandType.StoredProcedure
    cmd.CommandText = "proc_generar_factura"

    cmd.Parameters.Add("@Id_factura", SqlDbType.Int)
    cmd.Parameters("@Id_factura").Value = factura.ID_Factura

    cmd.Parameters.Add("@Id_cliente", SqlDbType.Int)
    cmd.Parameters("@Id_cliente").Value = factura.ID_Cliente

    cmd.Parameters.Add("@fecha_factura", SqlDbType.DateTime)
    cmd.Parameters("@fecha_factura").Value = factura.fecha

    cmd.Parameters.Add("@activo", SqlDbType.Bit)
    cmd.Parameters("@activo").Value = factura.activo

    cmd.Parameters.Add("@ID_Usuario", SqlDbType.Int)
    cmd.Parameters("@ID_Usuario").Value = factura.ID_Usuario


    cmd.ExecuteNonQuery()

    MessageBox.Show("Factura registrada con éxito")
    Catch ex As Exception
    MsgBox(ex.Message)
    End Try
    End Sub


    Este código esta en .NET

    ResponderEliminar
    Respuestas
    1. Hola Julio:

      Excelente aporte, seria mucho mejor si compartes la estructura de tu Store Procedure, de esta manera los que deseen utilizar SP en sus proyectos podrían guiarse de tu ejemplo.

      Gracias por tu interés en los artículos.

      Eliminar
    2. Estimado esta es la estructura de mi SP:

      USE [Sistema_Facturacion]
      GO
      /****** Object: StoredProcedure [dbo].[proc_generar_factura] Script Date: 12/03/2015 02:48:29 p.m. ******/
      SET ANSI_NULLS ON
      GO
      SET QUOTED_IDENTIFIER ON
      GO
      ALTER PROCEDURE [dbo].[proc_generar_factura]
      @Id_factura INT,
      @Id_cliente INT,
      @fecha_factura DATETIME,
      @activo BIT,
      @ID_Usuario int
      AS
      BEGIN TRY
      BEGIN TRAN generar_factura
      INSERT INTO FACTURA
      (
      Id_factura,
      Id_cliente,
      fecha_factura,
      activo,
      ID_Usuario
      )
      VALUES
      (
      @Id_factura,
      @Id_cliente,
      @fecha_factura,
      @activo,
      @ID_Usuario
      )
      COMMIT TRAN generar_factura
      PRINT 'Factura Generada Correctamente'
      END TRY
      BEGIN CATCH
      ROLLBACK TRAN generar_factura
      PRINT @@error
      SELECT
      ERROR_NUMBER() AS ErrorNumber,
      ERROR_SEVERITY() AS ErrorSeverity,
      ERROR_STATE() AS ErrorState,
      ERROR_PROCEDURE() AS ErrorProcedure,
      ERROR_LINE() AS ErrorLine,
      ERROR_MESSAGE() AS ErrorMessage

      END CATCH

      Eliminar
    3. Hay que tener en cuenta que este solamente es el Encabezado de la Factura, mas adelante compartiré el detalle de la factura.

      Eliminar
  10. A mi parecer es mejor mandar a llamar el Procedure con parametros, que insertar directamente la query,

    ResponderEliminar
    Respuestas
    1. Hola:

      Lo que es mejor eso lo decidirá el desarrollador o el mismo proyecto, bien para algunos es mejor usar SP para otros menos expertos lo mejor es usar las consultas SOL directamente así como para otros será mucho mejor usar Entity Framework...

      El artículo trata como a si lo dice el título sobre programación en 3 capas, la manera de interactuar con la base de datos por el momento no importa porque se saldría mucho del contexto...posiblemente en un artículo dedicado a la capa de acceso a datos podríamos tocar estos tres puntos:

      * Consultas directas desde. NET
      * Usar Stores Procedures
      * Entity Framework

      Que en lo particular me iría por EF como mejor opcion, pero repito lo antes ya Comentado, eso dependerá del nivel de conocimiento o del mismo proyecto.

      Saludos

      Eliminar
    2. Para ser sincero con EF, nunca he trabajado!

      Eliminar
    3. Hola
      @Julio César.
      Gracias por tu aporte del store procedure.
      Hoy en día con las nuevas tecnologías que se están usando y enseñando en los centros de estudios como universidad e institutos. Yo veo el lenguaje transac SQL mas como cultura general.
      Hoy en día se enseña y se usa los famosos ORM'S como son los más conocidos EF y NHibernate.
      Son tecnologías que te permite trabajar entre entidades de negocio, que te ayuda mucho en escribir consultas con pocas líneas de código trabajando directo entre las entidades.
      Sumándole si usas un framework de IoC para aplicar injection.
      Por mi parte yo manejo las reglas de negocio en la capa lógica, no en la base de datos. La lógica de negocio no puede ser mixta, bien es en la capa lógica o en la db, pero jamas en los dos lados, esto lo recomiendo para los lectores de este blog.

      Eliminar
  11. Muy claro el uso de EF en las diferentes capas, muchas gracias.

    ResponderEliminar
  12. Hola Sandy:
    Me agrada que te haya gustado el articulo.

    Saludos

    ResponderEliminar
  13. Exelente Articulo Felicidades, me funciona muy bien. Ahora lo estoy adaptando y quiero que el id del producto se incremente automaticamente cuando se agrege un nuevo registro. Es decir que quede algo asi:

    id=> P00001,P00002, P00003,....,P00010, etc

    ResponderEliminar
    Respuestas
    1. Hola:
      Gracias por tus comentarios.

      Espero que sigas visitando Parvulos .Net

      Eliminar
  14. Me encanto tu articulo me hubiera gustado que hubieras hecho con asp.net , deberías hacerlo con oracle, una consulta en la parte de validación como harías si hubieras tenido un campo registrado que es datetime, de antemano muchas gracias por tu ayuda. saludos desde lima peru.

    ResponderEliminar
    Respuestas
    1. Hola Clara:

      Gracias por tus comentarios me alegra que el artículo te haya gustado y espero que te haya sido de utilidad.

      Como verás los conceptos son generales aplicables a cualquier front y database los que se sientan más comodos usando ASP.Net como front u Óracle como motor de base de datos podrán fácilmente hacerlo sin problemas, la idea es llegar a la mayor cantidad de Párvulos posibles...

      En cuanto a la validación de un campo del tipo DateTime esto dependerá de que es lo que se tiene que validar en este campo, si el campo no puede ser nulo y el front permite que se capture Null en el entonces bastará con hacer un:

      Campofecha ¡= null

      pero repito dependerá mucho de que es lo que se quiera validar...

      Saludos desde la sultana del norte

      Eliminar
  15. Exelente aporte compañero, mi duda es cómo se encajaría al proyecto el usa de una webservice

    ResponderEliminar
    Respuestas
    1. Hola Ludio:

      Eso depende de que funcionalidad provee el WebServices.

      Eliminar
  16. Muchas gracias por la explicación tan detallada.
    Esta parte no la tengo clara, ¿Cuál sería ese proyecto?
    2. En el proyecto que descargaron anteriormente si observaron viene embebida un archivo de base de datos llamado DataBase1.sdf que noes otra cosa mas que un archivo de base de datos propios del motor SQlCompact, cree una carpeta en la unidad C y llámela “Proyecto-3Capas”, después copien este archivo en ese path.

    Saludos!

    ResponderEliminar
    Respuestas
    1. Hola Rekon:

      Gracias por visitar Parvulos.Net.

      En relacion a tu pregunta, en la seccion "Arquitectura 3 capas en .Net" menciono lo siguiente:

      >>Para que se de una mejor idea de que hablo, por favor descargue este proyecto de >>ejemplo, ejecútelo, analice el código, observe como para realizar una misma >>funcionalidad en dos lugares diferentes tuvimos que escribir casi las mismas líneas de >>código.

      Dentro de ese parrafo encontraras un link de descarga del proyecto al que me refiero.

      Eliminar
  17. Saludos veo que en en c# usas en Entidad

    public class EProducto
    {
    public int Id { get; set; }
    public string Descripcion { get; set; }
    public string Marca { get; set; }
    public decimal Precio { get; set; }
    }

    Pero en el proyecto Vb usas

    Public Class EProducto

    Public Property Id() As Integer
    Public Property Descripcion() As String
    Public Property Marca() As String
    Public Property Precio() As Decimal
    End Class

    Es decir no usas el Get -Set

    PEnsaba que debia ser asi:

    Public Class EProducto
    Public Property Id() As Integer
    Get
    Return m_Id
    End Get
    Set
    m_Id = Value
    End Set
    End Property
    Private m_Id As Integer
    Public Property Descripcion() As String
    Get
    Return m_Descripcion
    End Get
    Set
    m_Descripcion = Value
    End Set
    End Property
    Private m_Descripcion As String
    Public Property Marca() As String
    Get
    Return m_Marca
    End Get
    Set
    m_Marca = Value
    End Set
    End Property
    Private m_Marca As String
    Public Property Precio() As Decimal
    Get
    Return m_Precio
    End Get
    Set
    m_Precio = Value
    End Set
    End Property
    Private m_Precio As Decimal
    End Class

    Me podrias clarar

    ResponderEliminar
    Respuestas
    1. Hola Arismendy:

      Antes que otra que guste que continues en el blog y que gusto que estes interesado en el articulo.

      En relacion a tu comentario, detengamonos a pensar que te resulta mas dificil hacer:

      Public Property Id As Integer
      ó
      Public Property Id() As Integer
      Get
      Return m_Id
      End Get
      Set
      m_Id = Value
      End Set
      End Property
      Private m_Id As Integer

      ¿Cual de esas dos formas de trabajar es mas rapida?, te dire que ambas son exactamente lo mismo, la diferencia es que las propiedades AutoImplementadas realizan el Get y el Set internamente y sin necesidad de que tu crees una sola linea.

      Para mayor informacion te dejo este link de la propia libreria de conocimiento de MSDN:

      https://msdn.microsoft.com/es-MX/library/dd293589.aspx

      Echale un vistazo y me comentas con cual de las dos formas de trabajar te quedas.

      Saludos

      Eliminar
  18. Excelente artículo viejo, la verdad me has despejado muchas dudas que tenia y q buscando en otros lugares me confundia mas, te deseo lo mejor de lo mejor.... graciass...saludos

    ResponderEliminar
    Respuestas
    1. Hola Carlos:

      Me alegro que te haya gustado y servido el articulo.

      Saludos

      Eliminar
  19. Respuestas
    1. Hola luis.

      Un gusto que desees implmentarlo, seria mucho mejor si despues compartes el la manera en que los estas implementando.

      Saludos

      Eliminar
  20. Muchas gracias por tu detallado artículo José Luis.
    Abarcas por completo lo que el título indica, lo demás (datos, manejo y acceso a ellos) es lo de menos.

    Saludos...

    ResponderEliminar
  21. Muchas gracias por su gran aporte y claridad en explicar.

    Felicitaciones !!!

    ResponderEliminar
  22. hola, con una pregunta si la base de datos esta en sql, como seria la cadena?

    ResponderEliminar
  23. Jose Luis, cuando harás este ejemplo pero con asp.net.

    ResponderEliminar
  24. Me da error "referencia objeto no establecida como instancia de un objeto".
    Exactamente en esta parte:
    public class ProductoBol
    {
    //Instanciamos nuestra clase ProductoDal para poder utilizar sus miembros
    private ProductoDal _productoDal = new ProductoDal();
    //
    //El uso de la clase StringBuilder nos ayudara a devolver los mensajes de las validaciones
    public readonly StringBuilder stringBuilder = new StringBuilder();

    //
    //Creamos nuestro método para Insertar un nuevo Producto, observe como este método tampoco valida el contenido
    //de las propiedades, sino que manda a llamar a una Función que tiene como tarea única hacer esta validación
    //
    public void Registrar(EProducto producto)
    {
    if (ValidarProducto(producto))
    {
    if (_productoDal.GetByid(producto.Id) == null)

    ResponderEliminar
  25. Me da error "referencia objeto no establecida como instancia de un objeto".
    Exactamente en esta parte:
    public class ProductoBol
    {
    //Instanciamos nuestra clase ProductoDal para poder utilizar sus miembros
    private ProductoDal _productoDal = new ProductoDal();
    //
    //El uso de la clase StringBuilder nos ayudara a devolver los mensajes de las validaciones
    public readonly StringBuilder stringBuilder = new StringBuilder();

    //
    //Creamos nuestro método para Insertar un nuevo Producto, observe como este método tampoco valida el contenido
    //de las propiedades, sino que manda a llamar a una Función que tiene como tarea única hacer esta validación
    //
    public void Registrar(EProducto producto)
    {
    if (ValidarProducto(producto))
    {
    if (_productoDal.GetByid(producto.Id) == null)

    ResponderEliminar
  26. gracias por tu tiempo Jose Luis, excelente articulo

    ResponderEliminar
  27. Podrias resubir los archivos, estan caidos :( se agradece!

    ResponderEliminar
  28. Buenas tardes desde Colombia
    El vinculo de descarga ya no funciona.

    ResponderEliminar
  29. Saludos

    por favor me puedes ayudar con el link del proyecto ya que no esta disponible, Gracias

    ResponderEliminar
  30. Excelente material, ya había olvidado el manejo de capas. Lo he implementado en .net 2013, por supuesto haciendo algunos ajustes. Y pues en verdad se trata de no solo copiar y pegar (o pedir el link de descarga), sino analizar para otros proyectos. Nuevamente mi agradecimiento por compartir tu conocimiento.

    ResponderEliminar
  31. Hola ValidadProducto es una variable? de donde la sacas?

    ResponderEliminar
    Respuestas
    1. Hola Wilmer: valodaeProducto se encuentra en la capa de lógica de negocio....

      Eliminar
    2. Estimado Jose Luis, Soy Rodrigo y quisiera solicitarte el link del ejemplo ya que esta caído, para tener el archivo Database1.sdf o dime como crearlo por favor. Muchas gracias.

      Rodrigo
      nosystems.mat@gmail.com

      Eliminar
  32. muchas gracias por compartir este post

    ResponderEliminar
  33. Muchas gracias por compartir toda esta información. Me gustaría, si no es molestia, que me digas donde obtener alguna bibliografia para completar mis conocimientos sobre este tipo de arquitectura en .Net

    ResponderEliminar
  34. Saludos, como seria la parte de búsqueda, es decir que al momento de escribir en una caja de texto me valla filtrando el DatgridView por el campo Descriptcion

    ResponderEliminar
  35. Quería descargar el ejercicio en vb pero no se puede.

    ResponderEliminar
  36. José Luis, buen aporte. ¿Habría manera de resubir o compartir el archivo con el proyecto?
    Gracias!.

    ResponderEliminar
  37. Muy claro tu ejemplo. Un excelente trabajo muchas gracias por compartirlo!!!

    ResponderEliminar
  38. Hola, Excelente tutorial. Por favor habilita de nuevo los links para descargar los ejemplos puesto que ya no funcionan. Muchas gracias.

    ResponderEliminar
  39. Interesante tutorial, gracias por compartir tus conocimientos. Los links de descarga no funcionan, por favor habilitarlos nuevamente. Gracias.

    ResponderEliminar
  40. Agradecido por esta publicación, Muy buen tutorial pero los links de descarga no funcionan, por favor URGENTE.

    ResponderEliminar
  41. Muy buen material,aunque yo en lo personal no me gusta aplicar programacion en tres capas, porque siento que el tiempo de desarrollo se duplica; seria genial aplicarlo en un proyecto de grandes envergaduras diria yo. Te felicito por el buen trabajo que hiciste explicando el tema :)

    ResponderEliminar
  42. Jose Luis, excelente material de programación en tres capas!!!

    Atentamente,
    Héctor Morel

    ResponderEliminar
  43. Jose luis, excelente por el trabajo, ya que estoy ingresando en programacion de v. net, necesito material de ayuda, y en los vinculos de descarga de dichos materiales esta roto, no funciona, agradecere si vuelve arreglar, y de esta forma aprender mas con su forma de plantear. gracias

    ResponderEliminar
  44. Nombres y funciones de algunas capas de programación

    ResponderEliminar
  45. Nombres y funciones de algunas capas de programación

    ResponderEliminar
  46. Hola, el link ya no funciona. ¿se podra restablecer?

    ResponderEliminar
  47. Hola, Favor restablecer el link ya no funciona.

    ResponderEliminar
  48. buenas tardes amigo, disculpa podrias ponerlo nuevamente ya que quiero ver el código, muchas gracias de ante mano

    ResponderEliminar
  49. Muy bueno articulo, gracias por compartirlo!!!!!!!!

    ResponderEliminar
  50. Excelente bien explicado!! gracias por compartirlo

    ResponderEliminar
  51. Excelente bien explicado!! gracias por compartirlo

    ResponderEliminar
  52. Una muy buena explicación!!
    gracias por tu aporte.
    un saludo

    ResponderEliminar
  53. Muy buen tutorial ahora es mas calro el paradigma de progrma a 3 capas, he probado a crear el ejemplo , esta funcionando bien , sinembargo muestra siempre el mismo error tras realizar alguna accion , por ejemplo cuando cuando inserto un nuevo producto lo hace pero manda el error "Referencia a objeto no establecida como una instancia de objeto ", alguien sabria como evitar este error.

    Gracias.

    ResponderEliminar
  54. Excelente tutorial, muy bien explicado!! Necesito aprender como programar con MVVM (Model View View Model) y me proporcionaron esta liga porque me dicen que primero necesito aprender como es la programación por capas para luego entender el concepto de MVVM.
    Es correcto comparar las capas de Presentación con la de VIEW, la de Negocio con MODEL VIEW, y la de Acceso de datos con MODEL?

    Saludos cordiales, agradezco cualquier comentario al respecto.

    ResponderEliminar
  55. Perfecto, excelente aporte. Quería indicar que el archivo en vb.net ya no está disponible, si pudieras resubirlo seria perfecto! Gracias.

    ResponderEliminar
  56. Hola puedes proporcionar el ejemplo en c#? por favor
    saludos

    ResponderEliminar
  57. Hola Jose luis, el enlace no funciona me compartes el codigo de vb .net

    ResponderEliminar
    Respuestas
    1. Hola Mi querido amigo, gracias por visitar Parvulos .Net, por favor sirvete a descargar los ejemplos de este link:

      https://1drv.ms/f/s!AiQ176xmckvhgWNlu4IzsUgASqq0

      Saludos

      Eliminar
    2. Se te agradece, el enlace, aunque ya lo había hecho a partir del código que has escrito, estoy pensando en grabar un video a partir de este material, me lo permites?

      Eliminar
    3. Hola: adelante, solo no olvides colocar las referencias..

      Saludos

      Eliminar
  58. Hola, muchas gracias por tu tiempo, un abrazo

    ResponderEliminar
  59. Otro punto es por favor revisa, los permisos porque no me permite descargar solo ver

    ResponderEliminar
  60. se lo acaban de borrar el link amigo mio del c# y el .net

    ResponderEliminar
  61. hola tengo una tarea y me piden que el diseno sea en vb y la capa de progamacion en c# como podria hacer para resolver eso y mesclar los 2 lenguajes

    ResponderEliminar
  62. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  63. Parcero muy bueno tu aporte, sin emvbargo quisiera tener acceso al codigo de C#, pero el vinculo de descarga no funciona, sería mucho pedir si me lo enviaras a mi correo electronico(rustia17_1@hotmail.com) de antemano muchas gracias.

    ResponderEliminar
  64. Muy bueno tu aporte y de ser posible me podrías mandar el código fuente a mi correo de migand1303@gmail.com

    Gracias

    Miguel Angel Castro

    ResponderEliminar
  65. de verdad gracias por tu aporte, es justo lo que buscaba

    ResponderEliminar
  66. Muchas gracias, es muy interesante.
    No consigo que funcione el enlace al código en vb.
    Podrías por favor enviármelo a nicecons@yahoo.com

    ResponderEliminar
  67. Excelente guía, sería posible que me envíes el código del proyecto en visual basic hackernivek88@gmail.com. Un saludo desde Perú.

    ResponderEliminar
  68. Excelente amigo.

    El único inconveniente es que los enlaces de OneDrive están rotos. Tal vez, dentro de sus posibilidades, pueda enviarme el código VB.NET a dwcardona@hotmail.com

    Sin más, muy agradecido por el Blog.

    ResponderEliminar
    Respuestas
    1. A veces no leer trae consecuencias.

      Ya vi el enlace correcto:
      https://1drv.ms/f/s!AiQ176xmckvhgWNlu4IzsUgASqq0

      No queda más que agradecerle amigo.

      Eliminar
  69. Excelente articulo, muy claro. Se agradece

    ResponderEliminar
  70. Hola, el archivo que dejaste para descargar yo no se encuentra hospedado, será que me podrías dar otro link para descargarlo?

    Hasta ahora me ha servido mucho tu explicación, gracias

    ResponderEliminar
  71. Hola José Luis, muchas gracias por tu aporte. Imagínate que lo escribiste en
    septiembre del 2014, y lo he encontrado en diciembre del 2017. Ha pasado tiempo, pero sigue muy vigente tu explicación, donde hoy existe los entity framework y demás yerbas, pero que a la hora de ir al comienzo, creo que tu artículo es el mejor punto de partida.
    Es bueno, si has seguido avanzando en el tema, con estas nuevas tecnologías que escribas tus comentarios al respecto, y ver si dejas algún ejemplo. He lidiado mucho con NHibernate, una propuesta clara para algunas cosas, la duda es si sigue los conceptos de la programación de NCapas.
    Muchas gracias por tu aporte.

    ResponderEliminar
    Respuestas
    1. Hola Jorge:

      Claro que los conceptos siguen vigentes, el uso de EF o NH no impacta en la forma de ordenar las capas, solo algunos ajustes pequeños que supongo ya sabrás, como por ejemplo, en el uso de Ef tenemos dos enfoques Code First o DataBase First, el primero tu defines el modelo de datos y sus relaciones a partir de la configuración de tus entidades, el segundo el modelo de la Bd define el modelo de entidades...ambos sentidos son buenos aunque personalmente me inclino por CF.

      Gracias por tus comentarios.
      Saludos

      Eliminar
  72. Hola Jose Luis, me pareció muy útil tu ejemplo, pero tengo una duda, en el archivo app.config del proyecto de tienda-datos, hay que modificarle?

    ResponderEliminar
    Respuestas
    1. Hola Mishu:

      No para nada, la cadena de conexión la toma directamente del primer proyecto. Te dejo aqui el link de descarga de los 3 proyectos de ejemplo:
      https://1drv.ms/f/s!AiQ176xmckvhgWNlu4IzsUgASqq0

      Espero te sean de utilidad.

      Gracias por visitar Parvulos . Net

      Próximamente nos mudamos.
      http://parvulosenpuntonet.com/

      Saludos

      Eliminar
  73. Hola Jose Luis, Me agrado como lo explicaste me pareció muy bueno, tengo unas dudas ojala pudieras ayudarme. Cual es la razón de agregar app.confing en el proyecto de tienda-datos, y no en el proyecto de presentación, por si acaso quiero cambiar la conexión ? Por lo que observo en la capa de datos es solo para acceder de alguna forma a la Base de datos, Pero si quiero agregar lo que se conoce como comportamiento (Procedimientos y/o funciones) Por ejemplo una Formula (a / b), pero en la de negocio validar si los valores son mayor que 0 para que no me de error ? Y la pregunta mas complicada de explicar seria, si quiero hacer un procedimiento que engloba varias clases, y que tiene varias llamadas a diferentes clases y a su ves tiene distintas validaciones, como seria mejor estructurarlo, hacer un nuevo proyecto, una nueva clase y en que proyecto poner la función ? O que libro me recomedarias leer para comprender esta parte.

    ResponderEliminar
    Respuestas
    1. El app config en la capa de datos es para poder definir en este archivo, la cadena de conexión ya que esta capa no tendrá acceso a presentación.

      ¿Donde creas las reglas de validación?, las reglas de validación de lógica de negocio van en la capa de lógica de negocio ,las reglas de validación de datos pueden ir en presentación, por ejemplo, validar que un campo sea del tipo entero fácilmente puedes controlarlo desde la captura, validar que no exista un cliente en la bd antes de agregarlo esa es tarea de la capa de negocio.

      Donde creas funciones comunes, depende de donde serán utilizadas, nuevamente puedes crear una clase en la capa de presentación que va a compartir funcionalidad con el front o puedes poder una clase con funcionalidad compartida en BL siempre y cuando sean las funciones correspondientes a la capa...

      Para validar reglas de datos, por favor busca el artículo correspondiente en este blog, estoy seguro que te será de mucha utilidad .

      Saludos

      Eliminar
  74. Tengo una duda porque nombraste a la clase como "ProductoBol"???
    Gracias.

    ResponderEliminar
    Respuestas
    1. Hola:

      solo para poder identificar que es un objeto "Bussiness Object Layer", osea que pertenece a la capa de Negocio...Únicamente para eso.

      Saludos

      Eliminar
  75. Desde hacia varios días estaba buscando un ejemplo como este, sencillo y practico. He seguido los pasos y el programa me ha funciona bien siempre y cuando el App.Config lo deje dentro del proyecto del form, en un principio el App.Config lo habia dejado dentro del proyecto de la capa de datos pero me salia este error:

    System.NullReferenceException: 'Referencia a objeto no establecida como instancia de un objeto.' System.Configuration.ConnectionStringSettingsCollection.this[string].get devolvió null.

    Si me pudieras orientar te agradecería. Te felicito por este post, excelentísimo.

    ResponderEliminar
  76. muchas gracias por tu tutorial, muy completo, solo una duda en link de descarga para el programa de ejemplo que se estara utilizando durante el tutorial no esta sirviendo, indica que ya no se encuentran archivos en onedrive, puedes por favor subirlos nuevamente para descargar

    gracias y exelente aporte

    ResponderEliminar
  77. excelente, pude entender todo , gracias por el esfuerzo,.

    ResponderEliminar
  78. Hermanazo que tal, mas claro imposible un tuto completo de como trabajar en capas, me aclaraste muchas dudas.
    Desarrollo en wpf con el patron mvvm es parecido o igual creo que a este.
    Te pregunto, sabes que en wpf se restringue por ejemplo utilizar el espacio de nombre system.windows en cualquier capa, es decir, messagebox no deberia utilizarse, sino aplicar un meto jna interfaz de comunicacion para los dialogos. Existen reglas parecidas que se deba saber al utilizar este patron?
    Gracias por el aporte y esperando tu ack.

    ResponderEliminar
  79. Excelente, gracias, me gustaría saber si tiene algún ejemplo consumiendo Web Services por favor

    ResponderEliminar
  80. Muchas gracias, buena base para crear proyectos más grandes

    ResponderEliminar
  81. Muchas gracias y excelente artículo. 👍

    ResponderEliminar
  82. Buen recurso, Jose quería preguntarte si es factible utilizar structure en lugar de clases para las entidades o para la capa de datos.

    Gracias anticipada

    ResponderEliminar
  83. Muchas gracias por el artículo, muy bueno

    ResponderEliminar
  84. Hola José Luis. Excelente tutorial. Clarísimo! Muchas gracias por tu aporte y tu tiempo!
    Juan Pablo

    ResponderEliminar
  85. Gracais José Luis.
    Los enlaces están rotos!!!

    ResponderEliminar
  86. Muchas gracias, con este pequeño tutorial recorde algo que necesitaba, saludos!

    Coloco nuevamente la descarga de proyectos para quienes los necesiten, los tomo de los comentarios anteriores.

    link de descarga de los 3 proyectos de ejemplo:
    https://1drv.ms/f/s!AiQ176xmckvhgWNlu4IzsUgASqq0

    ResponderEliminar
  87. Excelente aporte aunque el archivo de base de datos ya no esta disponible, podías subirlo nuevamente :)

    Tengo un problema al hacerlo para SQL server y me arroja el siguiente error:

    Referencia a objeto no establecida como instancia de un objeto

    en el archivo config modifique el string quedando así:






    GRACIAS

    ResponderEliminar
  88. Excelente aporte aunque el archivo de base de datos ya no esta disponible, podías subirlo nuevamente :)

    Tengo un problema al hacerlo para SQL server y me arroja el siguiente error:

    Referencia a objeto no establecida como instancia de un objeto

    en el archivo config modifique el string quedando así:

    < connectionStrings >
    < add name="cnnString"
    connectionString="Data Source=SERVIDOR;Initial Catalog=BD;User ID=USUARIO"
    providerName="System.Data.SqlClient" / >
    < / connectionStrings >

    GRACIAS

    Responder

    ResponderEliminar
  89. eso fue mas claro que otras explicaciones y resulto facil entender pero como seria si quiero registrar un usuario con clave y demas datos ???? un ayuda porfavor ???

    ResponderEliminar
  90. Muchas gracias José Luis García Bautista, muy interesante y muy instructivo y la explicación paso a paso el mayor aporte.

    ResponderEliminar
  91. Un gusto saludarlo. Lei tu articulo y me parecio muy interesante.
    Al tratar de descargar el ejemplo C# parece que el vinculo desaparecio.
    Tienes alguna forma de enviarme el ejemplo.
    Lo agradecería mucho.
    Mi correo: paulossa1961@gmail.com
    Gracias de antemano.

    ResponderEliminar
  92. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  93. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  94. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  95. en caso que tenga una consulta o procedimiento almacenado que retorna datos de entidades de tablas de mi base de datos ejemplo quiero obtener categoría el nombre y el nombre del producto como que clase utilizaría de la Capa de Entidades o Entity Layer se creería una clase especifica donde tuviera los datos de las dos tablas entidades de mi base de datos ?

    ResponderEliminar

  96. Próximamente nos mudamos.
    http://parvulosenpuntonet.com/

    Donde tienes tu blog, el enlace ya no funciona.

    ResponderEliminar
  97. Muy bueno el articulo, lo busque varias veces este tema, pero es la primera vez que lo veo tan bien explicado.

    ResponderEliminar
  98. Excelente explicación, mas detallada no se puede, te felicito!!

    ResponderEliminar
  99. Te felicito, excelente tutorial muy bien detallado y expresado. Gracias por tu aporte. Saludos

    ResponderEliminar

Deja un comentario si el articulo fue de utilidad.