Dim customAssembly As Assembly = Assembly.Load(customAssemblyName)
Dim assemblyStrongName As StrongName = CreateStrongName(customAssembly)
reportEngine.AddFullTrustModuleInSandboxAppDomain(assemblyStrongName)
c#
string customAssemblyName = "CustomReportingFunctions, Version=1.0.0.0, " +
"Culture=neutral, PublicKeyToken=b9c8e588f9750854";
Assembly customAssembly = Assembly.Load(customAssemblyName);
StrongName assemblyStrongName = CreateStrongName(customAssembly);
reportEngine.AddFullTrustModuleInSandboxAppDomain(assemblyStrongName);
fiGure 30-30
Designing reports .
667
There are two things you will note from this code. The first is that you are loading the custom assembly
from the GAC using its name (in order to obtain its strong name so you can notify the reporting engine
that it’s trusted), including its version, culture, and public key token. This string can be obtained by
copying it from where you added the assembly reference to the report in its Report Properties dialog box.
The second is the use of the GetStrongName function to return the StrongName object, the code for
which is below:
Vb
Private Shared Function CreateStrongName(ByVal assembly As Assembly) As StrongName
Dim assemblyName As AssemblyName = assembly.GetName()
If assemblyName Is Nothing Then
Throw New InvalidOperationException("Could not get assembly name")
End If
' Get the public key blob
Dim publicKey As Byte() = assemblyName.GetPublicKey()
If publicKey Is Nothing OrElse publicKey.Length = 0 Then
Throw New InvalidOperationException("Assembly is not strongly named")
End If
Dim keyBlob As New StrongNamePublicKeyBlob(publicKey)
' Finally create the StrongName
Return New StrongName(keyBlob, assemblyName.Name, assemblyName.Version)
End Function
Code snippet CreateStrongName.vb
c#
private static StrongName CreateStrongName(Assembly assembly)
{
AssemblyName assemblyName = assembly.GetName();
if (assemblyName == null)
throw new InvalidOperationException("Could not get assembly name");
// Get the public key blob
byte[] publicKey = assemblyName.GetPublicKey();
if (publicKey == null || publicKey.Length == 0)
throw new InvalidOperationException("Assembly is not strongly named");
StrongNamePublicKeyBlob keyBlob = new StrongNamePublicKeyBlob(publicKey);
// Finally create the StrongName
return new StrongName(keyBlob, assemblyName.Name, assemblyName.Version);
}
Code snippet CreateStrongName.cs
668 .
chaPter 30 reporTing
Now when you run the report you will have the same output as when you embedded the code in the
report, but in a more reusable and maintainable form.
report layout
Generally reports are produced in order to be printed, therefore you must consider how the
printed report will look in your report design. The first thing to ensure is that the dimensions of
your report match the paper size that it will be printed on. Open the Report Properties window
via the Report . Report Properties menu. The selected tab will be the Page Setup tab, from which
you can select the paper size, the margins, and the orientation of the page (portrait or landscape).
Many reports tend to extend beyond one page, and it can be useful to show something at the
top and bottom of each page to show which company and report it belongs to, and where that
page belongs within the report (in case the pages are dropped, for example). So far you have been
dealing just with the body of the report, but you can add a page header and footer to the report
to use for these purposes. Page headers tend to be used for displaying the company logo, name,
and information about the company (like a letterhead). Page footers tend to be used to display
page numbers, the report title, and perhaps some totals for the information displayed on that page.
Add a page header to your report via the
Report . Add Page Header menu command.
This adds a page header area in the
report designer above the report body (see
Figure 30-31), which you can resize to your
needs, and upon which you can place various
controls such as textboxes and images. You
can even place other controls such as a Table
or Gauge, although it’s rare to do so. If
you drag a field from the Report Data tool
window directly onto the page header you will note that it creates a complex expression (as it does
on the report body), so add a table first if you want to display some totals, for example.
Adding a page footer is much the same
process. Select the Report . Add Page Footer
menu to add a page footer area in the report
designer below the body of the report
(see Figure 30 - 32 ).
You can use the built-in report fields to
display information such as the page number,
number of pages, report name, the time the
report was generated, and so on, which can be used anywhere in your report. You can find them in
the Report Data tool window, under the Built-in Fields category.
fiGure 30-31
fiGure 30-32
The value for the Report Name field is retrieved from the filename of the report
with the extension removed.
Designing reports .
669
Generally you will want to show the page numbers in the form as Page 1 of 6. However, the page
number and page count fields are separate, so it’s best to drop a textbox in the footer and drop both
fields in that:
Page [ & PageNumber] of [ & TotalPages]
The values in the square brackets will automatically turn into placeholders with the correct
expressions behind them (the & specifies that these are global variable references) that get the values
from the built-in fields. You can alternatively drag these fields from the Report Data tool window
into the textbox and add the static text in between.
Be careful that you don’t remove the page header or footer once you’ve created
it (by selecting Remove Page Header or Remove Page Footer from the Report
menu) because this will delete the content of the header/footer and adding it
back again won’t restore its content. There is no warning displayed when you
do this, so if you do so by accident use the Undo function to restore it to its
previous state.
One question you may now have is how to create report headers and footers (that only appear on
the first/last page of the report, rather than each page). An example of a report header would be to
display the title of the report and other report information at the top of the report (on the first page
only), and an example of a report footer would be to display some totals at the end of the report (on
the last page only).
The report designer doesn’t support report headers/footers as special areas of the report in the same
way it does for page headers/footers because you can simply include them in the body of the report.
By putting the report header content at the top of the body of your report it will only display once,
then it will display the content (which may expand to cover multiple pages), and finally at the
bottom of your report you can put the report footer content. The only issue to deal with is that you
won’t want the page header on the first page of your report (because you will only want the report
header), and you won’t want the page footer on the last page (because you will only want the report
footer). To do this, right-click your report header and select Header Properties from the menu. From
the General tab (which will be the one selected), uncheck the Print on First Page checkbox. The
process is much the same for the page footer: right-click your report footer, select Footer Properties
from the menu, and then uncheck the Print on Last Page checkbox.
The final thing you must consider with your report layout is where the page breaks will occur.
For example, you may want a table to appear all on the same page where possible rather than
half on one page and half on another. Or perhaps you have its data grouped, and you want each
group to start on a new page. You can do this by setting page break options on the controls that
support them (Table, Matrix, List, Rectangle, Gauge, and Chart). Each of these controls has the
PageBreak property (select the control in the report designer and find the property in the Properties
tool window). This gives you the option to start a new page before it displays the control, after it
displays the control, or both before and after it displays the control. You can set KeepTogether to
true so that if the output of the control stretches across two pages it will attempt to display it all on
670 .
chaPter 30 reporTing
the one page by starting it on the next page instead. When you are grouping data in a table, matrix,
or list, you can also set the page break options for the group. When you view the properties of a
group (right-click the group in the Row Groups pane at the bottom of the designer and select Group
Properties from the menu) you will note a Page Breaks tab. Here you can select whether there should
be a page break between each group, and you can also select whether there should be an additional
page break before and/or after each group.
subreports
Subreports is a feature that enables you to insert the contents of one report into another. You can
insert the contents (excluding headers and footers) of any report into another by adding a Subreport
control to your main report and setting its ReportPath property to the path of the other report
to display in that area. By merging a number of reports into a single output report you are able
to create quite complex report structures. Other uses of subreports include creating master-detail
reports, drill-down reports, and splitting reports into predefined “components” that can be used by
multiple reports — enabling each component to be defined once and used multiple times. This also
has the advantage that changes can be made in a single place and automatically picked up by the
other reports (such as a standard report header with company information, used by all the reports).
First look at a scenario where the contents of the subreport are not linked to the “master” report.
Create a new report, and simply put a textbox on it with some text. Now add a Subreport control to
your main report, and set the ReportName property to the filename of the other report (but without
the extension).
Unfortunately the report to be used as the subreport must be located in the same
folder as the main report.
When you run the project and view the report you will see that the contents of the subreport are
merged into the main report.
Getting a little more complicated now, hook up a data source to the subreport and show some data
in it (in a standalone fashion from the main report). The issue now is, because the data sources
aren’t shared between the main report and the subreport, how do you pass the data to that report?
You do this by handling the SubreportProcessing event on the LocalReport object in the code
that configures the Report Viewer control (discussed in full later in this chapter). You will need to
add an event handler for this event like so:
Vb
AddHandler reportViewer.LocalReport.SubreportProcessing, AddressOf ProcessSubreport
c#
reportViewer.LocalReport.SubreportProcessing += ProcessSubreport;
Designing reports .
671
and add a function for this event handler that adds the data to the
SubreportProcessingEventArgs object passed in as a parameter (including the name of the
dataset), like so:
Vb
Private Sub ProcessSubreport(ByVal sender As System.Object,
ByVal e As SubreportProcessingEventArgs)
e.DataSources.Add(New ReportDataSource("DataSetName", data))
End Sub
c#
private void ProcessSubreport(object sender, SubreportProcessingEventArgs e)
{
e.DataSources.Add(new ReportDataSource("DataSetName", data));
}
When you run the project now the subreport will be populated with data.
Now take a look at the slightly more complex scenario where what is displayed in the subreport is
dependent on data in the main report. Say, for example, the main report is displaying the details
of each customer, but you also want to show the orders each customer made in the last month
underneath their details using a subreport. So that the subreport knows which customer to retrieve
the order details for, you need to make use of Report Parameters.
Note that there are a lot of overheads in implementing this scenario in this way.
There will be multiple calls to the database — one for each customer to return
their order details, which will put strain on the database server. A better, more
efficient way for this scenario would be to return a joined customer details
+ orders dataset from the database, and use the Table control to group by
customer and display their order details. However, this scenario is just used as
an example of how to pass information from the main report to subreports.
Create a report (which will be the main report) to display the details of each customer (in a list),
and another report (the subreport) that displays the orders that a customer has made. Under the
customer details fields (but still in the list), add a Subreport control that points to the subreport
you created, and hook up the code-behind as previously described. What you will note is that
when handling the SubreportProcessing event to return the order details data to the subreport,
you need to know which customer to return the data for (the subreport will be rendered for each
customer, therefore this event handler will be called to return the order details for each customer).
This is where you need to create a Report Parameter for the subreport that the main report will use
to pass the current customer’s ID to it.
To add a new parameter to the subreport, go to the Report Data tool window, right-click the
Parameters folder, and select Add Parameter from the menu. Create the parameter with CustomerID
as its name, and set its data type to Integer.
672
.
chaPter 30 reporTing