Our ReportViewer WebForms Control is one of the best in the industry. It has a unique rendering engine that we call WebPaper. The technique we use is similar to what Google docs uses for their document viewer. There is a common issue across all report viewers, rendering inconsistently in browsers. Report designers spend countless hours creating reports, but their reports often render very differently in different browsers. Since reports usually require very strict layout, we decided to take out this dependency on HTML rendering. WebPaper actually renders high fidelity graphics on the server and sends them asynchronously to the client as needed. This technique allows us to achieve pixel perfection for every report across every browser.

Create an MVC Project

Let's take a look at how we can use this popular Control in an MVC application that uses the Razor View Engine. Before we begin, you should probably download a working sample of ReportViewer in MVC that we have published. You will also want to have Studio for ASP.NET Wijmo installed. First, we need to create a new MVC4 application in Visual Studio.

Add References

Now we need to add references to the ComponentOne assemblies. Make sure you have Studio for ASP.NET Wijmo installed and then open the Add References dialog in your project. You might want to go get a cup of coffee while waiting for this to load. Once loaded, type in "ComponentOne" into the search box and select: The assemblies referenced are:

  • C1.Web.Wijmo.Controls.4
  • C1.C1Report

Custom UrlExtension

In order to use the ReportViewer, we are going to need the JavaScript and CSS files from within it. So let's create a custom UrlExtension called WebResource that returns a WebResource URL from the assembly. This is something done easily in WebForms, but not part of MVC. So, create a file with the path of "Helpers/WebResource.cs" and add the UrlExtension like so:


using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Web;  
using System.Web.Mvc;  
using System.Web.UI;  


public static class UrlExtensions  
{  
    //URL Helper to return webresource.axd link to embedded resources in an assembly  
    public static string WebResource(this UrlHelper urlHelper, Type type, string resourcePath)  
    {  
        //Create page in order to use ClientScript. Razor Views do no have access to Page otherwise this could be done in the View itself.   
        var page = new Page();  
        return page.ClientScript.GetWebResourceUrl(type, resourcePath);  
    }  
}  

Reference Wijmo Scripts

The ReportViewer uses Wijmo widgets for its UI. You can skip this step if you are using our Wijmo MVC Project Template. Otherwise, we will need to include jQuery, jQuery UI, and Wijmo like so:


<!--jQuery References-->  
<script src="http://code.jquery.com/jquery-1.8.2.min.js" type="text/javascript"></script>  
<script src="http://code.jquery.com/ui/1.9.1/jquery-ui.min.js" type="text/javascript"></script>  
<!--Theme-->  
<link href="http://cdn.wijmo.com/themes/aristo/jquery-wijmo.css" rel="stylesheet" type="text/css" title="rocket-jqueryui" />  
<!--Wijmo Widgets CSS-->  
<link href="http://cdn.wijmo.com/jquery.wijmo-complete.all.2.3.5.min.css" rel="stylesheet" type="text/css" />  
<!--Wijmo Widgets JavaScript-->  
<script src="http://cdn.wijmo.com/jquery.wijmo-open.all.2.3.5.min.js" type="text/javascript"></script>  
<script src="http://cdn.wijmo.com/jquery.wijmo-complete.all.2.3.5.min.js" type="text/javascript"></script>  

Reference Embedded Resources

Next, let's add the JavaScript and CSS files we need using our WebResource UrlExtension. Open "Views/Shared/_Layout.cshtml" and add the following references.


    <link rel="stylesheet" type="text/css" href="@Url.WebResource(typeof(C1.Web.Wijmo.Controls.C1TargetControlBase), "C1.Web.Wijmo.Controls.C1ReportViewer.Resources.wijreportviewer.css")" />  
    <link rel="stylesheet" type="text/css" href="@Url.WebResource(typeof(C1.Web.Wijmo.Controls.C1TargetControlBase), "C1.Web.Wijmo.Controls.C1ReportViewer.Resources.c1reportviewer.css")" />  
    <script src="@Url.WebResource(typeof(C1.Web.Wijmo.Controls.C1TargetControlBase), "C1.Web.Wijmo.Controls.Resources.wijmo.extensions.c1splitter.js")" type="text/javascript"></script>  
    <script src="@Url.WebResource(typeof(C1.Web.Wijmo.Controls.C1TargetControlBase), "C1.Web.Wijmo.Controls.Resources.wijmo.extensions.c1menu.js")" type="text/javascript"></script>  
    <script src="@Url.WebResource(typeof(C1.Web.Wijmo.Controls.C1TargetControlBase), "C1.Web.Wijmo.Controls.C1ReportViewer.Resources.wijreportviewer.js")" type="text/javascript"></script>  
    <script src="@Url.WebResource(typeof(C1.Web.Wijmo.Controls.C1TargetControlBase), "C1.Web.Wijmo.Controls.C1ReportViewer.Resources.c1reportviewer.js")" type="text/javascript"></script>  

