So I ran into a couple of different issues when setting up advanced reporting scenarios with reporting services. While reporting services runs fairly well in the server report situations but lacks flexibility in application integration scenarios. Having to hard code datasets for each report and hard code fill and get methods seemed to lack flair to me. I wanted a more robust and elegant solution. So I came up with an idea that allowed me to take preset report segments and load them dynamically into a report definition before passing it to the renderer.
You start by setting up a blank RDLC with header and footer. This allows you to keep formatting the same through the report. You can use global variables or parameters in the header and the footers. Most of the time this will be standard for the application and template for the company.

You load the RDLC from a file stream into a XDocument.
private Stream Load_FileStream()
{
Stream MasterReport =
new FileStream(Server.MapPath("~/Report/Master.rdlc"),FileMode.Open,FileAccess.Read);
return MasterReport;
}
Using LINQ you can search the nodes for the report items node in the body. This is where the guts of the report is stored.
private Stream PrepReport()
{
XDocument ReportDoc = XDocument.Load(XmlReader.Create(Load_FileStream()));
IEnumerable<XNode> hold = ReportDoc.DescendantNodes().InDocumentOrder();
XElement BodyItems =
(XElement) hold.Where(n =>
n.GetType() != typeof (XText) &&
((XElement) n).Parent != null &&
((XElement) n).Name.LocalName.ToUpper() == "REPORTITEMS" &&
((XElement) n).Parent.Name.LocalName.ToUpper() == "BODY").SingleOrDefault();
BodyItems.Add(NewNodes(ReportDoc.Root.Name.Namespace));
return CreateMemoryStream(ReportDoc);
}
You can then use LINQ to XML to insert a new node package to add tables, sub-reports, or graphs. In this example I add a sub-report with a parameter passed to it.
private XElement NewNodes(XNamespace @namespace)
{
var holder = new XElement(@namespace + "Subreport",
new object[] {
new XAttribute("Name", "Subreport1"),
new XElement(@namespace + "Parameters",
new XElement(@namespace + "Parameter",
new object[]{
new XElement(@namespace + "Value","=Parameter!Period.Value"),
new XAttribute("Name", "Period")
})),
new XElement(@namespace + "ReportName", "NameHere"),
new XElement(@namespace + "Top", "0.25in"),
new XElement(@namespace + "Width", "7.25in"),
new XElement(@namespace + "Left", "0.125in"),
new XElement(@namespace + "Height", "0.25in")
});
return holder;
}
The width value will change for the orientation of the paper, and the top will change based on how many reports you insert. The height is set to a quarter inch because sub-reports auto expand to the content in them. All that is left now is to send the Report the a memory stream and into the renderer.
private Stream CreateMemoryStream(XDocument doc)
{
MemoryStream memStream = new MemoryStream();
XmlWriter writer = XmlWriter.Create(memStream);
if (writer != null)
{
doc.Save(writer);
writer.Close();
}
memStream.Position = 0;
return memStream;
}
This will package the report file into a memory stream and return it. Make sure you reset the position or the read will start at the end and give an invalid RDLC error. Once packaged you provide the UI to this the same way you would with a static report in an application. The example here is ASP.net.
That will provide the basis for an ad-hoc report even if your own unique twist on a private report integration.
Happy coding, and remember to code like you have to support it.
The full code sample looks like this.
1: private void GenerateReport()
2: { 3: ReportViewer1.LocalReport.LoadReportDefinition(PrepReport());
4: ReportViewer1.LocalReport.DisplayName = "Set what you want the export file name to be.";
5: ReportViewer1.LocalReport.LoadSubreportDefinition("SubReport", 6: new FileStream(Server.MapPath("~/Report/Subreport.rdlc"), 7: FileMode.Open, FileAccess.Read));
8: ReportViewer1.ReportError += ReportError;
9: ReportViewer1.LocalReport.SubreportProcessing += LocalReport_SubreportProcessing;
10: ReportViewer1.LocalReport.Refresh();
11: ReportViewer1.ZoomMode = ZoomMode.PageWidth;
12: ReportViewer1.Focus();
13: }
14: private void LocalReport_SubreportProcessing(object sender,
15: Microsoft.Reporting.WebForms.SubreportProcessingEventArgs e)
16: { 17: //This is where you provide the data sources for you subreports.
18: e.DataSources.Add(new ReportDataSource("Something", "MethodCallHere")); 19: }
20:
21: private void ReportError(object sender, Microsoft.Reporting.WebForms.ReportErrorEventArgs e)
22: { 23: //Error Handling and Report Logging here
24: }
25:
26: public class _Default : System.Web.UI.Page
27: { 28: protected void Page_Load(object sender, EventArgs e)
29: { 30: }
31:
32: private Stream Load_FileStream()
33: { 34: var MasterReport = new FileStream(Server.MapPath("~/Report/Master.rdlc"), FileMode.Open, FileAccess.Read);
35: return MasterReport;
36: }
37:
38: private Stream PrepReport()
39: { 40: XDocument ReportDoc = XDocument.Load(XmlReader.Create(Load_FileStream()));
41: IEnumerable<XNode> hold = ReportDoc.DescendantNodes().InDocumentOrder();
42: var BodyItems = (XElement) hold.Where(
43: n =>
44: n.GetType() != typeof (XText) &&
45: ((XElement) n).Parent != null &&
46: ((XElement) n).Name.LocalName.ToUpper() == "REPORTITEMS" &&
47: ((XElement) n).Parent.Name.LocalName.ToUpper() == "BODY").SingleOrDefault
48: ();
49: BodyItems.Add(NewNodes(ReportDoc.Root.Name.Namespace));
50: return CreateMemoryStream(ReportDoc);
51: }
52:
53: private XElement NewNodes(XNamespace @namespace)
54: { 55: var holder = new XElement(@namespace + "Subreport",
56: new object[]
57:
58: { 59: new XAttribute("Name", "Subreport1"), 60: new XElement(@namespace + "Parameters",
61: new XElement(@namespace + "Parameter",
62: new object[]{ 63: new XElement(@namespace + "Value",
64: "=Parameter!Period.Value")
65: , new XAttribute("Name", "Period") 66: })),
67: new XElement(@namespace + "ReportName", "NameHere"),
68: new XElement(@namespace + "Top", "0.25in"),
69: new XElement(@namespace + "Width", "7.25in"),
70: new XElement(@namespace + "Left", "0.125in"),
71: new XElement(@namespace + "Height", "0.25in")
72: });
73:
74: return holder;
75: }
76:
77: private Stream CreateMemoryStream(XDocument doc)
78: { 79: var memStream = new MemoryStream();
80: XmlWriter writer = XmlWriter.Create(memStream);
81: if (writer != null)
82: { 83: doc.Save(writer);
84: writer.Close();
85: }
86: memStream.Position = 0;
87: return memStream;
88: }
89:
90: private void GenerateReport()
91: { 92: ReportViewer1.LocalReport.LoadReportDefinition(PrepReport());
93: ReportViewer1.LocalReport.DisplayName = "Set what you want the export file name to be.";
94: ReportViewer1.LocalReport.LoadSubreportDefinition("SubReport", 95: new FileStream(
96: Server.MapPath("~/Report/Subreport.rdlc"), 97: FileMode.Open, FileAccess.Read));
98: ReportViewer1.ReportError += ReportError;
99: ReportViewer1.LocalReport.SubreportProcessing += LocalReport_SubreportProcessing;
100: ReportViewer1.LocalReport.Refresh();
101: ReportViewer1.ZoomMode = ZoomMode.PageWidth;
102: ReportViewer1.Focus();
103: }
104:
105: private void LocalReport_SubreportProcessing(object sender,
106: Microsoft.Reporting.WebForms.SubreportProcessingEventArgs e)
107: { 108: //This is where you provide the data sources for you subreports.
109: e.DataSources.Add(new ReportDataSource("Something", "MethodCallHere")); 110: }
111:
112: private void ReportError(object sender, Microsoft.Reporting.WebForms.ReportErrorEventArgs e)
113: { 114: //Error Handling and Report Logging here
115: }
116: }
0ce1f7bf-a25c-49f7-a2d7-91e55c16001a|4|4.3
Reporting