A really annoying RDLC limitation

C is visiting family and I'm supposed to be on vacation, but somehow work keeps getting in the way.  Also house improvement chores; if work ever gets terminally dispiriting, I think my contractor might take me on as a stucco apprentice.

Still, I have a long queue of questions from various people on reports that I was going to look into during this period.  I can't help everybody, no matter how much time I have, because some things just aren't… fixable … and I'll talk about one of those next.  (Linda — I promise to do yours too!)  

But Himanshu is first, because not only did this request pose a question and ask for help, but also it came with some valuable information that we should all share.   

Here's the original email (which came with the RDLC as an attachment):

I need your help to solve the problem related with Report (.rdlc), I am also facing the same problem which you discuss in one of the Thread in the ASP form, in my case i have multiple subreports on the main page and i need every sub report start from the new page. To seprate them i use rectangle inbetween and use the Insert Page Break After rectangle. I need to show the subreports on the basis of a report parameter. If parameter says don't show the report it work as i use the express to hide the report and it's fine but left the blank page but if i use the same condition for rectangle (to hide), page break don't work in normal condition and report start from the end of the previous page. I don't know why this is happening.

Big surprise, I had no idea either. After ascertaining that subreports weren't critical to the issue, I went about reproducing this with a report holding multiple tables and datasets, and bashed my brain on it for a while — a little at a time, over a month. Nothing good happened.

Meanwhile, instead of lying back and expecting a miracle, Himanshu went about doing his own research. Next stop: Microsoft support.

Deus ex machina, not so much

I pinged Himanshu to say I hadn't forgotten about the problem, but had no resolution yet. Here's what I learned:

Thank you so much for keeping my hope alive. I didn’t found any solution from Microsoft as well. I am attaching a mail herewith which tells this is a bug in VS2005.

When I read the enclosed e-mail (from Chris Baldwin Himself!), I could see that from MS POV, it's not a bug, and is not really about VS2005 either:

Hello Himanshu, This is a limitation of RDLC in the VS 2008 controls that will be lifted in VS 2010. In RDLC for VS 2008, page breaks on conditionally hidden items are never honored even if they are visible at runtime. In VS 2010, the page break will occur if the item is visible at runtime.

[… later, in followup:]

Hi Himanshu, No I do not believe there is a solution for VS2005 or VS2008 controls in local mode. -Chris

Let's look at that again:

[CB] In RDLC for VS 2008, page breaks on conditionally hidden items are never honored even if they are visible at runtime.  

Well.   That's that, then.

Hmmm.

It's not entirely surprising that some things are "underimplemented" in local mode.  After all, this is the mode of ReportViewer that doesn't expect you to have an instance of Reporting Services available; the ReportViewer control does all the rendering and, presumably, doesn't have all the "smarts" of the server. (That's why not all export types are supported in local mode, too.) 

Let's face it: MS aren't getting a SQL Server license from you to use this control, it's pretty much a freeby for .NET developers.  Its engine is out of step with Reporting Services (as far as I know local mode doesn't support the 2008 RDL schema additions yet, right?) and it needs a separate development effort.

Let's look at the "underimplementation" in question here: Forced page breaks are ignored when items in a data region are conditionally hidden. 

It may sound silly, but actually this is a development compromise that makes a lot of sense.  Pagination is a PITA to start with; when you add the logic for "what if items stretch/flow" you complicate everything, and of course one has to add that. 

Then, adding "what if some items don't always show?", figuring out whether they will show before figuring out pagination, and how big they will be IF they will show and IF they stretch… it's huge.  The page collection is almost always figured out at a different time than the actual data, and — as I've discussed elsewhere — it looks like the sequence of evaluation may be slightly different in local mode than in the server engine — meaning, we can't just "borrow" this code from one engine to the next.

So, MS left this particular feature out of the freebie local mode.  It's frustrating, but definitely understandable.

So that's reality. What can we do?

While the original requirement isn't "fixable", sometimes you can talk to users about it and come to a compromise resolution, given the tools at hand.

One thing I suggested to Himanshu, if the report was assembling a "book" (as is normally the case with subreports that start on their own page each), and  if the "booK' was rendered to a PDF,  was that each subreport could be programmatically rendered separately.  When you do this, obviously, the conditional rendition of some subreports is as simple as … not rendering the ones you don't want.  IOW, this report is a case of "reports as document sections", something I've also discussed here before. The PDFs you do want can be "stitched together" using PDFTK (as discussed in that earlier post), or using the iText libraries that form the basis of PDFTK.