Add ReportService to Project

We will need a Web Service for the ReportViewer to communicate to the server for WebPaper rendering. This is a service that ComponentOne provides. It gets added automatically in WebForms when you put the Control on a Form, but we will have to manually add it here. I recommend that you download the sample project and copy the reportService.ashx file from there. Here are the contents of the file if you wish to create it manually.


//ver:[120921]  
<%@ WebHandler Language="C#" Class="reportService" %>  

using System;  
using System.IO;  
using System.Web;  
using System.Collections;  
using System.Collections.Specialized;  

using C1.Web.Wijmo.Controls;  
using C1.Web.Wijmo.Controls.C1ReportViewer;  
using C1.Web.Wijmo.Controls.C1ReportViewer.ReportService;  

public class reportService : IHttpHandler, System.Web.SessionState.IRequiresSessionState  
{  

public void ProcessRequest(HttpContext context)  
    {  
object answer;  
        NameValueCollection args = context.Request.Params;  
        string command = args["command"];          
        if (string.IsNullOrEmpty(command))  
            command = "";  
string fileName = (string)args["fileName"];  
string resolvedFileName = fileName;  
if (!string.IsNullOrEmpty(resolvedFileName))  
{  
if (!resolvedFileName.StartsWith("~/"))  
{  
resolvedFileName = "~/" + resolvedFileName.Replace(@"\\", "/");  
}  
resolvedFileName = HttpContext.Current.Server.MapPath(resolvedFileName);  
}  
string clientId = args["clientId"];  
if (string.IsNullOrEmpty(clientId))  
{  
throw new HttpException("Parameter clientId can not be empty.");  
}  
string reportName = args["reportName"];  
string cookie = args["cookie"];  
string documentKey = string.IsNullOrEmpty(args["documentKey"]) ? "" : args["documentKey"];  
int dpi = string.IsNullOrEmpty(args["dpi"]) ? 96 : int.Parse(args["dpi"]);  
int zoom = string.IsNullOrEmpty(args["zoom"]) ? 100 : int.Parse(args["zoom"]);  
int pageIndex = string.IsNullOrEmpty(args["pageIndex"]) ? 0 : int.Parse(args["pageIndex"]);  
bool printTarget = string.IsNullOrEmpty(args["printTarget"]) ? false : bool.Parse(args["printTarget"]);  

IC1WebReportService reportService = null;  

if (!string.IsNullOrEmpty(documentKey))  
{  
reportService = C1WebReportServiceHelper.MakeHelper(documentKey);  
}  
if (reportService == null)  
{  
object inMemoryDoc = null;  
if (C1.Web.Wijmo.Controls.C1ReportViewer.C1ReportViewer.IsDocumentRegistered((string)args["fileName"])  
&& !C1.Web.Wijmo.Controls.C1ReportViewer.C1ReportViewer.HasCachedDocument(resolvedFileName, reportName))  
{  
inMemoryDoc = C1.Web.Wijmo.Controls.C1ReportViewer.C1ReportViewer.GetDocument((string)args["fileName"]);  
}  
string reportsFolderPath = (string)HttpContext.Current.Cache[clientId + "_ReportsFolderPath"];  
ReportCache reportCache = (ReportCache)HttpContext.Current.Cache[clientId + "_ReportCache"];  
System.Collections.Generic.Dictionary<string, object> extraOptions =  
(System.Collections.Generic.Dictionary<string, object>)HttpContext.Current.Cache[clientId + "_ExtraOptions"];  
if (reportCache == null)  
{  
reportCache = new ReportCache();  
}  
if (extraOptions == null)  
{  
extraOptions = new System.Collections.Generic.Dictionary<string, object>();  
}  
if (string.IsNullOrEmpty(reportsFolderPath))  
{  
reportsFolderPath = "~/tempReports";  
}  

reportService = new C1WebReportServiceHelper(inMemoryDoc,  
reportCache,  
extraOptions,  
ViewType.PageImages,  
HttpContext.Current.Server.MapPath(reportsFolderPath), VirtualPathUtility.ToAbsolute(reportsFolderPath));  
}  
        byte[] buffer;          
        switch (command)  
        {  
case "error":  
throw new Exception("test error");  
case "generate":  
case "status":  
DocumentStatus documentStatus = reportService.GetDocumentStatus(resolvedFileName, reportName, ParseReportParams(args["reportParams"]), cookie);  
                context.Response.ContentType = "application/json";  
answer = JsonHelper.ObjectToString(documentStatus, null, true);  
context.Response.Write(answer);  
                context.Response.End();  
                break;  
case "markup":  
context.Response.ContentType = "application/json";  
answer = reportService.GetPageImagesMarkup(documentKey,  
int.Parse(args["dpi"]), int.Parse(args["zoom"]),  
(int[])((System.Collections.ArrayList)ToArrayList(args["pageIndices"])).ToArray(typeof(int)),  
bool.Parse(args["getImagesOnly"]));  
context.Response.Write(C1.Web.Wijmo.Controls.JsonHelper.ObjectToString(answer, null, true));  
context.Response.End();  
break;  
            case "dialog":  
                context.Response.ContentType = "text/x-html";  
                context.Response.Write(GetDialogTemplateContent((string)args["name"]));  
                context.Response.End();  
                break;  
case "search":  
context.Response.ContentType = "application/json";  
answer = reportService.SearchText(documentKey,  
ReadTextPostData(context.Request, "query"),  
bool.Parse(args["caseSensitive"]),  
bool.Parse(args["useRegExp"]));  
context.Response.Write(C1.Web.Wijmo.Controls.JsonHelper.ObjectToString(answer, null, true));  
context.Response.End();  
break;  
case "outline":  
context.Response.ContentType = "application/json";  
answer = reportService.GetDocumentOutline(documentKey);  
context.Response.Write(C1.Web.Wijmo.Controls.JsonHelper.ObjectToString(answer, null, true));  
context.Response.End();  
break;  
            case "Export":  
                /*  
                Response.Buffer = false;  
                Response.AppendHeader("Content-Type", "octet-stream");  
                Response.AppendHeader("Content-Disposition", "attachment");  
                Response.Flush();  
                */  
                string exportFormat = args["exportFormat"];  

string url = reportService.ExportToFile(args["documentKey"], exportFormat, args["exportedFileName"]);  
                context.Response.ContentType = "text/html";  

                System.Text.StringBuilder htmlBuilder = new System.Text.StringBuilder();  
                htmlBuilder.AppendLine("<html>");  
                htmlBuilder.AppendLine("<header>");  
                htmlBuilder.AppendLine("<title>Download " + exportFormat + "</title>");  
                if (!url.StartsWith("error:"))  
                    htmlBuilder.AppendLine("<meta http-equiv=\\"REFRESH\\" CONTENT=\\"5; url=" + url + "\\">");  
                htmlBuilder.AppendLine("</header>");  
                htmlBuilder.AppendLine("<body>");  
                if (!url.StartsWith("error:"))  
                {  
                    htmlBuilder.AppendLine("<h1>");  
htmlBuilder.AppendLine(C1.Web.Wijmo.Controls.C1ReportViewer.C1ReportViewer.LocalizeString("C1ReportViewer.HttpHandler.ExportedDocumentDownload", "Exported document download."));  
                    htmlBuilder.AppendLine("</h1>");  
                    htmlBuilder.AppendLine("<p>");  
htmlBuilder.AppendLine(  
string.Format(  
C1.Web.Wijmo.Controls.C1ReportViewer.C1ReportViewer.LocalizeString("C1ReportViewer.HttpHandler.DownloadMsgFormat",  
"Your download should begin shortly. If your download does not start in approximately 10 seconds, you can <a href=\\"{0}\\">click here</a> to launch the download."), url));  
                    htmlBuilder.AppendLine("</p>");  

                    htmlBuilder.AppendLine("<div style=\\"background-color:#cccccc;\\">");  
                    htmlBuilder.AppendLine(  
string.Format(C1.Web.Wijmo.Controls.C1ReportViewer.C1ReportViewer.LocalizeString("C1ReportViewer.HttpHandler.DownloadUrlFormat", "Download URL: <a href=\\"{0}\\">{0}</a>"), url)  
 );  
                    htmlBuilder.AppendLine("<br />");  
htmlBuilder.AppendLine(  
string.Format(C1.Web.Wijmo.Controls.C1ReportViewer.C1ReportViewer.LocalizeString("C1ReportViewer.HttpHandler.ExportFormatMsgFormat", "Export Format: {0}"), exportFormat)  
);  
                    htmlBuilder.AppendLine("<br />");  
htmlBuilder.AppendLine(  
string.Format(C1.Web.Wijmo.Controls.C1ReportViewer.C1ReportViewer.LocalizeString("C1ReportViewer.HttpHandler.InternalDocKeyMsgFormat", "Internal Document Key: {0}"), args["documentKey"])  
);  
                    htmlBuilder.AppendLine("</div>");                                          

                }  
                else  
                {  
                    htmlBuilder.AppendLine("<h2 style=\\"color:red;\\">" + url + "</h2>");  
                }  
                htmlBuilder.AppendLine("</body>");  
                htmlBuilder.AppendLine("</html>");  
                context.Response.Write(htmlBuilder.ToString());        
                context.Response.End();  
                break;  
            case "":  
            default:  
                context.Response.Clear();  
                context.Response.ContentType = "image/png";  
buffer = reportService.GetPageImage(documentKey, dpi, zoom, pageIndex, printTarget);  
                context.Response.OutputStream.Write(buffer, 0, buffer.Length);  
                context.Response.End();  
                break;  
        }  
}  

internal static string ReadTextPostData(HttpRequest httpListenerRequest)  
{  
return ReadTextPostData(httpListenerRequest, null);  
}  
internal static string ReadTextPostData(HttpRequest httpListenerRequest, string key)  
{  
StreamReader reader = new StreamReader(httpListenerRequest.InputStream);  
string s = reader.ReadToEnd();  
if (!string.IsNullOrEmpty(key) && s.StartsWith(key + "="))  
{  
s = s.Substring((key+"=").Length);  
}  
if (s.StartsWith("text="))  
{  
s = s.Substring("text=".Length);  
}  
return s;  
}  

internal static Hashtable ReadJsonPostData(HttpRequest httpListenerRequest)  
{  
StreamReader reader = new StreamReader(httpListenerRequest.InputStream);  
string json = reader.ReadToEnd();  
if (json.StartsWith("json="))  
{  
json = json.Substring("json=".Length);  
}  
if (json.StartsWith("jsonData="))  
{  
json = json.Substring("jsonData=".Length);  
}  
Hashtable ht = JsonHelper.StringToObject(json);  
return ht;  
}  

ReportParameterValue[] ParseReportParams(string jsonStr)  
{  
ReportParameterValue[] reportParams = null;  
if (!string.IsNullOrEmpty(jsonStr))  
{  
Hashtable reportParamsObj = JsonHelper.StringToObject(jsonStr);  
if (reportParamsObj != null)  
{  
ArrayList reportParamsArr = (ArrayList)reportParamsObj["reportParameters"];  
//reportParams  
reportParams = new ReportParameterValue[reportParamsArr.Count];  
for (int i = 0; i < reportParamsArr.Count; i++)  
{  
Hashtable par = (Hashtable)reportParamsArr[i];  
reportParams[i] = new ReportParameterValue();  
reportParams[i].n = (string)par["n"];  
reportParams[i].vs = ((ArrayList)par["vs"]).ToArray();  
}  
}  
}  
return reportParams;  
}  

    string GetDialogTemplateContent(string dialogName)  
    {  
string localPath = HttpContext.Current.Server.MapPath("~/ReportDialogs/" + dialogName + ".htm");  
        if (System.IO.File.Exists(localPath))  
        {  
            using (System.IO.StreamReader reader = new System.IO.StreamReader(localPath))  
            {  
                string s = reader.ReadToEnd();  
                return s;  
            }  
        }  
return C1.Web.Wijmo.Controls.C1ReportViewer.C1ReportViewer.GetDialogTemplateContent(dialogName);  
    }  

System.Collections.ArrayList ToArrayList(string s)  
{  
int k;  
s = s.Trim(new char[] { '[', ']' });  
string[] indicesStrArr = s.Split(',');  
System.Collections.ArrayList arr = new System.Collections.ArrayList();  
for (int i = 0; i < indicesStrArr.Length; i++)  
{  
if (int.TryParse(indicesStrArr[i], out k))  
{  
arr.Add(k);  
}  
}  
return arr;  
}  

    public bool IsReusable  
    {  
        get  
        {  
            return false;  
        }  
    }  
}              

Add ReportViewer Markup

Now we can begin adding ReportViewer widgets in our application. Open up "Views/Home/Index.cshtml" and add the following HTML markup that the ReportViewer uses for its toolbar and viewing pane. This markup can be simplified if you do not want all of the bells and whistles in the toolbar.


<div id="reportviewersample" style="width: 800px; height: 600px;">  
<div class="c1-c1reportviewer-toolbar ui-widget-header ui-corner-all">  
<button class="print" title="Print">  
Print</button><span class="exportgroup">  
<button class="export">  
Export</button>  
<button class="select\_export\_format">  
select export format</button></span>  
<button class="firstpage" title="First page">  
First page</button>  
<button class="previouspage" title="Previous page">  
Previous page</button><span><input type="text" class="pageindex" value="1" />  
/  
<label class="pagecount">  
0</label></span>  
<button class="nextpage" title="Next page">  
Next page</button>  
<button class="lastpage" title="Last page">  
Last page</button>  
<button class="zoomout" title="Zoom out">  
Zoom out</button>  
<button class="zoomin" title="Zoom in">  
Zoom in</button><select class="zoom"><option value="10%">10%</option>  
<option value="25%">25%</option>  
<option value="50%">50%</option>  
<option value="75%">75%</option>  
<option selected="selected" value="100%">100%</option>  
<option value="125%">125%</option>  
<option value="150%">150%</option>  
<option value="200%">200%</option>  
<option value="400%">400%</option>  
<option value="actual size">Actual size</option>  
<option value="fit page">Fit page</option>  
<option value="fit width">Fit width</option>  
<option value="fit height">Fit height</option>  
</select><span><input type="checkbox" id="c1reportviewer\_continuousview\_0" class="continuousview" /><label  
for="c1reportviewer\_continuousview\_0">continuous view</label></span><span><input  
type="checkbox" id="c1reportviewer\_fullscreenmode\_1" class="fullscreenmode" /><label  
for="c1reportviewer\_fullscreenmode\_1">full screen</label></span></div>  
<div id="ctl15" class="__c1splitter  c1-c1reportviewer-splitter" style="height: 250px;  
width: 400px;">  
<div>  
<div class="c1-c1reportviewer-tabs">  
<ul>  
</ul>  
</div>  
</div>  
<div>  
<div class="wijmo-wijreportviewer-reportpane">  
<center class="wijmo-wijreportviewer-content">  
</center>  
</div>  
</div>  
</div>  
<div class="c1-c1reportviewer-statusbar">  
</div>  
</div>  

Initialize the ReportViewer

The last thing we need to do is initialize the ReportViewer widget. We can do this by adding a script block to "Views/Home/Index.cshtml" that initializes the c1reportviewer and sets its options. The reportService option is the url for the reportService.ashx we added. The fileName option is the report definition file (Xml, RDL etc). And the reportName is the name of the report to load within the file.


$(document).ready(function () {  
$("#reportviewersample").c1reportviewer({  
reportServiceUrl: "@Url.Content("~/reportService.ashx")",  
fileName: "C1ReportXML/CommonTasks.xml",  
reportName: "01: Alternating Background (Greenbar report)",  
zoom: "fit width",  
});  
});  

Run

The result should be a C1ReportViewer running in MVC4. The toolbar buttons and controls will automatically call the service and re-render the report on the server. So far, this is the only pure MVC implementation of a ReportViewer I have seen. The others I have seen (and made) are dependent on the WebForms View Engine and ASPX views. So I hope you find this helpful. Please don't forget to download the reference sample to see more Reports rendered and different options for the ReportViewer. You can also see a view this sample online.