Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Quickly add reports to your website

0.00/5 (No votes)
17 Jul 2007 2  
Shows how to add a Crystal Reports report to an ASP.NET page, with grouping, filtering, and drill down.

Screenshot - CrystalReportIntro.gif

Introduction

Whenever a website stores data in a database, you'll want to run reports on that data, to find ways to improve customer service, detect abuse, etc.

Fortunately, Visual Studio comes with a powerful report generator, Crystal Reports, included for free! Once you've learned how to use it, it allows you to quickly create web pages that show various reports. Its many features include grouping, sorting, filtering, graphs, exporting to PDF and Excel files, etc. Figuring out how to use this power can be a bit tricky though.

This article shows step by step how to use Crystal Reports to add a report to a web page. You'll see how to programmatically apply grouping and filters, and how to provide drill down from group level totals to the supporting detail records.

Needed to run the code

  • Visual Studio 2005 (it may also work with Visual Studio 2003, but the code wasn't tested with this).
  • You need to have Crystal Reports installed as part of Visual Studio. To see if you have it installed, add a new item in Visual Studio - Crystal Report should be included in the list of templates. If it's not there, install it from the Visual Studio installation.
  • Access to a SQL Server 2005, SQL Server 2000, or SQL Server Express server, and the ability to restore a database to the server and to create a database user.

Installing the code

  1. Download the sources and unzip into a directory.
  2. You'll find the sources for a simple ASP.NET 2.0 website, and a database backup file, CrystalReportIntro.bak.

The article will talk you through creating the website, so the website sources are for convenience only. However, you do need to install the database:

  1. Restore CrystalReportIntro.bak into your database server as database "CrystalReportIntro".
  2. Create a login "mp" with password "crystal2007".
  3. On the database "CrystalReportIntro", create a user "mp" based on login "mp".
  4. Have a look at the database. You'll find it has just one table "Sales" with simple sales information, and a View "vSales" - based on table "Sales" - that adds a calculated column with sales totals. The report will be based on this View.

I. Create a blank website

Create a new ASP.NET website, with language Visual C#.

II. Create the Sales.rpt definition file

The Sales.rpt file will hold the definition of the report. Further down, you'll see how to deploy that definition on one of your web pages.

  1. Add a new item to the web site.
  2. In the list of templates, choose Crystal Report.
  3. Call it Sales.rpt and click Add.
  4. Dismiss the registration popup if you get one.
  5. On the Crystal Reports Gallery popup, choose to use the report wizard and the Standard expert. Click OK.
  6. On the following page, expand the "Create new connection" section.
  7. Then, expand the "OLE DB (ADO)" section. A dialog will pop up.
  8. Choose "SQL Native Client" as your provider. Leave "Use Data Link File" unchecked. Click Next.
  9. Enter the name of your database server. Set the username to "mp", the password to "crystal2007", and the name of the database to "CrystalReportIntro".
  10. There is one more page, but it has advanced stuff we don't need. Click Finish.
  11. You're now back at the Report Creation Wizard. It lets you drill down to the database server and the database you just added, and onto the tables, Views, etc. in the database.
  12. Drill down until you see the View "vSales". Move it to the right hand field. Click Next.

  13. You now get to choose what fields to show in your report.
  14. Click the little plus next to "vSales" to see the individual fields, and move all fields over to the right. However, you won't need the ID field, so move that back to the left. Hit Next.

  15. This page lets you add grouping on a particular field. The report will have grouping, and will show sales totals per group. To get Crystal Reports to add grouping right away, specify grouping on one of the fields. Later, you'll see how to change the grouping field dynamically in C# code.
  16. Move the "ProductName" field (under Report Fields) to the right hand box. Hit Next.

  17. It now lets you specify summaries. Trying to be helpful, Crystal Reports has already specified a number of fields for you. For this example though, only do summation on the Total field.
  18. Move all fields in the right hand box, except for the Total field, back to the left. Hit next.

  19. Hit Next three times to skip through the Group Sorting page and the Chart page and the Record Selection page.
  20. Choose a Report Style you like. Personally, I tend to go for the "Red/Blue Border" style. Hit Finish.
  21. The report definition file Sales.rpt should now open, showing your report.

III. Add the report to your Default.aspx page

Now that we have the report definition, let's add it to an ASP.NET page.

  1. Open Default.aspx in design mode.
  2. Expand the Crystal Reports section in the toolbox.
  3. Drag a CrystalReportViewer control onto the page. You'll see that it adds the CrystalReportViewer control.
  4. <CR:CrystalReportViewer ID="CrystalReportViewer1" 
        runat="server" AutoDataBind="true" />
  5. Hit F5 to run Default.aspx. You'll get just a blank page. Some code needs to be added to bind the Sales.rpt file to the CrystalReportViewer control on the page.
  6. First, add a few assemblies to Default.aspx.cs that support Crystal Reports:
  7. using CrystalDecisions.CrystalReports.Engine;
    using CrystalDecisions.Shared;
    
    public partial class _Default : System.Web.UI.Page 
    {
  8. Add a method CreateCrystalReportDocument that builds a ReportDocument object. It is this object that will tell the CrystalReportViewer control on the page where to find the report definition, what record selection and grouping to use, etc.
  9. public partial class _Default : System.Web.UI.Page 
    {
        private const string sourceTableOrView = "vSales";
    
        // -------------------------------------
    
        //
    
        private ReportDocument CreateCrystalReportDocument(
                                    string filterString,
                                    string groupFieldName)
        {
            ReportDocument rpt = new ReportDocument();
    
            // ---- Load the report definition
    
            string reportPath = Server.MapPath("Sales.rpt");
            rpt.Load(reportPath);
    
            // ---- Assign connection information for each table in the database
    
            // Build connection info
    
            ConnectionInfo connectionInfo = new ConnectionInfo();
            connectionInfo.ServerName = @"YOUR DATABASE SERVER NAME";
            connectionInfo.DatabaseName = "CrystalReportIntro";
            connectionInfo.UserID = "mp";
            connectionInfo.Password = "crystal2007";
    
            // Assign to all tables used by the report
    
            Tables tables = rpt.Database.Tables;
            foreach (CrystalDecisions.CrystalReports.Engine.Table table in tables)
            {
                TableLogOnInfo tableLogonInfo = table.LogOnInfo;
                tableLogonInfo.ConnectionInfo = connectionInfo;
                table.ApplyLogOnInfo(tableLogonInfo);
            }
    
            // ---- Set the record selection. If filterString
            //      is null, all records will be selected.
    
            rpt.DataDefinition.RecordSelectionFormula = filterString;
    
            // ---- Set grouping field
    
    
            // We'll set the first level of grouping (group 0)
    
            Group group = rpt.DataDefinition.Groups[0];
    
            // We'll be grouping on a field in vSales
            // ("vSales" is the value of sourceTableOrView)
    
            CrystalDecisions.CrystalReports.Engine.Table groupFieldTable = 
                             rpt.Database.Tables[sourceTableOrView];
    
            // Assign the field whose name is passed
            // into this function via parameter groupFieldName
    
            group.ConditionField = groupFieldTable.Fields[groupFieldName];
    
            return rpt;
        }

    Don't forget to replace the string "YOUR DATABASE SERVER NAME" with the name of your database server.

    In a real application, you'll want to structure this a bit better, factoring out methods, etc. The code here is structured to make it easy to follow.

    1. CreateCrystalReportDocument starts by loading the report definition contained in the Sales.rpt file.
    2. It then tells the report how to connect to the database. A Crystal Reports report has connection information per table - this way, your report can show data from different databases. So, the code loops through all tables used by the report to assign the connection information.
    3. It then sets the record selection string, as passed in to the function via the parameter filterString. Later on, you'll see this is basically a SQL WHERE clause.
    4. Finally, it sets the field on which to group the data, as passed in to the function via the parameter groupFieldName. The code keeps it simple, and only sets one level of grouping (group 0). And, it assumes that the group field will always come from our view, "vSales". It would be easy to have additional grouping levels, and use fields from different tables.
  10. Finally, in the Page_Init method, assign the ReportDocument object that was made by CreateCrystalReportDocument to the CrystalReportViewer control. Pass in null for filterString (to show all records without filtering) and ProductName as the field to group on. Later, we'll do something more useful with this.
  11.     private void Page_Init(object sender, EventArgs e)
        {
            ReportDocument rpt = null;
    
            rpt = CreateCrystalReportDocument(null, "ProductName");
            CrystalReportViewer1.ReportSource = rpt;
        }

    It needs to go in Page_Init rather than Page_Load due to technical issues with the Crystal Report software.

  12. Hit F5 again to run your site. The default page will appear with the report, grouped by Product Name.
  13. Click the Export button to the far left in the grey bar (next to the Print button). You'll find that you now have the ability to export your report to lots of formats, including PDF and Excel files. Cool, eh?

IV. Add dynamic filtering and grouping

  1. First, lay some groundwork by adding a number of input fields to Default.aspx to set grouping and filters.
  2.     <form id="form1" runat="server">
        <div>
        
        <p>
            Filter
            <blockquote>
            <table>
            <tr><td>Product Name:</td>
            <td><asp:TextBox ID="tbProductName" runat="server" /></td></tr>
            <tr><td>Price:</td>
            <td><asp:TextBox ID="tbPrice" runat="server" /></td></tr>
            <tr><td>Nbr Sold:</td>
            <td><asp:TextBox ID="tbNbrSold" runat="server" /></td></tr>
            <tr><td>Sold In City:</td>
            <td><asp:TextBox ID="tbSoldInCity" runat="server" /></td></tr>
            </table>
            </blockquote>
        </p>
        
        <p>
            Group on: 
            <asp:DropDownList ID="ddlGrouping" runat="server">
              <asp:ListItem Selected="True" Value="ProductName">Product Name</asp:ListItem>
              <asp:ListItem Selected="False" Value="Price">Price</asp:ListItem>
              <asp:ListItem Selected="False" Value="NbrSold">Nbr Sold</asp:ListItem>
              <asp:ListItem Selected="False" Value="SoldInCity">Sold In City</asp:ListItem>
            </asp:DropDownList>
        </p>
        
        <p>
            <asp:Button ID="btnReload" runat="server" 
                 Text="Reload Report" OnClick="btnReload_Click" />
        </p>
        
        <CR:CrystalReportViewer ID="CrystalReportViewer1" 
                     runat="server" AutoDataBind="true" />

    The TextBoxes will be used to filter on one or more fields. If a TextBox is left blank, it won't participate in the filter. If you enter a value, that value will be added to the filter. Great if you only want to see sales in Boston with item price $3.50.

    To keep things simple, the filter only tests on equality (= operator). It wouldn't be hard to add more options, such as Like (for strings), greater than, and less than.

    The dropdown box will let you specify the field to do grouping on.

  3. Back in Default.aspx.cs, add a utility function AddFilter to help build a filter expression.
  4.     private void AddFilter(
                            ref string filterString,
                            string databaseFieldName,
                            TextBox fieldInput,
                            string valueQuoteChar)
        {
            string fieldValue = fieldInput.Text.Trim();
    
            // If input field left blank, don't filter on it.
    
            if (fieldValue == "") return;
    
            // Escape for quotes if we're going to quote the value
    
            string escapedFieldValue = fieldValue;
            if (valueQuoteChar == @"'")
                escapedFieldValue = fieldValue.Replace(@"'", @"''");
    
            // Add boolean clause to the complete filter expression
    
            // This method always uses operator =
    
            string booleanSqlExpression =
                "{" + sourceTableOrView + "." + databaseFieldName + "} = " +
                valueQuoteChar +
                escapedFieldValue +
                valueQuoteChar;
    
            // separate boolean clauses with AND operator
    
            if (!string.IsNullOrEmpty(filterString)) filterString += " AND ";
            filterString += booleanSqlExpression;
        }

    It takes one of the TextBoxes you just added to Default.aspx, the corresponding database field, and a quote character (used for string values), generates a boolean clause, and adds it to whatever is already in the parameter filterString.

    The code it generates is pretty much the same as you'd use in a SQL WHERE, except that field names are within curly braces { }.

  5. Finally, add the click handler btnReload_Click for the Reload button.
  6.     protected void btnReload_Click(object sender, EventArgs e)
        {
            // Create filter expression, based on the filter input fields.
    
            // If an input field is left blank, it isn't used in the filter expression.
    
            string filterString = null;
            AddFilter(ref filterString, "ProductName", tbProductName, @"'");
            AddFilter(ref filterString, "Price", tbPrice, "");
            AddFilter(ref filterString, "NbrSold", tbNbrSold, "");
            AddFilter(ref filterString, "SoldInCity", tbSoldInCity, @"'");
    
            // Get the name of the field to group on
    
            string groupFieldName = ddlGrouping.SelectedValue;
    
            // Create report document based on the new filter and grouping, and assign to 
    
            // CrystalReportViewer control. This will show the new report.
    
    
            ReportDocument rpt = 
                CreateCrystalReportDocument(filterString, groupFieldName);
    
            CrystalReportViewer1.ReportSource = rpt;
        }

    It simply uses AddFilter to build a filter string, gets the group field name from the dropdown, and then creates a new report using CreateCrystalReportDocument.

  7. Hit F5 to run the website again. Try grouping on different fields. Put Toy in the Product Name filter field to see only toy sales, etc.

V. Hang on to report settings after page reload

  1. While the page is running, in the grey horizontal box, you'll see a zoom drop down - it's the one saying 100%. Change it to 150%. The report will reload with a bigger font, but it will have lost any filtering and grouping! This is because the report settings aren't stored in ViewState. Some code is needed to hang on to the settings when the page reloads.
  2. You may be wondering whether adding:

    if (!Page.IsPostBack) { .... }

    to Page_Init will help by stopping it from re-initialising the CrystalReportViewer. Give it a try - the report won't load at all when the page reloads after changing the zoom.

    Another approach could have been to have the Page_Init construct a report document with the right filter string and grouping, based on the filter textboxes and the grouping dropdown. However, the value of those controls is not yet available when Page_Init executes.

    A simple approach that does work is to store the report document in a Session object.

  3. Update Page_Init so it stores the report document in a Session object "SalesRpt". Instead of always regenerating the report document, it now loads it from the Session object, if it is there.
  4.     private void Page_Init(object sender, EventArgs e)
        {
            ReportDocument rpt = null;
    
            // Load the report document from the Session object.
    
            // If it's not there, generate a new report document.
    
            if (Session["SalesRpt"] == null)
            {
                rpt = CreateCrystalReportDocument(null, "ProductName");
                Session["SalesRpt"] = rpt;
            }
            else
            {
                rpt = (ReportDocument)Session["SalesRpt"];
            }
    
            CrystalReportViewer1.ReportSource = rpt;
        }
  5. Also update btnReload_Click, so it updates the Session object with the new report document it has just generated - so Page_Init will pick it up at the next page reload.
  6.     protected void btnReload_Click(object sender, EventArgs e)
        {
            // Create filter expression, based on the filter input fields.
    
            // If an input field is left blank, it isn't used in the filter expression.
    
            string filterString = null;
            AddFilter(ref filterString, "ProductName", 
                      tbProductName, @"'");
            AddFilter(ref filterString, "Price", tbPrice, "");
            AddFilter(ref filterString, "NbrSold", tbNbrSold, "");
            AddFilter(ref filterString, "SoldInCity", 
                      tbSoldInCity, @"'");
    
            // Get the name of the field to group on
    
            string groupFieldName = ddlGrouping.SelectedValue;
    
            // Create report document based on the new filter and grouping, and assign to 
    
            // CrystalReportViewer control. This will show the new report.
    
    
            ReportDocument rpt = 
                CreateCrystalReportDocument(filterString, groupFieldName);
    
            CrystalReportViewer1.ReportSource = rpt;
    
            // Store newly generated report document
            // in Session object, so Page_Init will pick it up.
    
            Session["SalesRpt"] = rpt;
        }
  7. Hit F5 to run the page again. Set a filter and reload the report. Now, change the zoom - the zoom will change, but the filtering and grouping on the report will remain.

VI. Fix up the headers and columns

Some of the elements on the report don't look all that good - little things like a header "ProductName" instead of "Product Name". Fortunately, changing the appearance of a report is very simple.

  1. Open the Sales.rpt file in Visual Studio. It allows you to edit the report - change text, move fields, etc.
  2. Double click in the ProductName header. It will let you insert a space between "Product" and "Name".
  3. Crystal Reports probably put the sales total per group in the wrong place. Click on it, and drag to a better place.
  4. Right click a field and click "Format Object" to change colors, borders, etc.

VII. Add drill down to the report

With drill down, the report starts off only showing group names and group totals. This gives users a nice overview without overwhelming them with detail. They can then drill down to the details of a group by simply clicking the group name.

  1. Open Sales.rpt. Right click on the grey horizontal bar saying "Details", and choose "Hide (drill down OK)".
  2. Hit F5 to run the page again. Click on one of the group headers (for example, Toys) - a new page will open with the individual sales for that group. Use the drop down on the grey horizontal bar to open the main report again.
  3. Note that the main report looks a bit odd.
    • The headers really only make sense in the drilled down state, not on the main report.
    • But, when you drill down, the headers disappear!
    • Each group header is shown twice.

    Let's fix this now.

  4. To hang on to the headers while drilling down:
    • Go back to Sales.rpt (in Visual Studio).
    • Right click on the report (in Visual Studio).
    • Hover on "Report" to get the Report submenu to appear.
    • Click Report Options.
    • Check "Show All Headers on drill-down".
    • Click OK.
  5. Get rid of the duplicate group name. In Sales.rpt, you'll see the group name field twice (it says "Group #1 Name") - once above the detail section, once below it. Get rid of the group name field above the detail section - click on it to select it, then hit the Delete button.
  6. Finally, ensure that the headers are only shown while drilling down:
    • Drag all headers from the top of the report down to the GroupHeaderSection1 (just above the Details section). This is the same area from which you just deleted the duplicate group name field.
    • Right click on the grey horizontal bar saying "GroupHeaderSection1", and choose "Hide (drill down OK)".
  7. Done! Run the report again. The group headers should now show up only once with their totals. And, the headers will only appear when you click a header to drill down.

Conclusion

Crystal Reports, which is included free with Visual Studio, lets you quickly add reports to any web site that uses a database.

This article has shown how to do this step by step, and how to allow users to filter records, choose on what field to group data, and how to provide drill down capability.

History

  • This is version 1.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here