Reporting is an integral part of any business application and the ability to integrate reporting into your latest and greatest ASP.NET MVC application is very likely a growing need. ComponentOne provides a very mature reporting control and web viewer in the Studio for ASP.NET Wijmo suite. These reporting tools can be used in an MVC application in association with the report service to display reports.

Let's see how we can preview an existing report in an MVC application using the ASP.NET web viewer. The goal is to be able to:

  • Preview the reports residing in different XML files.

  • Be able to change the record source at run time.


I'd like to preview two reports in my application; one is a report of the most expensive products and the other is a country wide quarterly order report. These reports have already been created using the ComponentOne Report Designer tool.

In Visual Studio we have created an MVC project and added references to C1Report and Wijmo.Controls DLL.

I will also need a model with properties for document, report name and the country for which I need to preview the report. Hence in an MVC application, I create the following model.

 public class ReportModel
{
public string DocName { get; set; }
public string ReportName { get; set; }
public SelectList Countries { get; private set; }

public ReportModel(string docName, string reportName, IEnumerable countries, string selectedCountry)
{
DocName = docName;
ReportName = reportName;
Countries = new SelectList(countries, selectedCountry);
}
}


Next we need a controller which will provide and register the report to the view. This controller will need the namespace "C1.Web.Wijmo.Controls.C1ReportViewer;". I will have to register the report document with "C1ReportViewer" and then assign this document to the "DocName" and "ReportName" properties of the "ReportModel" created above. Also I have chosen to create the report queries here for demo purpose, however these queries should reside in a separate file from which they can be read. Next we assign these queries to the report recordsource based on the choice of report to preview. Here is the code for ActionResult which will do just what we need.

public class ReportController : Controller
{
string dbPath = "|DATADIRECTORY|\\TwoTables.mdb";
string docName, rptName, rptPath, query;

public ActionResult Demo()
{
var country = this.Request["ddlCountry"];

if (country == null)
{
query = "SELECT DISTINCT [Ten Most Expensive Products].TenMostExpensiveProducts, [Ten Most Expensive Products].UnitPrice FROM [Ten Most Expensive Products];";
rptPath = HttpContext.Server.MapPath("~/ReportLayout/MostExpensive.xml");
docName = "MostExpensive";
rptName = docName;
}
else
{
query = "SELECT DISTINCT [Quarterly Orders].CustomerID, [Quarterly Orders].CompanyName,[Quarterly Orders].City, [Quarterly Orders].Country FROM [Quarterly Orders] WHERE [Quarterly Orders].Country = \"" + country + "\";";
rptPath = HttpContext.Server.MapPath("~/ReportLayout/QuarterlyOrdersReport.xml");
docName = "Quarterly Orders Report";
rptName = docName;

if (C1ReportViewer.IsDocumentRegistered(docName))
{
C1ReportViewer.UnRegisterDocument(docName);
}
}

C1ReportViewer.RegisterDocument(docName, MakeDoc);

ReportModel model = new ReportModel(docName, rptName + "_" + country, new string[] { "All", "Austria", "Germany", "UK", "USA" }, country ?? "All");

return View(model);
}

public C1PrintDocument MakeDoc()
{
C1Report report = new C1Report();

report.Load(rptPath, rptName);
report.DataSource.ConnectionString = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + dbPath + ";Persist Security Info=False";
report.DataSource.RecordSource = query;
report.Render();
return report.C1Document;
}
}


To use the ReportViewer in MVC we need the javascipt and CSS from the assembly. Hence we create a custom UrlExtension called WebResource that returns a WebResource URL from the assembly.

 public static string GetWebResourceUrl(this UrlHelper urlHelper, Type type, string resourceName)
{
Page page = new Page();
return page.ClientScript.GetWebResourceUrl(type, resourceName);
}


Now we need to add the JavaScript and CSS references for the ReportViewer to our view file, I have named the view as "Demo". Open the view and add the following references.

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


The ReportViewer needs Wijmo widgets for the viewer UI, so we are going to add the CDN reference for Wijmo to our view.

<script src="http://code.jquery.com/jquery-1.11.1.min.js" type="text/javascript"></script>
<script src="http://code.jquery.com/ui/1.11.0/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-pro.all.3.20142.46.min.css" rel="stylesheet" type="text/css" />
<!--Wijmo Widgets JavaScript-->
<script src="http://cdn.wijmo.com/jquery.wijmo-open.all.3.20142.46.min.js" type="text/javascript"></script>
<script src="http://cdn.wijmo.com/jquery.wijmo-pro.all.3.20142.46.min.js" type="text/javascript"></script>


We will need a Web Service for the ReportViewer to communicate to the server for rendering the report. In Web Forms this is automatically done when we add the viewer control, however here in MVC we need to manually add it. The name of the service is "reportService.ashx". Here is the code from the service.

//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"];
System.Collections.Generic.Dictionary<string, object> extraOptions =
(System.Collections.Generic.Dictionary<string, object>)HttpContext.Current.Cache[clientId + "_ExtraOptions"];
string url = reportService.ExportToFile(args["documentKey"], exportFormat, args["exportedFileName"], extraOptions);
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 "getImageUrls":
context.Response.Clear();
context.Response.ContentType = "text/html";
string[] imageUrls = reportService.GetPageImageUrls(documentKey, dpi, zoom);
string strUrls = string.Empty;
foreach (string imgUrl in imageUrls)
{
if (string.IsNullOrEmpty(strUrls))
{
strUrls += string.Format("{0}://{1}:{2}{3}", context.Request.Url.Scheme, context.Request.Url.Host, context.Request.Url.Port, imgUrl);
}
else
{
strUrls += "," + string.Format("{0}://{1}:{2}{3}", context.Request.Url.Scheme, context.Request.Url.Host, context.Request.Url.Port, imgUrl);
}
}
context.Response.Write(strUrls);
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;
}
}

}


Now it is time to create the reportviewer in the view, so we add the following mark-up to the view:

@using (Html.BeginForm("Demo", "Report"))
{
<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>

@Html.DropDownList("ddlCountry", Model.Countries);
<button type="submit">Apply</button>
}


Next we initialize the report viewer in JavaScript and set the following:
1. reportServiceURL
2. fileName where the report resides.
3. reportName which we want to preview.

Here is the JavaScipt for the above

[javascript]$(document).ready(function () {
$("#reportviewersample").c1reportviewer({
reportServiceUrl: "@Url.Content("~/reportService.ashx")",
fileName: "@Model.DocName",
reportName: "@Model.ReportName",
zoom: "fit width",
});

});[/javascript]

Note that we are assigning whatever report is selected in the dropdown and correspondingly the controller will set the recordsource for the report.

Here is the preview that we get when we run the sample that we created above.

ReportSample

You can download the sample here C1ReportInMVC .

This was a short walk-through on MVC reporting with ComponentOne ReportViewer, you can also view our previous blog post for detailed information on the same topic.

Thanks for reading.