Yes, you can handle running page numbers. You have to provide an offset to each report in turn, to add to what it thinks the current page count is, as a parameter, based on the accumulated page count of the reports before it. (You can interrogate each PDF's pagecount using PDFTK or iText.  I think you can also get TotalPages from ReportViewer's LocalReport member, in one of three modes: Progressive, Actual, and Estimate, but I haven't tried this.)

Astute readers will immediately realize that what we have here is an alternate method of doing "Table of Contents reports". Gather the page numbers, as described above, "pour" them into a data source for the TOC report, run the TOC report last of all, but "stitch" it together first. My team is doing this for a client right now.

What should Himanshu do?

There's a good chance that Himanshu's report needs to be viewed interactively, not just in a PDF. I suggested that the right resolution for this report might be dynamic loading, rather than conditional hiding. 

In other words: you can load the report definition dynamically, don't ask a single report definition to be all things to all rendering instances.

At the risk of being ridiculously self-referential in this post, let me remind you that not every report should be resolved with tricks on the reporting engine.  In a "Quick Report" example, I created a dynamic report layout — but also questioned the limits to which this technique should be stretched.  Sometimes, it's better to create a non-dynamic report on-the-spot, to do exactly what you want, without any tricks. 

It's a variation on "Doctor, doctor, my arm hurts when I do this. " "Well, don't do that."  If conditional hiding doesn't work for this report, don't use it. 

Himanshu asked me to be more explicit about this, so I will :-).

What is dynamic loading in ReportViewer? 

Check out http://gotreportviewer.com, if you haven't already.  You'll see a couple of items labelled "generate an RDLC" and another one labelled "load an RDL from a stream".

While the title of the latter says "an RDL", check out the code for the former (either sample).  You'll definitely see code that looks like this:

/* where m_rdl is a MemoryStream */
this.reportViewer1.LocalReport.LoadReportDefinition(m_rdl); 

How does Himanshu apply dynamic loading? 

Himanshu's not going to do anything as fancy as creating an entire RDL from scratch at runtime.  In this scenario, you take the original RDLC — just removing the conditional visibility elements, leaving the page breaks where you want them — and use it as a template, rather than as the literal RDLC to be loaded to the ReportViewer control.

Somewhere, somehow, Himanshu has a bunch of instructions from the user or from biz logic that say which subreports should be shown on the current run.  That logic has to be applied to the RDLC instead of within the RDLC, to create the actual RDLC that will be run.

Here's a quicky example of how you would remove the unwanted subreports.  Frankly I would rather do this with XSLT than loading into the DOM but people still seem to think it's an arcane skill, so here's all the code you really need:

Dim fn As String = […]' your template RDLC here
Dim xx As System.Xml.XmlDocument = New System.Xml.XmlDocument()
Dim yy As System.Xml.XmlElement, zz As System.Xml.XmlElement
xx.Load(fn)
Dim sr As String ' name of the subreport to remove
sr = "subReportOnFinances"

' don't get too upset about this XPATH,
' I'm showing you all the qualifiers but
' in a second example below you'll see
' they're not really necessary:
yy = xx.SelectSingleNode( _
   "/*[local-name()='Report' and " & _
   "namespace-uri()='http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition']" & _
   "/*[local-name()='Body' and namespace-uri()=" & _
   "'http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition'][1]"
& _
   "/*[local-name()='ReportItems' and namespace-uri()=" & _
   "'http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition'][1]"
& _
   "/*[local-name()='Subreport' and " & _
   "namespace-uri()='" & _
   "http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition'"
& _
   "and @Name='" & sr & "']")
' or if you prefer to remove subreports by sequence,
' in this example subReportOnFinances is the 2nd subreport in the series.
' I'll cut out some of the other xpath you really don't need here too:
yy = xx.SelectSingleNode("/*/*/*/*[local-name()='Subreport'][2]")

zz = yy.ParentNode
zz.RemoveChild(yy)
' repeat, removing all subreports not to be used in this
' run, from the document.

' Finally, for this example, I'll save to a temp RDLC,
' but again you can use streams or however you want to deal
' with the rest of the non-report-specific problem of
' temporary files:
xx.Save("c:\temp\x.rdlc")

In addition to removing the subreports you don't want, you can adjust the Top attributes for the subreports that remain, if you find that this is needed.  You'll find that each subreport element has a Top child element, you can work out how much space you want in between each subreport, etc.  If necessary, remove them manually from a copy of the template in the Report Designer, moving the remaining subreports until they suit you. Examine the results in the XML report definition, to figure out the right way to adjust them at runtime.

Better than nothing

… not too bad, actually.  And certainly more productive than wailing into the storm (assuming you're not Lauren Bacall), or poking little notes into the MS feature wish list.