Creating and Debugging a SSRS Custom Rendering Extension

SQL Server Reporting Services (SSRS) is shipped with many predefined rendering extensions to save a report in various different formats, e.g. CSV, Excel, PDF, Word, PowerPoint. Nevertheless, a customer requested creating his own rendering extension. The goal was to create a new extension based on the predefined PowerPoint-Extension with fallback to the original one. The fallback will be done depending on custom properties, set in the report definition of the rendered report. The Report Server GUI should only contain the new PowerPoint-Rendering Extension.

This blog post is for demo purposes of the overall progress with working fallback to the original extension. It will not contain the code for creation of a custom PPTX itself. Further blog posts will describe the Report Server Object Model to understand the rendered report with its complex structure of Report Design and Runtime Instances with calculated data.

The scenario is based on SSRS 2016 and SQL Server Data Tools (SSDT) / Visual Studio 2015 (VS).

VS-Project for Custom Rendering Extension

The VS-Project is a Class-Library-Project, which uses .NET framework 4.5.2 in my scenario.

Project References

Reference following assemblies, located in Subdirectory of Visual Studio, e.g. C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies:

  • Microsoft.ReportingServices.Interfaces
  • Microsoft.ReportingServices.ProcessingCore
  • Microsoft.ReportingServices.RPLObjectModel

Extension Class

The extension class, using a switch depending on the existence of a custom report property "PPTXExportMode". If it is not set or set to "Default", the original PPTX-Export is called. If set, it throws a NotImplementedException - the code for creating the desired custom PPTX has to be added there.

 using System;
using Microsoft.ReportingServices.Interfaces;
using Microsoft.ReportingServices.OnDemandReportRendering;

namespace CustomPowerPointExtension
{
    public class MyPptxRenderingExtension : IRenderingExtension
    {

        #region IRenderingExtension
        public string LocalizedName
        {
            get {
                return LOCALIZED_NAME;
            }
        }

        public void GetRenderingResource(CreateAndRegisterStream createAndRegisterStreamCallback,
                                         System.Collections.Specialized.NameValueCollection deviceInfo)
        {
            this.StandardPptxRendering.GetRenderingResource(createAndRegisterStreamCallback, deviceInfo);
        }

        public bool Render(Microsoft.ReportingServices.OnDemandReportRendering.Report report,
                           System.Collections.Specialized.NameValueCollection reportServerParameters,
                           System.Collections.Specialized.NameValueCollection deviceInfo,
                           System.Collections.Specialized.NameValueCollection clientCapabilities,
                           ref System.Collections.Hashtable renderProperties,
                           CreateAndRegisterStream createAndRegisterStream)
        {
            return DoRender(report, reportServerParameters, deviceInfo, clientCapabilities, ref renderProperties, createAndRegisterStream);
        }

        public bool RenderStream(string streamName,
                                 Microsoft.ReportingServices.OnDemandReportRendering.Report report,
                                 System.Collections.Specialized.NameValueCollection reportServerParameters,
                                 System.Collections.Specialized.NameValueCollection deviceInfo,
                                 System.Collections.Specialized.NameValueCollection clientCapabilities,
                                 ref System.Collections.Hashtable renderProperties,
                                 CreateAndRegisterStream createAndRegisterStream)
        {
            return DoRender(report, reportServerParameters, deviceInfo, clientCapabilities, ref renderProperties, createAndRegisterStream);
        }


        public void SetConfiguration(string configuration)
        {
            this.StandardPptxRendering.SetConfiguration(configuration);
        }
        #endregion

        #region Custom Code
        private const string LOCALIZED_NAME = "My PPTX renderer";
        private const string REPORT_PROPERTY_PPTX_EXPORT_MODE = "PPTXExportMode";
        private const string REPORT_PROPERTY_PPTX_EXPORT_MODE_DEFAULT = "Default";
        private const string REPORT_PROPERTY_PPTX_EXPORT_MODE_CUSTOM = "Custom";
        private string standardPptxRenderingTypeName = "Microsoft.ReportingServices.Rendering.PowerPointRendering.PptxRenderingExtension,Microsoft.ReportingServices.PowerPointRendering";
        private IRenderingExtension standardPptxRendering;

        protected IRenderingExtension StandardPptxRendering
        {
            get
            {
                if (this.standardPptxRendering == null)
                {
                    Type t = Type.GetType(this.standardPptxRenderingTypeName);
                    this.standardPptxRendering = (IRenderingExtension)Activator.CreateInstance(t);
                }
                return this.standardPptxRendering;
            }
        }

