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>
      &#1
60;
<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>

</html>

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">

</asp:ObjectDataSource>

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 h
acia 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:

WorkingReport

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:

DataSet_Original

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:

TableAdapter_Original

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.

TableAdapter_2

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:

 TableAdapter_Fin

Hacemos click en el botón Finish y luego guardamos el documento que ahora queda modificado en el método GetData como sigue:

 TableAdapter_Modificado

Ahora vamos a modificar nuestra página para agregar dos campos donde se puedan ingresar fechas y poder manipular nuestro reporte:

 parametros

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:

Reporte_Con_Parametros

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.

10 thoughts on “Construyendo Reportes Para Aplicaciones Web: Parte 2

  1. Carlos Arias

    Como para un reporte de 2 hojas, no que imprima 2 copias, si no 2 hojas de un reporte una hoja unas cosas y otra hoja otras cosas???

    Reply
  2. admin Post author

    @Oscar:
    Puedes usar:

    ObjectDataSource1.SelectParameters.Add(“Variable”, Request.QueryString[“Parametro”]);

    Saludos.
    Jose

    Reply
  3. Luis Fernando López Duque

    Hola buen día. El ejemplo que muestras es super facil y práctico, pero tengo un inconveniente cuando lo ejecuto, me visualiza el siguiente mensaje de error: Error al procesar el informe.
    No se pudo encontrar el tipo especificado en la propiedad TypeName de ObjectDataSource ”.
    Expreso mis agradecimientos por la colaboración prestada.

    Reply
  4. admin Post author

    @Luis Fernando:

    En el caso del ejemplo el ObjectDataSource usa el tipo: Report.Part1.DataSet1TableAdapters.OrdersTableAdapter

    En donde:
    Report.Part1 <-- es el nombre del projecto DataSet1 <-- Nombre del dataset de donde se extraeran los datos OrdersTableAdapter <-- el table adapter que se define dentro del dataset Tienes que verificar que los nombres que usas sean los correctos. Jose

    Reply
  5. Vanessa

    no entiendo, me muestra errores en algunas linas

    — calInicio (dice que no existe en el cotexto actual)

    — no-proof:=”mso-no-proof:” new?;=”new?;” courier=”courier”> (cada palabra o signo no existe dentro del mismo contexto)

    Reply
  6. Daniel

    Que tal, antes que nada gracias por el manual, pero me sale el siguiente error:
    Error al procesar el informe.
    ObjectDataSource ‘ObjectDataSource1’ no pudo encontrar un método ‘GetDataByInternet’ no genérico que no tenga ningún parámetro.

    Y continua el error a pesar de que le indique las fechas, realmente es el único error que me sale, espero puedas ayudarme.

    Saludos

    Reply
  7. Axel

    Hola, tengo un problema, al momento de ejecutarlo me pone el siguiente error:
    ObjectDataSource ‘ObjectDataSource1’ no pudo encontrar un método ‘GetData’ no genérico que no tenga ningún parámetro
    esto es porque le puse la condición where con los parametros de fecha de inicio y termino, si se los quito funciona pero no filtra nada y me pone el siguiente error:
    ObjectDataSource ” no pudo encontrar un método ‘GetData’ no genérico que tenga parámetros: FechaInicial, FechaFinal.
    porque no los he puesto
    otra cosa mas
    en el código del botón me sale error en esta línea de código no-proof:=”mso-no-proof:” new?;=”new?;” courier=”courier”>
    por lo que yo la quite de esa parte, no sé si sea un problema también
    Gracias por tu ayuda

    Reply
    1. admin Post author

      @Axel, el DataSet genera un metodo que (por defecto) se llama GetData el cual lee los datos de la fuente de datos.
      En el query del DataSet, si escribes los parámetros como @FechaInicial y @FechaFinal, entonces estos son agregados a la definición del método como parámetros. En caso contrario el método se crea sin parámetros.
      Si agregas los parámetros, entonces debes pasar los valores para dichos parámetros cuando antes de llamar al método Refresh() del Object Data Source de la siguiente manera:

      ObjectDataSource1.SelectParameters.Add(“FechaInicial”, calInicio.SelectedDate.ToShortDateString());
      ObjectDataSource1.SelectParameters.Add(“FechaFinal”, calFinal.SelectedDate.ToShortDateString());

      Con eso debe ser suficiente para que el Object Data Source tenga toda la información para llamar al método GetData del DataSet.

      Saludos.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *