Construyendo Reportes Para Aplicaciones Web: Parte 2
Esta es la segunda parte de la serie de artículos dedicada a la construcción de reportes para aplicaciones web. En esta parte vamos a examinar dos aspectos muy importantes en la construcción de reportes. El primero es como “hacer” reportes que se parametrizan por código. El segundo es como enviarle parámetros a los reportes para que puedan filtrar información y mostrar solo lo que nos interesa.
En primer lugar debemos ver en detalle el código generado en nuestro ejemplo de órdenes de la base de datos Northwind. Recuerden que no hemos escrito hasta ahora ninguna línea de código, a pesar que la aplicación fue creada en C#, aún no hemos creado nada usando lenguaje.
Lo primero es ver el código fuente de la página:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Report.Part1._Default" %>
<%@ Register Assembly="Microsoft.ReportViewer.WebForms, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Namespace="Microsoft.Reporting.WebForms" TagPrefix="rsweb" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<rsweb:ReportViewer ID="ReportViewer1" runat="server" Font-Names="Verdana"
Font-Size="8pt" Height="400px" Width="649px">
<LocalReport ReportPath="Ordenes.rdlc">
<DataSources>
<rsweb:ReportDataSource DataSourceId="ObjectDataSource1"
Name="DataSet1_Orders" />
</DataSources>
</LocalReport>
</rsweb:ReportViewer>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
SelectMethod="GetData"
TypeName="Report.Part1.DataSet1TableAdapters.OrdersTableAdapter">
</asp:ObjectDataSource>
</div>
</form>
</body>
Vamos a analizarlo línea por línea:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Report.Part1._Default" %>
La primera línea es la directiva que indica a IIS que es una página ASP.NET escrita en lenguaje C# y que su archivo de Code Behind es Default.aspx.cs. Para quienes no estén muy empapados con el tema; el archivo Code Behind es un archivo asociado a la página donde se realiza toda la programación de métodos y eventos que nos servirán para la manipulación de objetos en nuestra página. El archivo, en este caso, tiene la extensión .cs que indica que es en lenguaje C#; y luce algo así:
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
namespace Report.Part1
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
No se preocupen, lo utilizaremos más adelante para modificar nuestro reporte.
La siguiente línea en nuestra página es muy importante, ya que es en ella donde se define que vamos a utilizar un objeto ReportViewer y donde hacemos referencia al ensamblado (Assembly) que tiene el manejo de dicho objeto:
<%@ Register Assembly="Microsoft.ReportViewer.WebForms, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Namespace="Microsoft.Reporting.WebForms" TagPrefix="rsweb" %>
Si están usando Visual Studio 2005, seguramente tendrán en la versión del objeto el número 8.0.0.0. No hay ningún problema, estos artículos funcionan perfectamente con esta versión de ReportViewer.
Luego tenemos la definición de una página web normal, pero dentro de su definición existen dos elementos que son los que nos interesan para nuestro análisis. El primer elemento es la definición del ReportViewer:
<rsweb:ReportViewer ID="ReportViewer1" runat="server" Font-Names="Verdana" Font-Size="8pt" Height="400px" Width="649px">
<LocalReport ReportPath="Ordenes.rdlc">
<DataSources>
<rsweb:ReportDataSource DataSourceId="ObjectDataSource1" Name="DataSet1_Orders" />
</DataSources>
</LocalReport>
</rsweb:ReportViewer>
Si se fijan bien, Un ReportViewer se compone para su funcionamiento de un objeto LocalReport y un ReportDataSource (fuente del reporte). El objeto LocalReport es necesario ya que estamos usando un reporte que existe dentro de nuestra aplicación, si estuviéramos usando un reporte que existe en un servidor de reportes con SQL Server Reporting Services, entonces deberíamos usar un objeto ServerReport (pero no se preocupen, ese es otro tema para una serie de artículos diferentes).
Bien, el objeto LocalReport tiene una referencia al archivo que guarda el documento de reporte por medio del parámetro ReportPath. Nuestro documento de reporte se llama Ordenes.rdlc. Por último es necesario indicar de donde vendrán los datos para este reporte, esto lo hacemos con el objeto ReportDataSource. Este necesita dos parámetros; el primero es el objeto que maneja la conexión con la fuente de datos que en este caso es un ObjectDataSource llamado ObjectDataSource1. El segundo parámetro dice quien tiene la definición de la fuente de datos (DataSet1) y dentro de esta fuente, que objeto está referenciando (Tabla Orders del DataSet).
El otro elemento dentro de la página es el ObjectDataSource que se conectará con la fuente de datos para pasarle la información al reporte:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
SelectMethod="GetData"
TypeName="Report.Part1.DataSet1TableAdapters.OrdersTableAdapter">
En este caso, el ObjectDataSource indica que se debe utilizar un tipo TableAdapter que existe dentro de DatasSet1 y que este adaptador se llama OrdersTableAdapter. Este objeto adaptador es el que conoce todo lo necesario para poder obtener los datos de la base de datos y devolverlos en el formato que se necesita. Por último se utilizará un método llamado GetData que tiene la lógica para hacer las llamadas al adaptador y obtener efectivamente los datos a mostrar.
Bueno, es tiempo de “ensuciarnos” las manos con un poco de programación. Debo decir que en al principio del artículo encerré entre comillas la palabra “hacer” refiriéndome a hacer reportes por código, porque esto en realidad no se puede hacer. Los documentos de reporte son documentos que hay que definirlos de manera gráfica, pero lo que si podemos hacer es manipular todo lo demás por código, y ese es el primer ejemplo que vamos a hacer.
En primer lugar vamos a quitarle a la página el objeto ObjectDataSource y vamos a quitarle al reporte la definición del reporte al objeto ReportViewer. Esto nos dejará la página de la siguiente forma:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Report.Part1._Default" %>
<%@ Register Assembly="Microsoft.ReportViewer.WebForms, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Namespace="Microsoft.Reporting.WebForms" TagPrefix="rsweb" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<rsweb:ReportViewer ID="ReportViewer1" runat="server" Font-Names="Verdana"
Font-Size="8pt" Height="400px" Width="649px">
</rsweb:ReportViewer>
</div>
</form>
</body>
</html>
Ahora vamos a trabajar con el archivo Code Behind (Default.aspx.cs) y vamos a agregar todo lo que teníamos antes pero por medio de código en C#:
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
namespace Report.Part1
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
ObjectDataSource ObjectDataSource1 =
new ObjectDataSource("Report.Part1.DataSet1TableAdapters.OrdersTableAdapter", "GetData");
Microsoft.Reporting.WebForms.ReportDataSource rds =
new Microsoft.Reporting.WebForms.ReportDataSource("DataSet1_Orders", ObjectDataSource1);
ReportViewer1.LocalReport.ReportPath = "Ordenes.rdlc";
ReportViewer1.LocalReport.DataSources.Add(rds);
}
}
}
Como pueden apreciar hicimos exactamente lo mismo, creamos un ObjectDataSource con la definición hacia el DataSet que teníamos, luego hicimos referencia al documento reporte Ordenes.rdlc y por último agregamos un ReportDataSource.
Esto produce el mismo resultado de la vez anterior:
Bien, ahora es tiempo de seguir con otro tema importante. El paso de parámetros. Supongamos que es necesario filtrar por un rango de fechas nuestro reporte, de esta forma se mostrará solamente las órdenes que correspondan a ese rango de fechas. Existen varias formas de hacer este trabajo pero nosotros utilizaremos la forma filtrando directamente en la tabla de la fuente de datos, entonces para ello tenemos que modificar nuestro DataSet de la siguiente forma:
Abrimos el documento DataSet donde tenemos lo siguiente:
Hacemos un click derecho sobre Orders y seleccionamos la opción “Configurar” (Configure en inglés). Y esto mostrará una ventana con el Query que se hace a la tabla:
Ahora modificamos el query agregando al final: WHERE OrderDate BETWEEN @FechaInicial AND @FechaFinal y hacienda click al botón Next llegamos al siguiente paso donde el asistente pregunta si se modifican los métodos para obtener los datos.
Es necesario tener seleccionados todos los checkboxes para que el asistente realice las actualizaciones necesarias y que incluya los nuevos parámetros de fecha que hemos agregado. Luego hacemos click en el botón Next y llegamos al final del asistente donde todo debe haber quedado reconstruido y listo para usarse:
Hacemos click en el botón Finish y luego guardamos el documento que ahora queda modificado en el método GetData como sigue:
Ahora vamos a modificar nuestra página para agregar dos campos donde se puedan ingresar fechas y poder manipular nuestro reporte:
El botón será ahora el encargado de hacer que el reporte se ejecute y obtenga los datos, pasando como parámetros los valores seleccionados de los dos calendarios que indican la fecha inicial y la fecha final. El código del botón queda de la siguiente forma:
protected void btnEjecutar_Click(object sender, EventArgs e)
{
ObjectDataSource ObjectDataSource1 =
new ObjectDataSource("Report.Part1.DataSet1TableAdapters.OrdersTableAdapter", "GetData");
ObjectDataSource1.SelectParameters.Add("FechaInicial", calInicio.SelectedDate.ToShortDateString());
ObjectDataSource1.SelectParameters.Add("FechaFinal", calFinal.SelectedDate.ToShortDateString());
Microsoft.Reporting.WebForms.ReportDataSource rds =
new Microsoft.Reporting.WebForms.ReportDataSource("DataSet1_Orders", ObjectDataSource1);
ReportViewer1.LocalReport.DataSources.Clear();
ReportViewer1.LocalReport.DataSources.Add(rds);
ReportViewer1.LocalReport.ReportPath = "Ordenes.rdlc";
ReportViewer1.LocalReport.Refresh();
}
Entonces, haciendo click sobre el botón obtenemos el siguiente resultado:
Podemos estar seguros que el reporte si filtró los datos tan solo viendo que el conteo de hojas dice que el reporte tiene solamente 3 hojas y no 76 como originalmente tenía.
Bueno, espero que esta segunda entrega haya sido de su agrado y les ayude a complementar lo que ya habíamos visto. Por favor, esperen el siguiente artículo que tratará sobre subreportes y como manejarlos.
Hasta la próxima.