        private static string GetExportMode(Microsoft.ReportingServices.OnDemandReportRendering.Report report)
        {
            CustomProperty cpExportMode = report.CustomProperties[REPORT_PROPERTY_PPTX_EXPORT_MODE];
            return (cpExportMode == null || cpExportMode.Instance == null || cpExportMode.Instance.Value == null)
                 ? REPORT_PROPERTY_PPTX_EXPORT_MODE_DEFAULT : cpExportMode.Instance.Value.ToString();
        }

        public MyPptxRenderingExtension()
        {

        }

        private bool DoRender(Microsoft.ReportingServices.OnDemandReportRendering.Report report,
                           System.Collections.Specialized.NameValueCollection reportServerParameters,
                           System.Collections.Specialized.NameValueCollection deviceInfo,
                           System.Collections.Specialized.NameValueCollection clientCapabilities,
                           ref System.Collections.Hashtable renderProperties,
                           CreateAndRegisterStream createAndRegisterStream)
        {
            string exportMode = GetExportMode(report);
            if (exportMode == REPORT_PROPERTY_PPTX_EXPORT_MODE_DEFAULT)
            {
                return this.StandardPptxRendering.Render(report,
                                                  reportServerParameters,
                                                  deviceInfo,
                                                  clientCapabilities,
                                                  ref renderProperties,
                                                  createAndRegisterStream);
            }

            // TODO: custom implementation
            throw new NotImplementedException(string.Format("Not implemented yet, report mode: {0}", exportMode));
        }
        #endregion
    }

}

Configuration

SSRS supports many types of extensions to create custom controls, renderings, deliveries and many more.

These extensions are configured in the SSRS configuration files. In order to enable them for report design in SSDT, they have to be configured there as well:

  • Design Time: Subdirectory of Visual Studio, e.g. C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies
  • Run Time: SSRS-Directory, e.g. C:\Program Files\Microsoft SQL Server\MSRS13.MSSQLSERVER\Reporting Services\ReportServer

To use the extension, modify RSReportDesigner.config (Design Time) and RSReportServer.config (Run Time).

Replace default extension

 <Extension Name="PPTX" Type="Microsoft.ReportingServices.Rendering.PowerPointRendering.PptxRenderingExtension,Microsoft.ReportingServices.PowerPointRendering" />

with new extension

 <Extension Name="MYPPTX" Type="CustomPowerPointExtension.MyPptxRenderingExtension,CustomPowerPointExtension" />

The extension must be copied to these folders:

  • Design Time: Subdirectory of Visual Studio, e.g. C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies
  • Run Time: Bin-Subdirectory of SSRS-Directory, e.g. C:\Program Files\Microsoft SQL Server\MSRS13.MSSQLSERVER\Reporting Services\ReportServer\bin

In my scenario, in order to get the fallback for the original extension working, it is also necessary to flag the resulting custom assembly as trustworthy within RSPreviewPolicy.config (Design Time) and RSSrvPolicy.config (Run Time).

  • Design Time
 <CodeGroup   
  class="UnionCodeGroup"  
    version="1"  
   PermissionSetName="FullTrust"  
 Name="MyCustomCodeGroup"  
  Description="Code group for my custom extension">  
      <IMembershipCondition class="UrlMembershipCondition"  
       version="1"  
       Url="C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies\CustomPowerPointExtension.dll"  
     />  
</CodeGroup>   
  • Run Time
    watch out for correct position to add the configuration, otherwise the Full Trust may not work: directly after $CodeGen$, see policy-files
 <CodeGroup   
  class="UnionCodeGroup"  
    version="1"  
   PermissionSetName="FullTrust"  
 Name="MyCustomCodeGroup"  
  Description="Code group for my custom extension">  
      <IMembershipCondition class="UrlMembershipCondition"  
       version="1"  
       Url="C:\Program Files\Microsoft SQL Server\MSRS13.MSSQLSERVER\Reporting Services\ReportServer\bin\CustomPowerPointExtension.dll"  
      />  
</CodeGroup>   

VS Post-Build-Event

To deploy the assembly automatically to the local SSDT-Directory, simply add a Post-Build-Event to the VS-Project. Because of the targeted directory within Program Files, it may be necessary to start Visual Studio in Elevated Mode ("start as Admin").

 copy /Y "$(TargetPath)" "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies"

Additionally, the copy only works, if the assembly is not currently accessed by SSDT - simply close the Report-Project within SSDT to get rid of the file lock.

Debugging

To debug Custom Renderings of SSDT, attach Visual Studio to the process "PreviewProcessingService.exe". The process is started by SSDT for report rendering. If the process doesn't exist yet, simply switch to Report Preview within SSDT. The debugging of Custom Renderings of SSRS is also possible, simply attach VS to the ReportServer-Process.

Further Reading