In a previous post I discussed how it is possible to enable HTML rendering for local reports firing via a ReportViewer instance. I did that because I was recently in a situation where we were using the ReportViewer in local mode had to support HTML reports. Running reports in local mode with the ReportVIewer affords you many luxuries, but HTML report generation is not one of them. Desperate times call for desperate measures.
I also mentioned the fact that rendering extensions, presumably for a good reason, are not extensible by default for local reports. By “extensible” I mean that there is no public object model exposed nor are their .config options to alllow, for example, your own custom rendering extension types to be loaded. Of course, as an absolute last resort, you can use Reflector to disassemble the code and hack into the object model using Reflection. I will be the first to tell you that this is not a good idea (for a number of obvious reasons) but the main one is that the component’s code can change (a new version comes out, etc). When that happens, private field names can change and cause your references obtained via Reflection to be null. That can lead to big problems and hard-to-solve bugs.
As I looked at the code from my previous post a little more I soon realized that the internal list of rendering extensions holds instances of type Microsoft.ReportingServices.ReportRendering.IRenderingExtension. With this interface you could create your own extension and then inject it into the internal list using Reflection.
Here’s some code do that:
1: private static void AddRenderingExtension(ReportViewer viewer, string formatName, Type renderingExtension)
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: Type type = Type.GetType("Microsoft.Reporting.LocalRenderingExtensionInfo, Microsoft.ReportViewer.Common, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
20:
21: ConstructorInfo ctor = type.GetConstructor
22: (
23: BindingFlags.NonPublic | BindingFlags.Instance,
24: null,
25: new Type[] { typeof(string), typeof(string), typeof(bool), typeof(Type), typeof(bool) },
26: null
27: );
28:
29: IList extensions = ListRenderingExtensions.Invoke(previewServiceInstance, null) as IList;
30:
31: object instance = ctor.Invoke(new object[]
32: {
33: formatName,
34: formatName,
35: true,
36: renderingExtension,
37: true
38: });
39:
40: extensions.Add(instance);
41: }
An example call would be:
AddRenderingExtension(reportViewerInstance, "HTMLOWC", typeof(HtmlOWCRenderingExtension));
I chose HtmlOWCRenderingExtension simply because it is a type that is not already in the internal list (not sure why) but yet implements IRenderingExtension. I am not sure of the actual differences between it and its parent type, Html40RenderingExtesion. As the name alludes to, I assume it deals with OWC (Office Web Components) and some special rendering considerations they have for HTML. Regardless, if you use the “Derived Types” capability in Reflector you will see that there are a few other Types that implement this interface:
As you can see above, in addition to the Excel and PDF extensions provided by the viewer by default, there are additional extensions available for images (which are available in server reports but not local reports). You could, of course, code your own implementation and then inject that as well. At this point, I am not sure of the complexities involved with that. A rendering extension that would be extremely useful would be one that is able to generate CSV files. Interestingly enough, server reports have that available as an export option. Local reports do not. You can probably guess where I’m going with this…