beaucrawford.net

Give me data or give me death

About the author

Author Name is someone.
E-mail me Send mail

Recent comments

Don't show

Authors

Tags

Don't show

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

    © Copyright 2010

    Enable HTML in ReportViewer LocalReport

    HTML is available as a rendering extension in server reports rendered by the ReportViewer control (Microsoft.Reporting.WebForms.ReportViewer).  By “server report” I mean a report that is rendered via the ServerReport property (which makes a Web service call to an actual Reporting Services installation).  The report is rendered on the server and sent back to you as a byte[] array. 

    The bummer is that, by default, there is no support for HTML in local reports (reports that have the .rdlc extension and are rendered via the LocalReport property).  In fact, you will get a LocalProcessingException if you attempt to use the “HTML4.0” format name.  In Reporting Services, the rendering extensions are completely extensible via a .config file.  Not so in the Web server control (and the Win Forms control).  In fact, the rendering extensions are hard coded in the class and there are no public extensibility points.  I found this extremely annoying, so I started poking around in Reflector and finally drilled to the following code:


       1: public override IEnumerable<LocalRenderingExtensionInfo> ListRenderingExtensions()
       2: {
       3:     if (this.m_renderingExtensions == null)
       4:     {
       5:         List<LocalRenderingExtensionInfo> list = new List<LocalRenderingExtensionInfo>();
       6:         Html40RenderingExtension extension = new Html40RenderingExtension();
       7:         list.Add(new LocalRenderingExtensionInfo("HTML4.0", extension.LocalizedName, false, typeof(Html40RenderingExtension), false));
       8:         Microsoft.ReportingServices.Rendering.ExcelRenderer.ExcelRenderer renderer = new Microsoft.ReportingServices.Rendering.ExcelRenderer.ExcelRenderer();
       9:         list.Add(new LocalRenderingExtensionInfo("Excel", renderer.LocalizedName, true, typeof(Microsoft.ReportingServices.Rendering.ExcelRenderer.ExcelRenderer), true));
      10:         RemoteGdiReport report = new RemoteGdiReport();
      11:         list.Add(new LocalRenderingExtensionInfo("RGDI", report.LocalizedName, false, typeof(RemoteGdiReport), false));
      12:         ImageReport report2 = new ImageReport();
      13:         list.Add(new LocalRenderingExtensionInfo("IMAGE", report2.LocalizedName, false, typeof(ImageReport), true));
      14:         PdfReport report3 = new PdfReport();
      15:         list.Add(new LocalRenderingExtensionInfo("PDF", report3.LocalizedName, true, typeof(PdfReport), true));
      16:         this.m_renderingExtensions = list;
      17:     }
      18:     return this.m_renderingExtensions;
      19: }


    So, as I mentioned, the rendering extensions are hard coded.  Above, on line 6, you can see that an instance of “Html40RenderingExtension” is created and then passed into the list via a wrapper instance of type LocalRenderingExtensionInfo.  If we look at the constructor for this class we can see that it looks like:

       1: internal LocalRenderingExtensionInfo(string name, string localizedName, bool isVisible, Type type, bool isExposedExternally) : this(name, localizedName, isVisible)
       2: {
       3:     this.m_type = type;
       4:     this.m_isExposedExternally = isExposedExternally;
       5: }

    I found the “isExposedExternally” boolean value somewhat interesting.  In fact, I dug around and found the aforementioned LocalProcessingException is thrown if the rendering extension is not set to be exposed via this field.  Hmmmmm…. that’s interesting.  Well, with the help of some Reflection magic, we can reach in and grab the private “m_isExposedExternally” field and simply updates its value to true.

    The code to do that is as follows:

    Note:  This is for the Microsoft.Reporting.WebForms.ReportViewer type.
       1: private static void EnableFormat(ReportViewer viewer, string formatName)
       2: {
       3:     const BindingFlags Flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
       4:  
       5:     FieldInfo m_previewService = viewer.LocalReport.GetType().GetField
       6:     (
       7:         "m_previewService",
       8:         Flags
       9:     );
      10:  
      11:     MethodInfo ListRenderingExtensions = m_previewService.FieldType.GetMethod
      12:     (
      13:         "ListRenderingExtensions",
      14:         Flags
      15:     );
      16:  
      17:     object previewServiceInstance = m_previewService.GetValue(viewer.LocalReport);
      18:  
      19:     IList extensions = ListRenderingExtensions.Invoke(previewServiceInstance, null) as IList;
      20:  
      21:     PropertyInfo name = extensions[0].GetType().GetProperty("Name", Flags);
      22:  
      23:     foreach (object extension in extensions)
      24:     {
      25:         if (string.Compare(name.GetValue(extension, null).ToString(), formatName, true) == 0)
      26:         {
      27:             FieldInfo m_isVisible = extension.GetType().GetField("m_isVisible", BindingFlags.NonPublic | BindingFlags.Instance);
      28:             FieldInfo m_isExposedExternally = extension.GetType().GetField("m_isExposedExternally", BindingFlags.NonPublic | BindingFlags.Instance);
      29:             m_isVisible.SetValue(extension, true);
      30:             m_isExposedExternally.SetValue(extension, true);
      31:             break;
      32:         }
      33:     }
      34: }

    Once you do this on your instance of ReportViewer you can then call LocalReport.Render and pass in the format name of “HTML4.0”.  That will obviously generate HTML and any images contained in the report can be captured by using normal <DeviceInfo> options that are passed to the Render method.  Given the fact that HTML rendering is not directly supported, there are a few minor annoyances with how images are processed.  I’ll blog on that someday.

    Enjoy!

    Categories: C# | SSRS
    Posted by Beau on Tuesday, July 01, 2008 9:19 PM
    Permalink | Comments (10) | Post RSSRSS comment feed

    Comments

    Top Thianthai us

    Thursday, August 07, 2008 10:26 AM

    Thanks for the solution. It works fine with tables. Smile But still have problem export chart image. Look forward to the workaround on export image in Local report in HTML format from ReportViewer.

    Ford us

    Monday, August 25, 2008 10:39 AM

    I'd love to hear how you approached the image annoyances. Looking at the generated HTML source (for a report with MS's column sort icons), the IMG SRC attribute is pointing to http://reportserver/... I am trying to pass in a base URL with the StreamRoot property to no avail.

    Any thoughts?

    Just discovered your blog -- great stuff, keep up the good work!

    nathan us

    Tuesday, September 09, 2008 9:06 AM

    I get the following error - Using the generic type 'System.Collections.Generic.IList<T>' requires '1' type arguments for the line: IList extensions = ListRenderingExtensions.Invoke(previewServiceInstance, null) as IList;

    Beau us

    Friday, September 19, 2008 7:11 PM

    nathan -- which version of the ReportViewer control are you using (2005 or 2008)? Also, WinForms or WebForms? Make sure you are using the control in LocalReport mode.

    beaucrawford.net

    Sunday, September 21, 2008 5:25 PM

    Pingback from beaucrawford.net

    Injecting an IRenderingExtension instance into the ReportViewer control

    Adam Huitt us

    Friday, October 10, 2008 2:15 PM

    I code mostly in VB - could you convert this to vb? Also, how exactly to I put this in my code? Do I need to just paste it in the codebehind with the web page holding the reportviewer control and then make a call to it? Do I need to reference anything in the page to make sure whatever types and controls I use in this sample code resolves properly?

    If you could post an example of how to use this code in VB from start to finish that would be fantastic.

    Thanks!

    Joanna us

    Thursday, October 16, 2008 1:21 PM

    Great post! I really needed that info for a project I'm working on. Here is the code in VB, for the gentleman that asked for it.


    Private Sub EnableFormat(ByRef viewer As ReportViewer, ByVal formatName As String)

    Const Flags As System.Reflection.BindingFlags = System.Reflection.BindingFlags.NonPublic + System.Reflection.BindingFlags.Public + System.Reflection.BindingFlags.Instance
    Dim m_previewService As System.Reflection.FieldInfo = viewer.LocalReport.GetType().GetField("m_previewService", Flags)
    Dim ListRenderingExtensions As System.Reflection.MethodInfo = m_previewService.FieldType.GetMethod("ListRenderingExtensions", Flags)
    Dim previewServiceInstance As Object = m_previewService.GetValue(viewer.LocalReport)
    Dim extensions As IList = ListRenderingExtensions.Invoke(previewServiceInstance, Nothing)
    Dim name As System.Reflection.PropertyInfo = extensions(0).GetType().GetProperty("Name", Flags)
    Dim extension As Object
    For Each extension In extensions
    If (String.Compare(name.GetValue(extension, Nothing).ToString(), formatName, True) = 0) Then
    Dim m_isVisible As System.Reflection.FieldInfo = extension.GetType().GetField("m_isVisible", System.Reflection.BindingFlags.NonPublic + System.Reflection.BindingFlags.Instance)
    Dim m_isExposedExternally As System.Reflection.FieldInfo = extension.GetType().GetField("m_isExposedExternally", System.Reflection.BindingFlags.NonPublic + System.Reflection.BindingFlags.Instance)
    m_isVisible.SetValue(extension, True)
    m_isExposedExternally.SetValue(extension, True)
    Exit For
    End If
    Next extension
    End Sub

    kim tuyen vn

    Monday, November 17, 2008 1:03 AM

    Hi hi... i same your error, khe khe , correct: using System.Colections !
    That is ok

    matt us

    Thursday, December 18, 2008 5:10 PM

    Can someone post an example of how you call localreport.render?
    What should I be passing in as the args?

    weblogs.asp.net

    Friday, February 20, 2009 12:55 PM

    Pingback from weblogs.asp.net

    Disable/Enable export format in SSRS and ASP.NET - Stephen Songer