So jump in the way back machine, Sitefinity v4 shipped with a (really bad\buggy) eComm module. However part of that module was reporting using the Telerik.Reporting assemblies. Sometime around v12 or 13 they decomissioned the old eComm module (in favour of uCommerce), and subsequently stopped shipping the Telerik.Reporting DLLs with Sitefinity.

Well, I had to do some more reporting UIs, so we re-licensed the product so we could get the latest v15 assemblies and (more importantly) get access to the Visual Studio 2022 reporting designer. It's not REQUIRED to generate a report, you can do it all through code, but it does simplify it quite substantially.

So the most important part is to follow the installation doc the provide, but when you come to the part about registering routes IGNORE IT! It'll tell you to just call this

ReportsControllerConfiguration.RegisterRoutes(GlobalConfiguration.Configuration);

However when Sitefinity tries to load it'll crash right away because some of the default route names conflict with Sitefinity. What we have to do instead is manually register them, and here it is! Just call this in Global.asax INSTEAD if the ReportsControllerConfiguration.RegisterRoutes that they provide.

RegisterReportingRoutes.csView on GitHub
using System;
using System.Linq;
using System.Web.Http;
using System.Web.Routing;
using Telerik.Sitefinity.HealthMonitoring;

namespace SitefinityWebApp.App_Start { public class RegisterReportingRoutes { public static void RegisterRoutes() { using (var methodPerformanceRegion1 = new MethodPerformanceRegion("RegisterReportingRoutes")) { RouteTable.Routes.MapHttpRoute( name: "GetClientsSessionTimeoutSeconds", routeTemplate: "api/{controller}/clients/sessionTimeout", defaults: new { action = "GetClientsSessionTimeoutSeconds" });

            RouteTable.Routes.MapHttpRoute(
                name: "TelerikReportingResources",
                routeTemplate: "api/{controller}/resources/{folder}/{resourceName}",
                defaults: new { action = "Resources" });

            RouteTable.Routes.MapHttpRoute(
                name: "Clients",
                routeTemplate: "api/{controller}/clients/{clientID}",
                defaults: new { action = "Clients", clientID = RouteParameter.Optional });

            RouteTable.Routes.MapHttpRoute(
                name: "Instances",
                routeTemplate: "api/{controller}/clients/{clientID}/instances/{instanceID}",
                defaults: new { action = "Instances", instanceID = RouteParameter.Optional });

            RouteTable.Routes.MapHttpRoute(
                name: "DocumentResources",
                routeTemplate: "api/{controller}/clients/{clientID}/instances/{instanceID}/documents/{documentID}/resources/{resourceID}",
                defaults: new { action = "DocumentResources" });

            RouteTable.Routes.MapHttpRoute(
                name: "DocumentActions",
                routeTemplate: "api/{controller}/clients/{clientID}/instances/{instanceID}/documents/{documentID}/actions/{actionID}",
                defaults: new { action = "DocumentActions" });

            RouteTable.Routes.MapHttpRoute(
                name: "DocumentPages",
                routeTemplate: "api/{controller}/clients/{clientID}/instances/{instanceID}/documents/{documentID}/pages/{pageNumber}",
                defaults: new { action = "DocumentPages" });

            RouteTable.Routes.MapHttpRoute(
                name: "DocumentInfo",
                routeTemplate: "api/{controller}/clients/{clientID}/instances/{instanceID}/documents/{documentID}/info",
                defaults: new { action = "DocumentInfo" });

            RouteTable.Routes.MapHttpRoute(
                name: "Documents",
                routeTemplate: "api/{controller}/clients/{clientID}/instances/{instanceID}/documents/{documentID}",
                defaults: new { action = "Documents", documentID = RouteParameter.Optional });

            RouteTable.Routes.MapHttpRoute(
                name: "Parameters",
                routeTemplate: "api/{controller}/clients/{clientID}/parameters",
                defaults: new { action = "Parameters" });

            RouteTable.Routes.MapHttpRoute(
                name: "ApiDefault",
                routeTemplate: "api/{controller}/{action}");
        }
    }
}

}

So for the viewer part, here's the setup doc but I'll also post my Vue implementation here (still uses some jQuery, viewer requires it...). Feel free to use or not, and\or tweak. It is a shareable vue component I use inside other REPORTNAME.cshtml files like this

<telerik-report-viewer ref="viewer" v-if="renderReport" :params="params"></telerik-report-viewer>
reportviewer-vue.component.jsView on GitHub
Vue.component('telerikReportViewer', {
    props: ['params'],
    data: function () {
        return {
            viewer: null,
            paths: $reportPaths
        }
    },
    mounted: function () {
        var $that = this;
        console.log("Mounting Report");

    this.createReport();
},
methods: {
    createReport: function () {
        var $that = this;

        if ($that.viewer == null) {
            console.log("Creating Viewer");
            $that.viewer = $("#reportViewer1").telerik_ReportViewer({
                serviceUrl: "/api/reports/",
                templateUrl: $that.paths.template,
                reportSource: {
                    report: $that.paths.source,
                    parameters: this.params
                },
                //Options
                viewMode: telerikReportViewer.ViewModes.INTERACTIVE,
                scaleMode: telerikReportViewer.ScaleModes.FIT_PAGE_WIDTH,
                scale: 1.0//,
                //persistSession: true
            }).data("telerik_ReportViewer");
        } else {
            //Already initalized
            console.log("Viewer Exists");
            $that.viewer.reportSource({
                report: $that.paths.source,
                parameters: this.params
            });

            $that.viewer.refreshReport();
        }
    },
    refreshReport: function () {
        this.createReport();
    }
},

template: '&lt;div class="report-wrapper" style="min-height: 1000px"&gt;&lt;div id="reportViewer1"&gt;&lt;/div&gt;&lt;/div&gt;'

})

function nullifyValue(val) { if (val == "") return null; else return val; }

ReportViewer.cshtmlView on GitHub
@model string

@using Telerik.Sitefinity; @using Telerik.Sitefinity.Model; @using System.Web.Configuration; @using Medportal.Sitefinity.Controls; @using Telerik.Sitefinity.Frontend.Mvc.Helpers;

@{ var version = WebConfigurationManager.AppSettings["telerikReportingVersion"].ToString(); //Single source the version... var cssPath = $"/include/ReportViewer/styles/telerikReportViewer.css?v={version}"; //cache bust with the version var jsPath = $"/include/ReportViewer/js/telerikReportViewer-{version}.js"; //cache bust with the version var templatePath = $"/include/ReportViewer/templates/telerikReportViewerTemplate-FA.html?v={version}"; //cache bust with the version }

@Html.StyleSheet(cssPath, "plugins") //You need a "plugins" Html.Section defined in your layout @Html.Script(jsPath, "plugins") //You need a "plugins" Html.Section defined in your layout @Html.Script("/Mvc/Views/TelerikReporting/Resources/reportviewer-vue.component.js?v=" + Util.MpConfig.ScriptStylePostfix, "plugins") <style> #reportViewer1 { position: absolute; left: 5px; right: 5px; top: -1px; bottom: 5px; font-family: 'segoe ui', 'ms sans serif'; min-height: 1000px; overflow: hidden; } </style>

<script> var $reportPaths = { template: '@templatePath', source: '@Model' } </script>