none
Rendering Image Programmatically Using ReportViewer Results in Only One Row RRS feed

  • Question

  • My Winforms app is using a ReportViewer to render reports to an image programmatically, using the ServerReport.Render method.

    This is working great for charts and textboxes, but all of the tables show up with only the first row present.  However, if I use the 'Export to...' button, and choose image, the resultant image does have all of the rows. So my guess is that the problem I'm having is with configuring the ServerReport.Render method.

    I've looked at the 'Image Device Information' page ( http://msdn.microsoft.com/en-us/library/ms155373.aspx ), which seems like a good start. The only thing I saw that seemed relevant was the 'StartPage' setting.  I set this to 0 to make sure it got all of the pages, but this made no difference.

    For reference, here is the relevant snippet of code:

                ReportViewer dummyReportViewer = new ReportViewer();
                dummyReportViewer.ServerReport.ReportPath = fullReportPath;
                dummyReportViewer.ServerReport.ReportServerUrl = new Uri(Settings.Default.ReportServerURL);
                Warning[] warnings;
                string[] streamids;
                string mimeType;
                string encoding;
                string extension;
                Byte[] renderedBytes = dummyReportViewer.ServerReport.Render("Image", "<DeviceInfo><OutputFormat>TIFF</OutputFormat><StartPage>0</StartPage></DeviceInfo>", out mimeType, out encoding, out extension, out streamids, out warnings);
    

    Wednesday, March 31, 2010 9:06 PM

