Search code examples
c#reporting-servicesreportviewerlocalreport

C# LocalReport: how to add custom code from a different directory


I'm using the Microsoft Reporting Services in a .NET Framework 4.8 application, using the LocalReport class to rendere a .rdlc report.

In my report, I'm using some custom code from some additional assemblies, that I added to the report via de Report Properties -> References menu.

In my code, when I render the report, I add the assemblies do the LocalReport domain to make them available in the report, with some code like this:

var assemblyName = myCustomAssembly.GetName();
var strongAssemblyName = new StrongName(new StrongNamePublicKeyBlob(assemblyName.GetPublicKeyToken()), assemblyName.Name, assemblyName.Version)
localReport.AddFullTrustModuleInSandboxAppDomain(strongAssemblyName);

Now, this works perfectly... but only if the .DLL that contains the assembly I'm trying to add is in the same folder as my executable. If it is somewhere else (for example, in my case, in a subfolder) the report generation fails, with an exception telling me assembly could not be loaded because the dll file could not be found.

Is there a way to solve this? Can I tell the LocalReport to load the assemblies from a specific folder, or add a search path in some way? Or is the only solution to have the assemblies in the root folder of my app?


Solution

  • So, a lot of answers to other posts here on SO suggested to make it work by adding the subfolder(s) I needed to the domain's SetupInformation.PrivateBinPath property. This, however, didn't seem to have any effect at all. In fact, if I read the SetupInformation.PrivateBinPath property immediately after setting it, I got null in return.

    So, I've dug deeper and decompiled part of the .NET Framework source code to get to the bottom of it.

    First of all: it should be noted that the report processing happens in a sandboxed AppDomain that is created specifically for generating the report, it doesn't run directly on the app's main domain. However, by decompiling the ReportViewer assemblies, I've found that the sandboxed domain is constructed with the setup information taken from the base AppDomain.CurrentDomain.SetupInformation, so any paths set there should also be available to the report generation.

    From what I can see, the root problem is this: the SetupInformation property of the AppDomain class seems to return a copy of the SetupInformation. So, setting anything in there is absolutely useless, because you're operating on a copy that is then discarded.

    What ended up working, was setting this property in the App.config file, like this:

    <probing privatePath="my\sub\path"/>
    

    and that solved the issue!

    I'm still curious if there is a way to actually set the probing path dynamically in code rather than having it hardcoded in the .config file, any comment in that direction is welcome.