Open a Report in ASP.NET Core Application
- 6 minutes to read
This topic describes several ways to display a report in a Document Viewer Control. It also addresses common issues that may occur when the DevExpress Document Viewer opens a report.
Specify a Report
Once incorporated into a web page, the DevExpress Document Viewer control can load a report at startup. The Bind method allows you to bind the Document Viewer to a View Model or report. You can create a new report instance and pass it as the parameter or specify a report by name.
@model DevExpress.XtraReports.Web.WebDocumentViewer.WebDocumentViewerModel;
//...
@{
var viewerRender = Html.DevExpress().WebDocumentViewer("DocumentViewer")
.Height("1000px")
.Bind(Model);
@viewerRender.RenderHtml()
}
// ...
Bind to a View Model (Recommended)
Synchronous Mode
Your controller must use the WebDocumentViewerClientSideModelGenerator object to create a View Model. You can call the GetModel method overload to create a report model and pass it to the view.
using DevExpress.XtraReports.Web.WebDocumentViewer;
namespace AspNetCoreViewerSample.Models {
public class ViewerModel {
public WebDocumentViewerModel ViewerModelToBind { get; set; }
}
}
using DevExpress.XtraReports.Web.WebDocumentViewer;
using DevExpress.AspNetCore.Reporting.WebDocumentViewer;
using Microsoft.AspNetCore.Mvc;
namespace AspNetCoreViewerSample.Controllers {
public class HomeController : Controller {
public IActionResult Index() {
return View();
}
public IActionResult Error() {
Models.ErrorModel model = new Models.ErrorModel();
return View(model);
}
public IActionResult DocumentViewer(
[FromServices] IWebDocumentViewerClientSideModelGenerator viewerModelGenerator,
[FromQuery] string reportName) {
reportName = string.IsNullOrEmpty(reportName) ? "TestReport" : reportName;
var viewerModel = viewerModelGenerator.GetModel(reportName, WebDocumentViewerController.DefaultUri);
return View(viewerModel);
}
}
}
You should also implement and register the IReportProvider service that enables the Document Viewer to get a report instance by name:
using AspNetCoreViewerSample.PredefinedReports;
using DevExpress.XtraReports.Services;
using DevExpress.XtraReports.UI;
namespace AspNetCoreViewerSample.Services
{
public class CustomReportProvider : IReportProvider
{
public XtraReport GetReport(string id, ReportProviderContext context)
{
if (ReportsFactory.Reports.TryGetValue(id, out var report))
{
return report();
}
throw new DevExpress.XtraReports.Web.ClientControls.FaultException(string.Format("Could not find report '{0}'.", id));
}
}
}
using System;
using System.Collections.Generic;
using DevExpress.XtraReports.UI;
namespace AspNetCoreViewerSample.PredefinedReports {
public static class ReportsFactory
{
public static Dictionary<string, Func<XtraReport>> Reports = new Dictionary<string, Func<XtraReport>>()
{
["TestReport"] = () => new TestReport()
};
}
}
using DevExpress.XtraReports.Services;
using AspNetCoreViewerSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IReportProvider, CustomReportProvider>();
var app = builder.Build();
Asynchronous Mode
This strategy allows you to generate reports asynchronously. You can call the UseAsyncEngine method (at app startup) to activate asynchronous mode, and use the GetModelAsync method to proceed.
using DevExpress.XtraReports.Web.WebDocumentViewer;
namespace AspNetCoreViewerSample.Models {
public class ViewerModel {
public WebDocumentViewerModel ViewerModelToBind { get; set; }
}
}
using System.Threading.Tasks;
using DevExpress.AspNetCore.Reporting.WebDocumentViewer;
using DevExpress.XtraReports.Web.WebDocumentViewer;
using Microsoft.AspNetCore.Mvc;
namespace AspNetCoreViewerSample.Controllers {
public class HomeController : Controller {
public IActionResult Index() {
return View();
}
public IActionResult Error() {
Models.ErrorModel model = new Models.ErrorModel();
return View(model);
}
public async Task<IActionResult> Viewer(
[FromServices] IWebDocumentViewerClientSideModelGenerator clientSideModelGenerator,
[FromQuery] string reportName) {
var reportToOpen = string.IsNullOrEmpty(reportName) ? "TestReport" : reportName;
var model = new Models.ViewerModel {
ViewerModelToBind = await clientSideModelGenerator.GetModelAsync(reportToOpen, WebDocumentViewerController.DefaultUri)
};
return View(model);
}
}
}
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureReportingServices(configurator => {
configurator.UseAsyncEngine();
}
var app = builder.Build();
You should also implement and register the IReportProviderAsync service that enables the Document Viewer to get a report instance by name. The following code snippet searches for a report file (.REPX) with the specified name in the `Reports’ folder. If the search is successful, the code sample loads the report from the file and returns an instance of the report.
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DevExpress.XtraReports.Services;
using DevExpress.XtraReports.UI;
using Microsoft.AspNetCore.Hosting;
namespace AspNetCoreViewerSample.Services {
public class CustomReportProviderAsync : IReportProviderAsync {
public const string MyReportsDirectoryName = "Reports";
readonly IWebHostEnvironment env;
public CustomReportProviderAsync(IWebHostEnvironment env) {
this.env = env;
}
public async Task<XtraReport> GetReportAsync(string id, ReportProviderContext context) {
if(string.IsNullOrEmpty(id))
return null;
var reportDirectoryPath = Path.Combine(env.ContentRootPath, MyReportsDirectoryName);
var reportLaytoutFileInfo = new DirectoryInfo(reportDirectoryPath).GetFiles(id + ".repx", SearchOption.TopDirectoryOnly).SingleOrDefault();
if(reportLaytoutFileInfo == null)
return null;
var reportLayout = await File.ReadAllBytesAsync(reportLaytoutFileInfo.FullName);
using(var ms = new MemoryStream(reportLayout)) {
ms.Position = 0;
return XtraReport.FromXmlStream(ms);
}
}
}
}
using DevExpress.XtraReports.Services;
using AspNetCoreViewerSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureReportingServices(configurator => {
configurator.UseAsyncEngine();
}
builder.Services.AddScoped<IReportProviderAsync, CustomReportProviderAsync>();
var app = builder.Build();
Pass a Report Instance
You can instantiate a report and pass it to the Bind method as necessary. This approach consumes more memory because a new report instance is generated when the page reloads.
Specify a Report Name
If you specify a report name as the Bind method parameter, the Document Viewer uses report name resolution services (described below) to obtain the report by name.
You can also use client-side APIs and call the OpenReport method of the client Document Viewer object, or handle the Document Viewer client-side event and call the OpenReport method of the event sender object. These methods accept report name as a parameter and use report name resolution services.
Use Report Name Resolution Services
If you pass report name to a method that opens a report, you must implement and register one of the following services (to resolve report names).
- IReportProvider
- A recommended service for the Document Viewer and Report Designer. The primary advantage of IReportProvider service is that it can be attached to reports created at runtime. IReportProviderAsync uses asynchronous operations.
- ReportStorageWebExtension
- This service is called when no other report name resolution services are available. It is designed to obtain reports (stored in our REPX format) from external storage (a file or a database). Note that the GetData method returns a serialized report. If you use the GetData method to specify the default parameter value for a loaded report, set the Value property to the parameter value.
The following services have a higher priority than the previously mentioned services, although the scope of their use is limited.
- IWebDocumentViewerReportResolver
Allows you to parse a report name, create a report instance, and return it to the calling method. If you have a parameterized report, you can specify parameters in the report name passed to the service and use parameters within the report’s constructor.
The IWebDocumentViewerReportResolver service is Intended for use only with the Web Document Viewer, and enables you to implement name resolution differently in the Report Designer and Document Viewer. The XRSubreport control does not use this service.
The IWebDocumentViewerReportResolver service does not support asynchronous mode.
Open Subreports
The XRSubreport control defines additional reports (subreports) included in the host report. The control’s ReportSource property specifies the report instance used as a subreport, and the ReportSourceUrl property specifies report name. The ReportSourceUrl property has priority over the ReportSource property.
A subreport is opened automatically alongside the main report. To open a subreport, the Document Viewer passes the XRSubreport.ReportSourceUrl property value to available report name resolution services. You can use the IReportProvider service IReportProvider to resolve report names for both the main report and subreports.
Note
The following services cannot use the XRSubreport.ReportSourceUrl property value:
When both IWebDocumentViewerReportResolver and IReportProvider services are available, the IWebDocumentViewerReportResolver service resolves the main report name while the IReportProvider service is called to resolve the subreport name.