Wednesday, October 14, 2015

Using WebForms ReportViewer Control Inside AngularJS Single Page Application

WebForms ReportViewer control can be used to render reports deployed to SQL Server Reporting Servcies (SSRS) Report Server or reports which exists in local machine inside a web application. In this post, let’s see how we can use the WebForms ReportViewer control inside a AngularJS Single Page Application to render reports deployed in SSRS Report Server.

I have a SPA running with Visual Studio created using one of my previous posts “Creating an Empty ASP.NET Project Powered by AngularJS using Visual Studio”. And I have set of reports deployed to SSRS Report Server already (Here I am not going to explain how you can deploy reports to SSRS Report Server). Let’s extend the the same application to include the ReportViewer inside Home page. My plan is to add a ReportViewer control inside ASP.NET Web Form and embed that particular page inside home page using an iframe.

So for that let’s start by adding a WebForm to the project. In my example, I am creating a folder inside App folder named pages and adding the WebForm there and naming it as Viewer.aspx.
image
Add WebForm
Now on the designer of Viewer.aspx, I am adding a ReportViewer control, from the Visual Studio Toolbox.
image
ReportViewer
So this is how my Viewer.aspx looks like after adding the ReportViewer.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Viewer.aspx.cs" Inherits="AngularJSSample.App.pages.Viewer" %> 

<%@ Register Assembly="Microsoft.ReportViewer.WebForms, Version=12.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" Namespace="Microsoft.Reporting.WebForms" TagPrefix="rsweb" %> 

<!DOCTYPE html> 

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
 
        <div style="height: 600px;">
            <rsweb:ReportViewer ID="reportViewer" runat="server" Width="100%" Height="100%"></rsweb:ReportViewer>
        </div>
    </form>
</body>
</html>
I have done some styling here, you can also apply whatever the styles you need. Also I have added a script manager, because the ReportViewer web control requires a script manager on the web form. Now inside the Viewer.aspx code behind, we need to add the code for processing the report.
public partial class Viewer : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            if (!IsPostBack)
            {
                string reportId = Request.QueryString["id"].ToString();
 

                string reportServerUrl = ConfigurationManager.AppSettings["ReportServerURL"];
                string domain = ConfigurationManager.AppSettings["rsDomain"];
                string userName = ConfigurationManager.AppSettings["rsUserName"];
                string password = ConfigurationManager.AppSettings["rsPassword"];
                string reportPath = ConfigurationManager.AppSettings["ReportPath"];
 
                reportViewer.ServerReport.ReportServerUrl = new Uri(reportServerUrl);
                reportViewer.ServerReport.ReportServerCredentials = new ReportCredentials(userName, password, domain);
                reportViewer.ServerReport.ReportPath = string.Format(reportPath, reportId);
                reportViewer.ProcessingMode = ProcessingMode.Remote;
                reportViewer.ShowCredentialPrompts = false;
                reportViewer.ServerReport.Refresh();
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}
Here the page will receive a URL parameter through a query string which specifies the report id to render. Basically the report id would be the report name which is deployed to SSRS Report Server. And I am retrieving following values from the web.config file.
<appSettings>
    <add key="ReportServerUrl" value="REPORT_SERVER_URL" /> <!--http://localhost/ReportServer etc.-->
    <add key="rsDomain" value="DOMAIN" />
    <add key="rsUserName" value="USERNAME" />
    <add key="rsPassword" value="PASSWORD" />
    <add key="ReportPath" value="/PATH_TO_REPORT_FOLDER/{0}" />
</appSettings>
You can find your ReportServerUrl from the SQL Server Reporting Services Configuration Manager.

image
SQL Server Reporting Services Configuration Manager
For ReportServerCredentials, I have the following the class ReportCredentials which is an  implementation of IReportServerCredentials.
public class ReportCredentials : IReportServerCredentials
{
    public ReportCredentials(string userName, string password, string domain)
    {
        UserName = userName;
        Password = password;
        Domain = domain;
    }
 
    public WindowsIdentity ImpersonationUser
    {
        get
        {
            return null;
        }
    }

    public ICredentials NetworkCredentials
    {
        get
        {
            return new NetworkCredential(UserName, Password, Domain);
        }
    }
 
    private string UserName { get; set; }
    private string Password { get; set; }
    private string Domain { get; set; }
 
    public bool GetFormsCredentials(out Cookie authCookie, out string userName, out string password, out string authority)
    {
        authCookie = null;
        userName = password = authority = null;
        return false;
    }
}
Here since I am not using any impersonation, ImpersonationUser is null and since I am not using forms authentication GetFormsCredentials method is returning false.

Next step is to show the list of reports and embed the Viewer.aspx web form inside the html page.
<div>
    <div ng-repeat="report in reports">
        <input type="button" ng-click="openReport(report.id)" value="{{report.name}}" />
    </div>
    
    <form class="form-horizontal" style="height: 100%;">
        <div style=" height:100%; background-color: transparent">
            <iframe src="{{'http://localhost:29986/App/pages/Viewer.aspx?id=' + reportId | trustAsResourceUrl}}" style="width:100%;height:600px"></iframe>
        </div>
    </form>
</div>
Here I have set of reports where I am ng-repeating and when clicking on an item, I am invoking the openReport method. Basically it will assign the report id to the model variable which I am passing as a query parameter to Viewer.aspx. On click on report items, it will make the iframe source different, which will load the particular report.
.controller('homeController', function ($scope) {
    $scope.message = "Now viewing home!";
 
    $scope.reports = [
       {
           id: 'Report1',
           name: 'Report 1'
       },
       {
           id: 'Report2',
           name: 'Report 2'
       },
    ]; 

    $scope.reportId = $scope.reports[0].id; 

    $scope.openReport = function (reportId)
    {
        $scope.reportId = reportId;
    }
})
Even you can get the available reports by calling the SSRS Report Server Web Service.

I have the following directive as well, to make Strict Contextual Escaping for the web forms absolute url.
.filter('trustAsResourceUrl', ['$sce', function ($sce) {
    return function (val) {
        return $sce.trustAsResourceUrl(val);
    };
}]);

So that’s it. Now let’s run the application.
image
Report 1
image
Report 2
I am uploading the full code sample to my OneDrive. Do check that out!


Happy Coding.

Regards,
Jaliya