Answers

  • You're right.

    There are some differences between rendering local reports and rendering server reports to the image renderer. I'm going to use snippets from Brian Hartman's complete code sample to describe them.

    With local reports, streams are generated by the viewer control itself, and you can implement CreateStreamCallback to create the Stream object for each image page and (more importantly) reference these Stream objects when you print them. See code below.

            private Stream LocalReportCreateStreamCallback(
                string name,
                string extension,
                Encoding encoding,
                string mimeType,
                bool willSeek)
            {
                MemoryStream stream = new MemoryStream();
                m_pages.Add(stream);

                return stream;
            }

    However, image streams are generated differently for server reports. The streams are all generated on the server, and each call to Render() gives you back only one stream (one page). So you must call the Render() method again and again to get all the image pages. You can do this by specifying the page number you want in the deviceinfo settings, but this causes the entire report to be rendered each time. A cheaper way to do this is to use the rs:PersistStreams and rs:GetNextStreams URL access parameters (see detailed information on this behavior in John Gallardo's blog). To do this, use the rs:PersistStreams URL access parameter the first time you call Render(), which then returns the first page. Then, use the rs:GetNextStream URL access parameter to call the Render() repeatedly to get each subsequent page. You can specify these URL access parameters using the urlAccessParameters argument in the ServerReport.Render() method, as shown in the code below.

            private void RenderAllServerReportPages(ServerReport serverReport)
            {
                string deviceInfo = CreateEMFDeviceInfo();

                // Generating Image renderer pages one at a time can be expensive.  In order
                // to generate page 2, the server would need to recalculate page 1 and throw it
                // away.  Using PersistStreams causes the server to generate all the pages in
                // the background but return as soon as page 1 is complete.
                NameValueCollection firstPageParameters = new NameValueCollection();
                firstPageParameters.Add("rs:PersistStreams", "True");

                // GetNextStream returns the next page in the sequence from the background process
                // started by PersistStreams.
                NameValueCollection nonFirstPageParameters = new NameValueCollection();
                nonFirstPageParameters.Add("rs:GetNextStream", "True");

                string mimeType;
                string fileExtension;
                Stream pageStream = serverReport.Render("IMAGE", deviceInfo, firstPageParameters, out mimeType, out fileExtension);

                // The server returns an empty stream when moving beyond the last page.
                while (pageStream.Length > 0)
                {
                    m_pages.Add(pageStream);

                    pageStream = serverReport.Render("IMAGE", deviceInfo, nonFirstPageParameters, out mimeType, out fileExtension);
                }
            }

     


    Cephas Lin This posting is provided "AS IS" with no warranties.
    Thursday, April 1, 2010 6:14 PM
    Moderator
  • I don't see anything wrong with the way you are invoking the render method in server mode.  But here are a few things to note/follow up on:

    1. If you place the dummyReportViewer on a form and export the report via the toolbar, are you seeing the output that you want?  It sounds like you are, but I just want to be sure.

    2. When you export via the report toolbar, it is executing the same code the viewer executes the same code that you have here, with one difference.  The built-in UI always passes null for the device info.  So if you see a difference with this viewer on a form, that is the likely issue.

    3. When you export with the built-in UI, only the primary stream is returned (the return value of the Render method).  So I don't think this is a seconday stream issue.  However, the equivalent way to get those streams in server mode is to call ServerReport.RenderStream and pass in the values from the streamids out parameter on Render.

    Friday, April 2, 2010 2:22 PM
    Moderator

All replies

  • Have you looked at the code at http://msdn.microsoft.com/en-us/library/ms252091.aspx to see if it helps?


    Cephas Lin This posting is provided "AS IS" with no warranties.
    Thursday, April 1, 2010 2:24 PM
    Moderator
  • I'm not seeing any Render overload with a CreateStreamCallback argument out of the six overloads that show up in Intellisense. This is a Server Report, that option must only exist for Local Reports.
    Thursday, April 1, 2010 3:04 PM
  • You're right.

    There are some differences between rendering local reports and rendering server reports to the image renderer. I'm going to use snippets from Brian Hartman's complete code sample to describe them.

    With local reports, streams are generated by the viewer control itself, and you can implement CreateStreamCallback to create the Stream object for each image page and (more importantly) reference these Stream objects when you print them. See code below.

            private Stream LocalReportCreateStreamCallback(
                string name,
                string extension,
                Encoding encoding,
                string mimeType,
                bool willSeek)
            {
                MemoryStream stream = new MemoryStream();
                m_pages.Add(stream);

                return stream;
            }

    However, image streams are generated differently for server reports. The streams are all generated on the server, and each call to Render() gives you back only one stream (one page). So you must call the Render() method again and again to get all the image pages. You can do this by specifying the page number you want in the deviceinfo settings, but this causes the entire report to be rendered each time. A cheaper way to do this is to use the rs:PersistStreams and rs:GetNextStreams URL access parameters (see detailed information on this behavior in John Gallardo's blog). To do this, use the rs:PersistStreams URL access parameter the first time you call Render(), which then returns the first page. Then, use the rs:GetNextStream URL access parameter to call the Render() repeatedly to get each subsequent page. You can specify these URL access parameters using the urlAccessParameters argument in the ServerReport.Render() method, as shown in the code below.

            private void RenderAllServerReportPages(ServerReport serverReport)
            {
                string deviceInfo = CreateEMFDeviceInfo();

                // Generating Image renderer pages one at a time can be expensive.  In order
                // to generate page 2, the server would need to recalculate page 1 and throw it
                // away.  Using PersistStreams causes the server to generate all the pages in
                // the background but return as soon as page 1 is complete.
                NameValueCollection firstPageParameters = new NameValueCollection();
                firstPageParameters.Add("rs:PersistStreams", "True");

                // GetNextStream returns the next page in the sequence from the background process
                // started by PersistStreams.
                NameValueCollection nonFirstPageParameters = new NameValueCollection();
                nonFirstPageParameters.Add("rs:GetNextStream", "True");

                string mimeType;
                string fileExtension;
                Stream pageStream = serverReport.Render("IMAGE", deviceInfo, firstPageParameters, out mimeType, out fileExtension);

                // The server returns an empty stream when moving beyond the last page.
                while (pageStream.Length > 0)
                {
                    m_pages.Add(pageStream);

                    pageStream = serverReport.Render("IMAGE", deviceInfo, nonFirstPageParameters, out mimeType, out fileExtension);
                }
            }

     


    Cephas Lin This posting is provided "AS IS" with no warranties.
    Thursday, April 1, 2010 6:14 PM
    Moderator
  • I don't see anything wrong with the way you are invoking the render method in server mode.  But here are a few things to note/follow up on:

    1. If you place the dummyReportViewer on a form and export the report via the toolbar, are you seeing the output that you want?  It sounds like you are, but I just want to be sure.

    2. When you export via the report toolbar, it is executing the same code the viewer executes the same code that you have here, with one difference.  The built-in UI always passes null for the device info.  So if you see a difference with this viewer on a form, that is the likely issue.

    3. When you export with the built-in UI, only the primary stream is returned (the return value of the Render method).  So I don't think this is a seconday stream issue.  However, the equivalent way to get those streams in server mode is to call ServerReport.RenderStream and pass in the values from the streamids out parameter on Render.

    Friday, April 2, 2010 2:22 PM
    Moderator
  • I don't see anything wrong with the way you are invoking the render method in server mode.  But here are a few things to note/follow up on:

    1. If you place the dummyReportViewer on a form and export the report via the toolbar, are you seeing the output that you want?  It sounds like you are, but I just want to be sure.

    2. When you export via the report toolbar, it is executing the same code the viewer executes the same code that you have here, with one difference.  The built-in UI always passes null for the device info.  So if you see a difference with this viewer on a form, that is the likely issue.

    3. When you export with the built-in UI, only the primary stream is returned (the return value of the Render method).  So I don't think this is a seconday stream issue.  However, the equivalent way to get those streams in server mode is to call ServerReport.RenderStream and pass in the values from the streamids out parameter on Render.

    Friday, April 2, 2010 2:22 PM
    Moderator