Dynamic Web Reports
Dynamic Web reports are created on-demand, possibly based on data supplied by the user. This type of solution typically involves using an ASP.NET page that presents a form to the user and collects the information needed to create the report, then creates a C1Report component to render the report into a temporary file, and returns a reference to that file.
The example that follows is a simple ASP.NET page that allows users to enter some information and to select the type of report they want. Based on this, the ASP code creates a custom version of the NorthWind "Employee Sales by Country" report and presents it to the user in the selected format.
The sample uses a temporary file on the server to store the report. In a real application, you would have to generate unique file names and delete them after a certain time, to avoid overwriting reports before the users get a chance to see them. Despite this, the sample illustrates the main techniques involved in delivering reports over the Web with C1Report.
To implement this type of application, follow these steps:
1. Start by creating a new Web application with a Web page that looks like this:
The page has five server controls:
• _1stYear: Contains a list of valid years for which there is data (1994, 1995, and 1996). Note that you can add the items by clicking the smart tag () and selecting Edit Items from the menu. From the ListItem Collection Editor, add three new items.
• _txtGoal: Contains the yearly sales goal for each employee.
• _btnHTML, _btnPDF: Buttons used to render the report into HTML or PDF, and show the result.
• _lblStatus: Displays error information if something goes wrong.
Note: If you run this application with a demo or beta version of the C1Report component, there will be errors when the control tries to display its About dialog box on the server. If that happens, simply reload the page and the problem should go away.
2. After the page has been set up, you need to add a reference to the C1Report component to the project. Just right-click the project in the Solution Explorer window, select Add Reference and choose the C1Report component.
3. Add the Nwind.xml to the project in the Data folder. Just right-click the project in the Solution Explorer window, select New Folder and rename the folder Data. Then right-click the folder, select Add Existing Item and browse for the Nwind.xml definition file, which is installed by default in the ComponentOne Samples\C1Report\C1Report\VB\NorthWind\Data directory in the Documents or My Documents folder.
4. Add a Temp folder to the project. Just right-click the project in the Solution Explorer window, select New Folder and rename the folder Temp.
5. If you have used traditional ASP, this is where things start to become interesting. Double-clicking the controls will take you to a code window where you can write full-fledged code to handle the events, using the same editor and environment you use to write Windows Forms projects.
Add the following code:
Imports C1.C1Report
…
' handle user clicks
Private Sub _btnHTML_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles _btnHTML.Click
RenderReport(FileFormatEnum.HTMLDrillDown)
End Sub
Private Sub _btnPDF_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles _btnPDF.Click
RenderReport(FileFormatEnum.PDF)
End Sub
• C#
using C1.C1Report;
…
// handle user clicks
private void _btnHTML_Click(object sender, System.EventArgs e)
{
RenderReport(FileFormatEnum.HTMLDrillDown);
}
private void _btnPDF_Click(object sender, System.EventArgs e)
{
RenderReport(FileFormatEnum.PDF);
}
This is the code that gets executed on the server, when the user clicks either button.
6. The following code delegates the work to the main routine, RenderReport:
Private Sub RenderReport(ByVal fmt As FileFormatEnum)
' build file names
Dim rpt As String = "Employee Sales by Country"
Dim fileIn As String = GetDataPath() & "NWind.xml"
Dim ext As String = Iif(fmt = FileFormatEnum.PDF, ".pdf", ".htm")
Dim fileOut As String = GetOutputPath() & rpt & ext
Try
' create C1Report component
Dim c1r As New C1Report()
' load the report
c1r.Load(fileIn, rpt)
' get user parameters
Dim year As String = _lstYear.SelectedItem.Text
Dim goal As String = _txtGoal.Text
' customize the report data source
Dim sSQL As String = "SELECT DISTINCTROW " & _
"Employees.Country, Employees.LastName, " & _
"Employees.FirstName, Orders.ShippedDate, Orders.OrderID, " & _
" [Order Subtotals].Subtotal AS SaleAmount " & _
"FROM Employees INNER JOIN (Orders INNER JOIN " & _
" [Order Subtotals] ON Orders.OrderID = " & _
" [Order Subtotals].OrderID) " & _
" ON Employees.EmployeeID = Orders.EmployeeID " & _
"WHERE Year(Orders.ShippedDate) = " & year & ";"
c1r.DataSource.RecordSource = sSQL
' customize the report's event handlers
Dim sScript As String = _
"If SalespersonTotal > " & goal & " Then" & vbCrLf & _
" ExceededGoalLabel.Visible = True" & vbCrLf & _
" SalespersonLine.Visible = True" & vbCrLf & _
"Else" & vbCrLf & _
" ExceededGoalLabel.Visible = False" & vbCrLf & _
" SalespersonLine.Visible = False" & vbCrLf & _
"End If"
c1r.Sections(SectionTypeEnum.GroupHeader2).OnPrint = sScript
' render the report into a temporary file
c1r.RenderToFile(fileOut, fmt)
' redirect user to report file
Response.Redirect("Temp/" + rpt + ext)
Catch x As Exception
_lblStatus.Text = "*** " & x.Message
End Try
End Sub
• C#
// render the report
private void RenderReport(FileFormatEnum fmt)
{
// build file names
string rpt = "Employee Sales by Country";
string fileIn = GetDataPath() + "NWind.xml";
string ext = (fmt == FileFormatEnum.PDF)? ".pdf": ".htm";
string fileOut = GetOutputPath() + rpt + ext;
try
{
// create C1Report component
C1Report c1r = new C1Report();
// load the report
c1r.Load(fileIn, rpt);
// get user parameters
string year = _lstYear.SelectedItem.Text;
string goal = _txtGoal.Text;
// customize the report data source
string sSQL = "SELECT DISTINCTROW " +
"Employees.Country, Employees.LastName, " +
"Employees.FirstName, Orders.ShippedDate, Orders.OrderID, " +
" [Order Subtotals].Subtotal AS SaleAmount " +
"FROM Employees INNER JOIN (Orders INNER JOIN " +
" [Order Subtotals] ON Orders.OrderID = " +
" [Order Subtotals].OrderID) " +
" ON Employees.EmployeeID = Orders.EmployeeID " +
"WHERE Year(Orders.ShippedDate) = " + year + ";";
c1r.DataSource.RecordSource = sSQL;
// customize the report's event handlers
string sScript =
"If SalespersonTotal > " + goal + " Then \n" +
" ExceededGoalLabel.Visible = True\n" +
" SalespersonLine.Visible = True\n" +
"Else\n" +
" ExceededGoalLabel.Visible = False\n" +
" SalespersonLine.Visible = False\n" +
"End If";
c1r.Sections[SectionTypeEnum.GroupHeader2].OnPrint = sScript;
// render the report into a temporary file
c1r.RenderToFile(fileOut, fmt);
// redirect user to report file
Response.Redirect("Temp/" + rpt + ext);
}
catch (Exception x)
{
_lblStatus.Text = "*** " + x.Message;
}
}
The RenderReport routine is long, but pretty simple. It begins working out the names of the input and output files. All file names are built relative to the current application directory.
Next, the routine creates a C1Report component and loads the "Employee Sales by Country" report. This is the raw report, which you will customize in the next step.
The parameters entered by the user are available in the _lstYear and _txtGoal server-side controls. The code reads these values and uses them to customize the report's RecordSource property and to build a VBScript handler for the OnPrint property. These techniques were discussed in previous sections.
Once the report definition is ready, the code calls the RenderToFile method, which causes the C1Report component to write HTML or PDF files to the output directory. When the method returns, the report is ready to be displayed to the user.
The last step is the call to Response.Redirect, which displays the report you just created on the user's browser.
Note that the whole code is enclosed in a try/catch block. If anything goes wrong while the report is being generated, the user gets to see a message explaining the problem.
7. Finally, there's a couple of simple helper routines that need to be added:
' get directories to use for loading and saving files
Private Function GetDataPath() As String
Return Request.PhysicalApplicationPath + "Data\"
End Function
Private Function GetOutputPath() As String
Return Request.PhysicalApplicationPath + "Temp\"
End Function
• C#
// get directories to use for loading and saving files
private string GetDataPath()
{
return Request.PhysicalApplicationPath + @"Data\";
}
private string GetOutputPath()
{
return Request.PhysicalApplicationPath + @"Temp\";
}
8. After you enter this code, the application is ready. You can press F5 and trace its execution within Visual Studio.
The following screen shot shows what the result looks like in the browser:
|