RDLC Report Performance Enhancements in Dynamics 365 Business Central

Dynamics 365 Business Central and Dynamics NAV use SQL Server Report Viewer control libraries to render our reports. 

The history of RDLC reporting 

The RDLC report template gets compiled into a small assembly and then in the runtime, that assembly is used to parse the data set and hereby generate a PDF or EMF pictures for printing. 

Illustrates processes for rendering reports based on on-demand access or scheduled runs.

Inside reports, you can embed Visual Basic .NET code to do some advanced handling, for example. Depending on the codes security asks, the report is either rendered in the server’s AppDomain or in another secure AppDomain that is generated by Report Viewer.

Running in the Report Viewer-generated AppDomain requires a lot of marshaling in between the two AppDomains, which results in much longer rendering times on reports, which means performance issues. For that reason, we only wanted to run the reports in the Report Viewer AppDomain when it was really needed. 

IMPORTANT: If you ran the reports in the servers AppDomain, there would be a slight memory leak, because each of the created assemblies would never get unloaded in the server AppDomain. However, if the same was done in the isolated AppDomain, we would be able to get rid of the assemblies, by unloading the secure AppDomain. At the time, we decided that the trade-off between performance gain over the small leak was acceptable. 

Just before we launched Dynamics NAV 2013 R2, we changed the .NET runtime to version 4.0. In .NET Framework 4.0, Code Access Security (CAS) was deprecated because it was way too complicated. Anyway, if you allowed another assembly to load in your process, it was better to trust them up front or isolate them completely. However, Report Viewer continued to use the CAS policy. With the deprecation, all renderings were deferred to the Report Viewer-generated AppDomain and therefore running slowly with performance issues. 

Over time, we realized that we could start the process with a CLR legacy switch: 

  <NetFx40_LegacySecurityPolicy enabled="true" /> 

For more information, see https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/netfx40-legacysecuritypolicy-element.

This would enable us to achieve the old behavior, although with various consequences to the CLR process. As an example, we would not be able to use the Dynamic Language Runtime (DLR) and enable assemblies in non-homogenous AppDomains.

There have been several blogs written on this over the years (thanks to Duilio Tacconi from Microsoft Support) to help our community design their reports for performance: 

That was a description of the past. Now let’s look at how things work today. 

Reporting in Dynamics 365 Business Central 

Dynamics 365 Business Central online runs in Windows Azure. We have a new development environment, and we are using a lot of great features in our product, developed and released by many great teams inside and outside of Microsoft. One of the new features is used for communication between tiers, (SignalR). 

Business Central uses SignalR to communicate with the language service in Visual Studio Code, which is our new development environment, and the backend endpoint in the server. SignalR uses DLR in its implementation, so we turned off the legacy switch described above to do the communication in the development environment. This meant that rendering reports locally on a development machine would be much slower (e.g. REPORT.SAVEASPDF, REPORT.SAVEAS, etc.). 

Time was ripe for a rewrite of the serverrendered reports. In the Business Central October’18 update 4 (February 2019), this was all completely rewritten. 

Instead of running rendering in the server’s standard AppDomain, we create a specific Report AppDomain where we can control exactly how marshal the data. In the end, we specify two marshaling operations: 

  • One for the RDLC stream 
  • One for the data stream 

This means that we can now control the loading and unloading of the AppDomain and thereby control the performance of trusted reports. At the same time, we can unload the dynamic assemblies that are generated by the rendering, while still the DLR features are still supported. 

The Report AppDomain, by default, recycles on every 1000 renderings, which helps avoid piling up too much memory. 

This effectively means that if you build RDLC reports, we recommend that you stop using the legacy switch, because it will only revert the server to the behavior that we had in previous releases with slight and controlled memory leaks as consequence. 

How to configure the reporting environment 

To control how you would like to run your rendering, a set of switches has been introduced. Let’s go through them with a description of what they do together. Most of these switches are internal only, meaning there should be no need to mess with them, but for backwards compatibility reasons, they are still offered. 

To use the legacy switch 

  <NetFx40_LegacySecurityPolicy enabled="true" /> 

This switch sets if you want to run in the servers AppDomain. If you enable the switch, all the new Report AppDomain isolation logic is disregarded. If you set the switch to false or remove it from the config files, the new logic kicks in and you will have fast reports rendered in the Report AppDomain. 

To run completely isolated on all renderings 

  <add key="LegacyReportAppDomainIsolation" value="true" /> 

If you add this switch to the CustomSettings.config on the server, you will be able to render reports in the high-security AppDomain, but you will have to set NetFx40_LegacySecurityPolicy to false or remove it completely. This will make all reports run slower. 

This switch is not available in the management UI. 

To control when to recycle the AppDomain 

  <add key="ReportAppDomainRecycleCount" value="1000" /> 

If you add this switch to the CustomSettings.config on the server and have enabled the new Report AppDomain, you will then be able to influence when the server will try to unload the Report AppDomain. I would not recommend changing this switch, which is why it is declared as internal. 

This switch is not available in the management UI. 

To control that custom RDLC layouts are trusted 

  <add key="ReportAppDomainIsolation" value="true" /> 

Specifies if application domain isolation is used for rendering custom RDLC layouts. Since custom reports are created by end-users in the client, they can contain some inline Visual Basic .NET code. For that reason, we would enforce secure AppDomain isolation to avoid any security risks from rendering custom RDLC layouts. However, it can considerably increase the time it takes to render reports.  

Disabling application domain isolation (<add key=”ReportAppDomainIsolation” value=” false ” /) can improve the rendering time but might have a negative impact on security and reliability.  

To be secure by default, we recommend that you leave this setting at the default value of true and change it only if you are running an on-premises installation where you can fully trust any custom report layout uploaded by end users. 

This does not apply to changes done through C/SIDE customization or AL extensions. 

 NOTE: The name of the switch will change in next major version because the name is not precise enough and we want to avoid confusion.  

The new switch will be <add key=IsolateCustomReportLayoutsToSecureAppDomain value=true /> 

 

Author:

Torben Wind Meyhoff – Dynamics 365 Business Central Server Architect