Post on 21-Sep-2018
ReportViewer y Rdlc, ejemplo Factura (datos fijos) Hola a todos:
El siguiente articulo pretende mostrar la manera de crear un reporte local usando ReportViewer y
Local Report o Rdlc enlazando su origen datos (DataSource) a una fuente proveniente de una Lista
Genérica de propiedades. Como se que esto puede resultar un tema demasiado complicado
cuando no se tiene experiencia en el manejo de reportes tratare de que el articulo sea lo mas
descriptivo posible y para ello me apoyare en la mayor cantidad de imágenes posibles, traten de
realizar el ejemplo siguiendo el tutorial, si un paso no les queda claro siéntanse con toda confianza
de hacer las consultas necesarias, al final del articulo tendrán el link de descarga del proyecto de
ejemplo.
Los datos los enviaremos desde un formulario simulando ser los datos de una Factura comercial,
que contiene Datos del cliente y los artículos adquiridos.
Bien, comencemos creando un Formulario y arrastrando los controles necesarios hasta obtener un
formulario como este:
Agregue un segundo formulario, llámelo FacturaRpt.cs este contendrá el control ReportViewer al
cual enlazaremos el LocalReport, asi que agregue uno arrastrándolo desde el cuadro de
herramientas.
Agregue un LocalReport, deje el nombre default:
Ahora, crearemos las clases contenedoras de las entidades, recuerde que una entidad esta
compuesta por campos y propiedades.
Inserte una nueva clase y nómbrela EArticulo, la cual contendrá todas las propiedades de la
entidad Articulo, la clase deberá de quedar con esta estructura:
Código C#:
namespace ReportViewerInvoiceReport_CSharp
{
public class EArticulo
{
public int Numero { get; set; }
public string Upc { get; set; }
public string Descripcion { get; set; }
public decimal Piezas { get; set; }
public decimal Precio { get; set; }
public decimal Importe { get; set; }
}
}
Código Vb.Net:
Public Class EArticulo
Public Property Numero() As Integer
Public Property Upc() As String
Public Property Descripcion() As String
Public Property Piezas() As Decimal
Public Property Precio() As Decimal
Public Property Importe() As Decimal
End Class
Después inserte una nueva clase y nómbrela EFactura, esta clase contendrá todas las
propiedades del encabezado de la factura, la clase deberá de quedar con esta estructura:
Código C#:
using System;
using System.Collections.Generic;
namespace ReportViewerInvoiceReport_CSharp
{
public class EFactura
{
public int Numero { get; set; }
public string Nombre { get; set; }
public string Rfc { get; set; }
public string Direccion { get; set; }
public decimal Subtotal { get; set; }
public decimal Iva { get; set; }
public decimal Total { get; set; }
public DateTime FechaFacturacion { get; set; }
//Creamos una lista con una nueva Instancia de la clase Articulo
//esta lista contendra el detalle de la factura
public List<EArticulo> Detail = new List<EArticulo>();
}
}
Código Vb.Net:
Imports System.Collections.Generic
Public Class EFactura
Public Property Numero() As Integer
Public Property Nombre() As String
Public Property Rfc() As String
Public Property Direccion() As String
Public Property Subtotal() As Decimal
Public Property Iva() As Decimal
Public Property Total() As Decimal
Public Property FechaFacturacion() As DateTime
'Creamos una lista con una nueva Instancia de la clase Articulo
'esta lista contendra el detalle de la factura
Public Detail As New List(Of EArticulo)()
End Class
Configurar el Reporte del informe “Report1.rdlc”
Para poder usar las clases de EFactura para llenar el Encabezado del reporte y EArticulo para el
detalle del mismo, primero debemos de generar el proyecto, para ello localice el menú Generar –
> Generar Solución
Establecer la fuente de datos del Reporte
Active el cuadro Datos del informe, Menú Ver –>Datos del informe
Establezca el Nombre del conjunto de datos y haga click sobre el botón Nuevo:
Seleccione Objeto y haga Click sobre el botón Siguiente:
Seleccione la clase EFactura, recuerde que esta clase contiene las propiedades para el
encabezado de la factura y haga Click sobre el botón Finalizar (Si no hubiera generado el proyecto
estas clases no estarían visibles, así que si no las ve por favor cierre la ventana y genere la
solución):
Observe que ya tiene una fuente de datos seleccionada, solo haga Click en el botón Aceptar:
Bien, ya tiene una Fuente de datos configurada:
Siga los mismos pasos para agregar la fuente de datos para el detalle del reporte solo que ahora
seleccionara la clase EArticulo, al final tendrá un resultado como el siguiente:
Configurar tamaño y encabezado del informe
Active la regla del informe para ello menú Informe –> Regla
Configure el tamaño del informe, menú Informe –> Propiedades del informe
Un punto muy importante en este paso es tener presente el Ancho o alto del reporte dependiendo
que tipo de orientación se haya elegido, en este caso el ancho es lo que interesa, considere que
tiene un ancho máximo de 21.59 cm. a este espacio le restara el valor total de los márgenes que
son 2.0 cm en total ya que configuro un margen de 0.5 cm, dando un espacio de trabajo real
disponible de 19.59 cm., si en el diseño de nuestro reporte nos pasamos de este espacio
disponible nuestro reporte en lugar de mostrarnos una hoja nos mostrar ara dos, normalmente la
segunda hoja sin o con pocos datos.
Bien, continuemos…
Agregue un encabezado de reporte, menú Informe –> Agregar encabezado de pagina
Active el cuadro de herramientas, menú Ver –> Cuadro de herramientas:
Arrastre tantos Cuadros de texto como se requieran, recuerde que nuestros datos provienen de
una clase, entonces por cada campo-propiedad de la clase tiene que crear un control Cuadro de
texto para poder visualizar el dato…esto de crear un control por cada propiedades de la clase
origen es únicamente para este reporte, en realidad usted puede elegir que datos mostrar y cuales
no.
Configurar control cuadro de texto:
Click derecho sobre el control cuadro de texto que deseemos configurar:
Recuerde que establecer un nombre a los controles que vayamos utilizando es de vital importancia
porque será en base a este como los identificaremos, en proyectos pequeños tal vez no tenga
problemas si deja los nombres asignados por default pero, en proyectos grandes esta mala
practica nos provocara constantes dolores de cabeza, entonces, nunca olvide ponerle un nombre a
los controles. Considere que el nombre debe de identificar fácilmente al control al que hace
referencia y al dato que contendrá o al cual estará relacionado, en este caso txtFactura es muy
descriptivo, ya que se entiende que es un control textBox o cuadro de texto y que contendrá el
numero de factura.
Para definir el origen del dato que mostrara el control cuadro de texto despliegue el ComboBox
Valor, vera que este estará cargado con las propiedades de las clases las cuales definimos como
fuente de datos, no le será difícil identificar a que clase pertenece cada una ya que al final podra
ver el nombre que le puso al origen de datos.
Desactivar la casilla “Permitir aumentar el alto” puede resultar muy importante para conservar un
buen diseño, tener activada esta casilla permite que el control cambie de tamaño en tiempo de
ejecución adecuándose al tamaño del valor que contiene, modificando con esto el diseño hasta el
punto de generar hojas innecesarias.
En este ejemplo el campo Factura será del tipo numérico por lo cual para conservar un formato de
numero correcto, tendrá que configurar el lenguaje que utilizara el campo, para mi ubicación
(México) la configuración de lenguaje será “es-Mex”, para configurar esta propiedad:
Seleccione el control cuadro de texto –> Presione F4:
Si no hiciera esto, un formato numérico podría no mostrarse correctamente por ejemplo la cantidad
Mil doscientos cincuenta y seis con cincuenta y seis decimales en México será 1,256.56 pero en
España se escribiría de esta manera 1.256,56. Esta configuración debemos de hacerla aun si en la
clase Main de nuestro proyecto hayamos definido un lenguaje de manera predeterminada.
Agregar parámetros al Reporte:
Para abarcar mas sobre el tema de ReportViewer y local report, subamos un poco mas el nivel de
nuestro proyecto agregando parámetros al mismo, esto no siempre es necesario así que en este
articulo solo lo haremos para fines ilustrativos, es decir, para cuando lleguemos a necesitarlo
tendremos el conocimiento disponible de como usarlos.
En el Datos del informe –> Click derecho sobre Parametros –> Agregar parametro:
De nuevo, recuerde que nombrar correctamente a cada control que agregamos es de vital
importancia:
Agregue un nuevo parámetro y nómbrelo parametroEmpresa.
Ahora, arrastre dos controles Cuadro de texto al informe, y vaya a las propiedades para configurar
el valor a mostrar:
Haga lo mismo para el segundo control agregado, veamos como va nuestro diseño del informe:
Para el detalle del informe, agregue un Tabla arrastrándola desde el cuadro de herramientas, vaya
a las propiedades de la tabla recién agregada, ubique la propiedad DataSetName par establecer el
origen de datos y seleccione Detalle, recuerde que ese fue el nombre que establecimos al origen
de datos:
Agregue las columnas que falten para completar el cuerpo del informe, para ello haga Click
derecho sobre la tabla, Insertar columna, Izquierda o derecha.
Después establezca el valor que mostrara cada columna, para ello tiene dos opciones, seleccionar
el valor desde el icono que vera en la esquina superior derecha de cada celda o ir a propiedades
del cuadro de texto tal cual como lo hizo anteriormente:
Establezca el tamaño de fuente, borde, tipo de dato, lenguaje a utilizar (recuerde lo comentado
anteriormente sobre el formato de numero/moneda)
¿Como va nuestro diseño de informe?
Si tienen un resultado como este, pueden dar su tarea por terminada en cuanto al diseño del
informe si aun no lo logran, todo es cuestión que revisen como configurar un control TextBox o
cuadro de texto como gusten llamarlo y ponerse a ver Alineación, tipo de fuente, borde, relleno,
etc…
Hasta aquí daremos por terminado nuestro diseño, ahora vayamos a:
Establecer una Lista Genérica como DataSource de un local report:
Ubique el Formulario contenedor del ReportViewer llamado FacturaRpt, seleccione el icono
superior derecho del reportViewer –> Seleccione el local report que utilizaremos (el único que
hemos diseñado)
Una vez echo lo anterior, genere el evento Load del Formulario, para ello puede hacer doble click
sobre la barra de titulo o seleccionar el form y presionar la tecla de función F4, esto abrirá la caja
de propiedades, en la parte superior podrá ver un icono con forma de “rayo” de color amarillo, haga
click sobre el para ver los eventos que implementa el control, después ubique el Evento que le
interese implementar en este caso nos interesa el evento “Load”.
Copie y pegue el siguiente código:
Código C#:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Microsoft.Reporting.WinForms;
namespace ReportViewerInvoiceReport_CSharp
{
public partial class FacturaRpt : Form
{
//
//Cree dos listas una para el Encabezado y otra para el detalle
//
public List<EFactura> Invoice = new List<EFactura>();
public List<EArticulo> Detail = new List<EArticulo>();
//
//Cree las propiedades publicas Titulo y Empresa
//
public string Titulo { get; set; }
public string Empresa { get; set; }
public FacturaRpt()
{
InitializeComponent();
}
private void FacturaRpt_Load(object sender, EventArgs e)
{
//Limpiemos el DataSource del informe
reportViewer1.LocalReport.DataSources.Clear();
//
//Establezcamos los parámetros que enviaremos al reporte
//recuerde que son dos para el titulo del reporte y para el
nombre de la empresa
//
ReportParameter[] parameters = new ReportParameter[2];
parameters[0] = new ReportParameter("parameterTitulo",
Titulo);
parameters[1] = new ReportParameter("parameterEmpresa",
Empresa);
//
//Establezcamos la lista como Datasource del informe
//
reportViewer1.LocalReport.DataSources.Add(new
ReportDataSource("Encabezado", Invoice));
reportViewer1.LocalReport.DataSources.Add(new
ReportDataSource("Detalle", Detail));
//
//Enviemos la lista de parametros
//
reportViewer1.LocalReport.SetParameters(parameters);
//
//Hagamos un refresh al reportViewer
//
reportViewer1.RefreshReport();
}
}
}
Código Vb.Net:
Imports Microsoft.Reporting.WinForms
Public Class FacturaRpt
'
'Cree dos listas una para el Encabezado y otra para el detalle
'
Public Invoice As New List(Of EFactura)()
Public Detail As New List(Of EArticulo)()
'
'Cree las propiedades publicas Titulo y Empresa
'
Public Property Titulo() As String
Public Property Empresa() As String
Private Sub FacturaRpt_Load(sender As System.Object, e As
System.EventArgs) Handles MyBase.Load
'Limpiemos el DataSource del informe
ReportViewer1.LocalReport.DataSources.Clear()
'
'Establezcamos los parametros que enviaremos al reporte
'recuerde que son dos para el titulo del reporte y para el nombre
de la empresa
'
Dim parameters As ReportParameter() = New ReportParameter(1) {}
parameters(0) = New ReportParameter("parameterTitulo", Titulo)
parameters(1) = New ReportParameter("parameterEmpresa", Empresa)
'
'Establezcamos la lista como Datasource del informe
'
ReportViewer1.LocalReport.DataSources.Add(New
ReportDataSource("Encabezado", Invoice))
ReportViewer1.LocalReport.DataSources.Add(New
ReportDataSource("Detalle", Detail))
'
'Enviemos la lista de parametros
'
ReportViewer1.LocalReport.SetParameters(parameters)
'
'Hagamos un refresh al reportViewer
'
ReportViewer1.RefreshReport()
End Sub
End Class
Hagamos lo ultimo que falta, enviar los datos desde el formulario principal:
Genere el Evento load del Formulario principal,
Después de generado el evento trate de seguir estas líneas de código, escriba linea por linea si
copia y pega será mas complicado que comprenda (suponiendo que su experiencia en desarrollo
no es amplia):
Código C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace ReportViewerInvoiceReport_CSharp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
/// <summary>
/// Funcion encargada de llenar el control DataGridView
/// <autor>José Luis García Bautista</autor>
/// </summary>
/// <returns>Una lista generica de la clase artículo</returns>
private static List<EArticulo> FillDgv()
{
//
//Cree una lista generica de la entidad EArticulo
//
List<EArticulo> listaArticulos = new List<EArticulo>();
//
//Instancie la clase EArticulo para agregar datos a la lista
//
EArticulo item = new EArticulo
{
//Establezca valores a cada una de
las propiedades
Numero = 1,
Upc = "7501020405680",
Descripcion = "Descripción del
artículo 1",
Piezas = 6,
Precio = new decimal(12.50),
Importe = (decimal)(6 * 12.5),
};
//
//Agregamos el Item a la lista
//
listaArticulos.Add(item);
EArticulo item1 = new EArticulo
{
Numero = 2,
Upc = "7501040805610",
Descripcion = "Descripción del
artículo 2",
Piezas = 3,
Precio = new decimal(22.50),
Importe = (decimal)(3 * 22.5),
};
listaArticulos.Add(item1);
EArticulo item2 = new EArticulo
{
Numero = 3,
Upc = "0412200805610",
Descripcion = "Descripción del artículo 3",
Piezas = 20,
Precio = new decimal(52.80),
Importe = (decimal)(20 * 52.80),
};
listaArticulos.Add(item2);
return listaArticulos;
}
private void Form1_Load(object sender, EventArgs e)
{
//
//La funcion GenerateNumber() se utiliza unicamente para
generar un Número
//aleatorio que simulara ser el numerod e factura
txtnumero.Text = GenerateNumber().ToString();
//Establecemos la propiedad AutoGenerateColumns en False para
evitar que se agreguen
//nuevas columnas a la derecha de las que creamos en tiempo
de diseño.
//
dgvdetalle.AutoGenerateColumns = false;
//
//Establecemos el DataSource del control DataGridView
//
dgvdetalle.DataSource = FillDgv();
//
//Mapeamos las propiedades de la clase devuelta por la
Funcion FillDgv()
//recuerde que esta funcion devuelve una lista del tipo
EArticulo
//
dgvdetalle.Columns["columnNumero"].DataPropertyName =
"Numero";
dgvdetalle.Columns["columnUpc"].DataPropertyName = "Upc";
dgvdetalle.Columns["columnDescripcion"].DataPropertyName =
"Descripcion";
dgvdetalle.Columns["columnPiezas"].DataPropertyName =
"Piezas";
dgvdetalle.Columns["columnPrecio"].DataPropertyName =
"Precio";
dgvdetalle.Columns["columnImporte"].DataPropertyName =
"Importe";
//
//Hacemos las sumatorias usando un método de extensión de
Linq
//
decimal sum = FillDgv().Sum(x => x.Importe);
decimal iva = (Math.Round(((sum / 116) * 16), 2));
decimal subtotal = Math.Round(sum - iva, 2);
txttotal.Text = Convert.ToString(Math.Round(sum, 2));
txtiva.Text = Convert.ToString(iva);
txtsubtotal.Text = Convert.ToString(subtotal);
}
private static int GenerateNumber()
{
Random rdm = new Random();
return rdm.Next();
}
private void InvoiceGenerate()
{
//
//Hacemos una instancia de la clase EFactura para
//llenarla con los valores contenidos en los controles del
Formulario
EFactura invoice = new EFactura();
invoice.Numero = Convert.ToInt32(txtnumero.Text);
invoice.Nombre = txtnombre.Text;
invoice.Rfc = txtrfc.Text;
invoice.Direccion = txtdireccion.Text;
invoice.FechaFacturacion = dtpfecha.Value.Date;
invoice.Subtotal = Convert.ToDecimal(txtsubtotal.Text);
invoice.Iva = Convert.ToDecimal(txtiva.Text);
invoice.Total = Convert.ToDecimal(txttotal.Text);
//Recorremos los Rows existentes actualmente en el control
DataGridView
//para asignar los datos a las propiedades
foreach (DataGridViewRow row in dgvdetalle.Rows)
{
EArticulo article = new EArticulo();
//
//Vamos tomando los valores de las celdas del row que
estamos
//recorriendo actualmente y asignamos su valor a la
propiedad de la clase intanciada
//
article.Numero =
Convert.ToInt32(row.Cells["columnNumero"].Value);
article.Upc =
Convert.ToString(row.Cells["columnUpc"].Value);
article.Descripcion =
Convert.ToString(row.Cells["columnDescripcion"].Value);
article.Piezas =
Convert.ToDecimal(row.Cells["columnPiezas"].Value);
article.Precio =
Convert.ToDecimal(row.Cells["columnPrecio"].Value);
article.Importe =
Convert.ToDecimal(row.Cells["columnImporte"].Value);
//
//Vamos agregando el Item a la lista del detalle
//
invoice.Detail.Add(article);
}
//
//Creamos una instancia del Formulario que contiene nuestro
//ReportViewer
//
FacturaRpt frm = new FacturaRpt();
//
//Usamos las propiedades publicas del formulario, aqui es
donde enviamos el valor
//que se mostrara en los parametros creados en el
LocalReport, para este ejemplo
//estamos Seteando los valores directamente pero usted puede
usar algun control
//
frm.Titulo = "Este es un ejemplo de Factura";
frm.Empresa = "Este es un ejemplo del Nombre de la Empresa";
//
//Recuerde que invoice es una Lista Generica declarada en el
FacturaRtp, es una lista
//porque el origen de datos del LocalReport unicamente
permite ser enlazado a objetos que
//implementen IEnumerable.
//
//Usamos el metod Add porque Invoice es una lista e invoice
es una entidad simple
frm.Invoice.Add(invoice);
//
//Enviamos el detalle de la Factura, como Detail es una lista
e invoide.Details tambien
//es un lista del tipo EArticulo bastara con igualarla
//
frm.Detail = invoice.Detail;
frm.Show();
}
private void btnImprimir_Click(object sender, EventArgs e)
{
InvoiceGenerate();
}
}
}
Código Vb.Net:
Public Class Form1
''' <summary>
''' Funcion encargada de llenar el control DataGridView
''' <autor>José Luis García Bautista</autor>
''' </summary>
''' <returns>Una lista generica de la clase artículo</returns>
Private Shared Function FillDgv() As List(Of EArticulo)
'
'Cree una lista generica de la entidad EArticulo
'
Dim listaArticulos As New List(Of EArticulo)()
'
'Instancie la clase EArticulo para agregar datos a la lista
'
'Establezca valores a cada una de las propiedades
Dim item As New EArticulo()
item.Numero = 1
item.Upc = "7501020405680"
item.Descripcion = "Descripción del artículo 1"
item.Piezas = 6
item.Precio = New Decimal(12.5)
item.Importe = CDec(6 * 12.5)
'
'Agregamos el Item a la lista
'
listaArticulos.Add(item)
Dim item1 As New EArticulo()
item1.Numero = 2
item1.Upc = "7501040805610"
item1.Descripcion = "Descripción del artículo 2"
item1.Piezas = 3
item1.Precio = New Decimal(22.5)
item1.Importe = CDec(3 * 22.5)
listaArticulos.Add(item1)
Dim item2 As New EArticulo()
item2.Numero = 3
item2.Upc = "0412200805610"
item2.Descripcion = "Descripción del artículo 3"
item2.Piezas = 20
item2.Precio = New Decimal(52.8)
item2.Importe = CDec(20 * 52.8)
listaArticulos.Add(item2)
Return listaArticulos
End Function
Private Sub Form1_Load(sender As System.Object, e As
System.EventArgs) Handles MyBase.Load
'
'La funcion GenerateNumber() se utiliza unicamente para generar
un Número
'aleatorio que simulara ser el numerod e factura
txtnumero.Text = GenerateNumber().ToString()
'Establecemos la propiedad AutoGenerateColumns en False para
evitar que se agreguen
'nuevas columnas a la derecha de las que creamos en tiempo de
diseño.
'
dgvdetalle.AutoGenerateColumns = False
'
'Establecemos el DataSource del control DataGridView
'
dgvdetalle.DataSource = FillDgv()
'
'Mapeamos las propiedades de la clase devuelta por la Funcion
FillDgv()
'recuerde que esta funcion devuelve una lista del tipo EArticulo
'
dgvdetalle.Columns("columnNumero").DataPropertyName = "Numero"
dgvdetalle.Columns("columnUpc").DataPropertyName = "Upc"
dgvdetalle.Columns("columnDescripcion").DataPropertyName =
"Descripcion"
dgvdetalle.Columns("columnPiezas").DataPropertyName = "Piezas"
dgvdetalle.Columns("columnPrecio").DataPropertyName = "Precio"
dgvdetalle.Columns("columnImporte").DataPropertyName = "Importe"
'
'Hacemos las sumatorias usando un método de extensión de Linq
'
Dim sum As Decimal = FillDgv().Sum(Function(x) x.Importe)
Dim iva As Decimal = (Math.Round(((sum / 116) * 16), 2))
Dim subtotal As Decimal = Math.Round(sum - iva, 2)
txttotal.Text = Convert.ToString(Math.Round(sum, 2))
txtiva.Text = Convert.ToString(iva)
txtsubtotal.Text = Convert.ToString(subtotal)
End Sub
Private Shared Function GenerateNumber() As Integer
Dim rdm As New Random()
Return rdm.[Next]()
End Function
Private Sub InvoiceGenerate()
'
'Hacemos una instancia de la clase EFactura para
'llenarla con los valores contenidos en los controles del
Formulario
Dim invoice As New EFactura()
invoice.Numero = Convert.ToInt32(txtnumero.Text)
invoice.Nombre = txtnombre.Text
invoice.Rfc = txtrfc.Text
invoice.Direccion = txtdireccion.Text
invoice.FechaFacturacion = dtpfecha.Value.[Date]
invoice.Subtotal = Convert.ToDecimal(txtsubtotal.Text)
invoice.Iva = Convert.ToDecimal(txtiva.Text)
invoice.Total = Convert.ToDecimal(txttotal.Text)
'Recorremos los Rows existentes actualmente en el control
DataGridView
'para asignar los datos a las propiedades
For Each row As DataGridViewRow In dgvdetalle.Rows
Dim article As New EArticulo()
'
'Vamos tomando los valores de las celdas del row que estamos
'recorriendo actualmente y asignamos su valor a la propiedad
de la clase intanciada
'
article.Numero =
Convert.ToInt32(row.Cells("columnNumero").Value)
article.Upc = Convert.ToString(row.Cells("columnUpc").Value)
article.Descripcion =
Convert.ToString(row.Cells("columnDescripcion").Value)
article.Piezas =
Convert.ToDecimal(row.Cells("columnPiezas").Value)
article.Precio =
Convert.ToDecimal(row.Cells("columnPrecio").Value)
article.Importe =
Convert.ToDecimal(row.Cells("columnImporte").Value)
'
'Vamos agregando el Item a la lista del detalle
'
invoice.Detail.Add(article)
Next
'
'Creamos una instancia del Formulario que contiene nuestro
'ReportViewer
'
Dim frm As New FacturaRpt()
'
'Usamos las propiedades publicas del formulario, aqui es donde
enviamos el valor
'que se mostrara en los parametros creados en el LocalReport,
para este ejemplo
'estamos Seteando los valores directamente pero usted puede usar
algun control
'
frm.Titulo = "Este es un ejemplo de Factura"
frm.Empresa = "Este es un ejemplo del Nombre de la Empresa"
'
'Recuerde que invoice es una Lista Generica declarada en
FacturaRtp, es una lista
'porque el origen de datos del LocalReport unicamente permite ser
enlazado a objetos que
'implementen IEnumerable.
'
'Usamos el metod Add porque Invoice es una lista e invoice es una
entidad simple
frm.Invoice.Add(invoice)
'
'Enviamos el detalle de la Factura, como Detail es una lista e
invoide.Details tambien
'es un lista del tipo EArticulo bastara con igualarla
'
frm.Detail = invoice.Detail
frm.Show()
End Sub
Private Sub btnImprimir_Click(sender As System.Object, e As
System.EventArgs) Handles btnImprimir.Click
InvoiceGenerate()
End Sub
End Class
Ahora solo queda probar nuestra aplicación:
Llenamos los datos:
Presione el botón Imprimir, si siguió al pie de la letra el Articulo tendrá un reporte como este:
Observe, en el reporte el Formato numérico y Módena, al igual que no tenemos hojas de mas sin
datos…
Ahora, exporte el reporte a Pdf y observe que los márgenes están dentro del limite, sin generar
hojas de mas, ni salirse de los márgenes:
Bueno, hemos llegado al final de este tutorial, en un entrega posterior abordaremos las imágenes
en Reportes Locales y utilizaremos una Bd como fuente de datos para nuestro reporte.
Saludos desde Monterrey, Nuevo León México!
Ejemplo C#
Ejemplo Vb.Net
Nota: El proyecto fue desarrollado en Vs2010 Ultímate usando Framework 4.0
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. 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.
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, 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:
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”
2. Diríjase al “Explorador de soluciones” y haga click derecho sobre el proyecto que acabamos de
crear:
3. Del menú desplegado seleccione Agregar->Nuevo Proyecto:
4. Seleccione, Instalado –> Visual C# –> Windows –> Aplicación de Windows Forms –> En el
campo Nombre escriba, “Tienda-Presentacion” y presione el botón “Aceptar”.
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°, utilice el nombre “Tienda-AccesoDatos”
7. Repita el paso 2, 3 y en el 4° utilice el nombre “Tienda-Entidades”
¿Como va nuestro diseño‟'?
Observe que cuando creamos el proyecto principal en automático se creo un proyecto vacío,
eliminemos ese proyecto para dejar la siguiente estructura:
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
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
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.
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 EProductocreado 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
8. Solución –> Proyectos –> Seleccione “Tienda-Entidades”
Observe como se a creado un nuevo elemento en nuestra carpeta de Referencias del proyecto
“Tienda-AccesoDatos”
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)
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"].ToStr
ing()))
{
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"].ToStr
ing()))
{
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"].ToStr
ing()))
{
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"].ToStr
ing()))
{
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"].ToStr
ing()))
{
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 EProductodeclarado 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
8. Solución –> Proyectos –> Seleccione “Tienda-AccesoDatos” y “Tienda-Entidades”
Observe como se acaban de agregar nuestras referencias:
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 aSystem.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”
11. En el formulario que tenemos por default llamado “Form1” diseñe una interfaz como la
siguiente:
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
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. 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.
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, 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:
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”
2. Diríjase al “Explorador de soluciones” y haga click derecho sobre el proyecto que acabamos de
crear:
3. Del menú desplegado seleccione Agregar->Nuevo Proyecto:
4. Seleccione, Instalado –> Visual C# –> Windows –> Aplicación de Windows Forms –> En el
campo Nombre escriba, “Tienda-Presentacion” y presione el botón “Aceptar”.
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°, utilice el nombre “Tienda-AccesoDatos”
7. Repita el paso 2, 3 y en el 4° utilice el nombre “Tienda-Entidades”
¿Como va nuestro diseño‟'?
Observe que cuando creamos el proyecto principal en automático se creo un proyecto vacío,
eliminemos ese proyecto para dejar la siguiente estructura:
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
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
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.
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 EProductocreado 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
8. Solución –> Proyectos –> Seleccione “Tienda-Entidades”
Observe como se a creado un nuevo elemento en nuestra carpeta de Referencias del proyecto
“Tienda-AccesoDatos”
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)
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"].ToStr
ing()))
{
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"].ToStr
ing()))
{
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"].ToStr
ing()))
{
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"].ToStr
ing()))
{
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"].ToStr
ing()))
{
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 EProductodeclarado 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
8. Solución –> Proyectos –> Seleccione “Tienda-AccesoDatos” y “Tienda-Entidades”
Observe como se acaban de agregar nuestras referencias:
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 aSystem.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”
11. En el formulario que tenemos por default llamado “Form1” diseñe una interfaz como la
siguiente:
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#
App. Config, cadenas de conexión Hola a todos:
En este articulo haremos una incursión al archivo de configuración o App.Cofig para definir una
cadena de conexión a una Base de Datos:
Agregar un archivo de configuración a un proyecto
Definir una cadena de conexión a una Base de datos
Utilizar el archivo de configuración para obtener la cadena de conexión
Entrando en contexto..
Imagine, que hoy desarrolla un sistema con 20 Formularios, la cual va a instalar en 50 Pc‟s que
utilizaran para la administración de su empresa, su conocimiento sobre las ventajas de usar un
archivo de configuración es nula, así que usted uso lo que muchos programadores llegamos a
utilizar en nuestro inicios, establecer la cadena de conexión en cada método o función donde
necesitemos comunicarnos con la Bd, algo como esto:
C#:
using System.Data.SqlClient;
private void btnProbar_Click(object sender, EventArgs e)
{
using(SqlConnection cnx = new SqlConnection("Data Source=LUISESCOBAR-
PC;Initial Catalog=Directorio;Integrated Security=True"))
{
cnx.Open();
MessageBox.Show(cnx.State.ToString());
}
}
Vb.Net:
Private Sub btnProbar_Click(sender As System.Object, e As
System.EventArgs) Handles btnProbar.Click
Using cnx = New SqlConnection("Data Source=LUISESCOBAR-PC;Initial
Catalog=Directorio;Integrated Security=True")
cnx.Open()
MessageBox.Show(cnx.State.ToString())
End Using
End Sub
y para los que tienen mas experiencia pero aun no llegan a conocer los beneficios del archivo de
configuración, establecer la cadena de conexión en una variable global del tipo String podría
aparentar ser una buena solución ya que se asigna una sola vez y se usa en todas las consultas
requeridas.
C#:
using System.Data.SqlClient;
namespace sinAppConfig
{
public class Connection
{
public static SqlConnection Conexion()
{
return new SqlConnection("Data Source=LUISESCOBAR-PC;Initial
Catalog=Directorio;Integrated Security=True");
}
}
}
Usamos la clase Connection:
private void btnProbar_Click(object sender, EventArgs e)
{
//Utilizamos la clase connection para abrir la cadena de conexion
using(SqlConnection cnx = Connection.Conexion())
{
cnx.Open();
MessageBox.Show(cnx.State.ToString());
}
}
Vb.Net:
Imports System.Data.SqlClient
Module Module1
Public Function Connection() As SqlConnection
Return New SqlConnection("Data Source=LUISESCOBAR-PC;Initial
Catalog=Directorio;Integrated Security=True")
End Function
End Module
Usamos la función Connection declarado en el modulo Module1:
Public Class Form1
Private Sub btnProbar_Click(sender As System.Object, e As
System.EventArgs) Handles btnProbar.Click
'Usamos la funcion publica Connection declarada en el modulo
Module1
Using cnx = Connection()
cnx.Open()
MessageBox.Show(cnx.State.ToString())
End Using
End Sub
End Class
Bien, continuando con nuestro caso hipotético, imagínese que después de algún tiempo se le
notifica de la necesidad de modificar el nombre del server o la ubicación física del mismo, trayendo
con esto la necesidad de modificar nuestra cadena de conexión pero, como declaro su cadena de
conexión en alguna clase publica o modulo para los de Vb.net y esta clase o modulo se comprimió
junto con el Exe al momento de crear su installer ya no tiene acceso a ella desde fuera del
proyecto, así que no tendrá otra opción que abrir su proyecto desde el Vs y modificar su cadena de
conexión en el lugar donde lo haya declarado, si es en una clase publica o modulo solo tendrá que
modificar una sola vez pero, si declaro la cadena en cada método o función donde se requería la
comunicación con la Bd tendrá un problema mas grande (aunque no monstruoso), en realidad
modificar la cadena de conexión no será el problema puesto que usando la combinación de teclas
Ctrl + F podrá fácilmente buscarla y reemplazarla por la nueva.
El problema viene al momento de pasar esta actualización a todas las instalaciones de su
Software, ya que como sabrá, al momento de hacer la modificación tendrá que compilar de nuevo
los proyectos (el que contiene su desarrollo y el proyecto Setup) e ir a cada equipo donde su
software este instalado, desinstalar y posteriormente instalar de nuevo su software, este es el
verdadero problema ya que como se abra dado cuenta, no es tarea sencilla ya que la actividad no
se resume en una acción si no en varias, ahora si consideramos la disponibilidad de los usuarios
para dejarnos trabajar a nosotros mientras ellos se retrasan en sus actividades, estaremos en un
problema.
¿ Solución? usar el archivo de configuración….
Si utiliza un archivo de configuración no tendrá tantos problemas, ya que el utilizarlo simplifica la
tarea a únicamente ir al directorio de instalación ubicar el archivo App.Config, abrirlo con un editor
de texto como NotePad, modificar su cadena de conexión, guardar y listo a seguir usando su
aplicación sin mas ni mas; no necesita abrir el proyecto en el Vs ni compilar, mucho menos instalar
de nuevo.
¿Comprende ahora la gran utilidad de este archivo tan simple?…
Hasta este punto creo que ya esta convencidos de la utilidad del archivo de configuración, ahora
vayamos viendo como agregarlo a nuestro proyecto y como utilizarlo:
Agregar un archivo de configuración a un proyecto
En este ejemplo utilizaremos una Bd creada en SqlServer Express 2008 y Vs2010 Ultimate, la Bd
es únicamente para probar la conexión, puede crear cualquiera en su Server.
Antes de comenzar cree un proyecto del tipo Windows Forms, abra el formulario y agregue un
botón.
Para crear el archivo de configuración tenemos dos opciones:
Utilizar el “Asistente para la configuración de orígenes de base de datos” o insertar un nuevo
elemento al proyecto del tipo App.Config, veamos paso a paso cada uno de ellos,
Utilizar el “Asistente para la configuración de orígenes de base de datos”
Menú Datos –> Agregar nuevo origen de datos
En esta parte, el asiste nos pregunta el nombre que asignaremos a la cadena de conexión,
podemos dejar el nombre por default o poner el nombre que creamos conveniente, en esta ocasión
dejemos el que nos pone por default mas adelante lo modificaremos:
Después de realizar todo lo anterior podrá ver en el “Explorador de soluciones” dos nuevos
archivos, que son el archivo de configuración y un DataSet:
El archivo DataSet por favor elimínelo, ya que no nos servirá de nada…
Si abre el archivo de configuración que acabamos de agregar a nuestro proyecto, podrá ver dentro
una estructura de código Xml como el siguiente:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
</configSections>
<connectionStrings>
<add
name="connectionString_CSharp.Properties.Settings.DirectorioConnectionStr
ing"
connectionString="Data Source=LUISESCOBAR-PC;Initial
Catalog=Directorio;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
Veamos que representa cada línea:
Esta línea representa el nombre de nuestra cadena de conexión, si recuerda en uno de los pasos
anteriores el asistente le preguntaba el nombre de la cadena de conexión, como dejamos el
nombre por default es este el que aparece como nombre de nuestra cadena:
<add name="connectionString_CSharp.Properties.Settings.DirectorioConnectionString"
Modifiquemos esta línea con el fin de tener un nombre mas corto (con este nombre será como
obtendremos el valor de nuestra Cadena):
<add name="cnxString"
La siguiente línea representa el valor de nuestra cadena de conexión:
connectionString="Data Source=LUISESCOBAR-PC;Initial
Catalog=Directorio;Integrated Security=True"
y por ultimo tendremos el nombre del motor de Base de Datos que estaremos usando:
providerName="System.Data.SqlClient" />
Bien, ya vimos como usar el asistente para definir una cadena de conexión…ahora vayamos a ver
Insertar un nuevo elemento al proyecto del tipo App.Config
Haga Click derecho sobre el proyecto –> Agregar –> Nuevo Elemento:
Seleccione “Archivo de configuración de aplicaciones”, deje el nombre por default y presione el
botón “Aceptar”
Observe que tenemos un nuevo archivo en el “Explorador de soluciones” ábralo dentro podrá ver
una estructura de código como la siguiente:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
</configuration>
Dentro de la etiqueta <configuration> usted tendrá que agregar el nombre de la cadena de
conexión, el valor de la cadena de conexión y el nombre del motor de Base de datos que estará
utilizando, esto ultimo no siempre es necesario ya que depende mucho del motor que estemos
utilizando porque para Bases de datos como Access, SqlCe…son necesarios para SqlServer no lo
es.
Al final usted deberá de tener un archivo con la siguiente estructura de código Xml:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="cnxString"
connectionString="Data Source=LUISESCOBAR-PC;Initial
Catalog=Directorio;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
Utilizar el archivo de configuración para obtener la cadena de conexión
Ahora veamos como utilizar la cadena de conexión que acabamos de crear por cualquiera de los
pasos anteriores.
Antes de poder utilizar el valor de cadena de conexión almacenada en la
etiqueta connectionString de nuestro archivo de configuración, debe de agregar una referencia al
ensamblado System.Configuration para esto:
Haga click derecho sobre el proyecto –> Agregar referencia –> Ficha .Net –> seleccione
System.Configuration –> Aceptar
Una vez echo esto, vayamos a la clase donde queramos abrir la conexión e importemos el espacio
de nombres System.Configuration, echo lo anterior estamos listos para utilizar nuestro App.Config.
para C#:
using System;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.Configuration;
namespace connectionString_CSharp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnconectar_Click(object sender, EventArgs e)
{
TestConnection();
}
private void TestConnection()
{
using(SqlConnection cnx = new
SqlConnection(ConfigurationManager.ConnectionStrings["cnxString"].ToStrin
g()))
{
cnx.Open();
MessageBox.Show(cnx.State.ToString());
}
}
}
}
Para Vb.Net:
Imports System.Data.SqlClient
Imports System.Configuration
Public Class Form1
Private Sub btnProbar_Click(sender As System.Object, e As
System.EventArgs) Handles btnProbar.Click
TestConnection()
End Sub
Private Sub TestConnection()
Using cnx As New
SqlConnection(ConfigurationManager.ConnectionStrings("cnxString").ToStrin
g())
cnx.Open()
MessageBox.Show(cnx.State.ToString())
End Using
End Sub
End Class
Bien, cuando compile su proyecto, lo instale y se le presente la necesidad de modificar la cadena
de conexión, simplemente diríjase al directorio de instalación ubique el archivo de configuración
(una vez instalado su software la nomenclatura de su archivo
será NombredelProyecto.Exe.Config), ábralo con el notepad, modifique su cadena, guarde los
cambios y sin mas ni mas podrá seguir trabajando…
Saludos desde Monterrey, Nuevo León México!
Ejemplo C#
Ejemplo Vb.Net
Nota: El proyecto fue desarrollado en Vs2010 usando una Bd SqlServer 2008 Express y Framework 4.0 Client Profile