This example demonstrates how to use custom types for report parameters, create custom parameter editors, and implement serialization/deserialization logic.
Files to Review
- CustomParameterType.cs
- CustomDataSerializer.cs
- CustomReportStorageWebExtension.cs
- HomeController.cs
- Viewer.cshtml
- Designer.cshtml
Documentation
- Use Report Parameters
- XML Serialization
- Reporting — Safe Deserialization
- Tasks and Solutions for ASP.NET Core Applications
More Examples
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
C#using System;
using System.ComponentModel;
using System.Globalization;
namespace CustomParameterEditorAspNetCoreExample {
[TypeConverter(typeof(CustomParameterTypeConverter))]
public class CustomParameterType {
public string Value { get; set; }
public override string ToString() {
return Value;
}
}
public class CustomParameterTypeConverter : TypeConverter {
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType) {
if (destinationType == typeof(string)) {
return ((CustomParameterType)value).Value;
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool CanConvertTo(ITypeDescriptorContext context,
Type destinationType) {
return destinationType == typeof(string) ||
base.CanConvertTo(context, destinationType);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value) {
var valueString = value as string;
if (valueString != null) {
return new CustomParameterType { Value = valueString };
}
return base.ConvertFrom(context, culture, value);
}
}
}
C#using DevExpress.XtraReports.Native;
using System.ComponentModel;
namespace CustomParameterEditorAspNetCoreExample {
[TypeConverter(typeof(CustomParameterTypeConverter))]
public class CustomDataSerializer : IDataSerializer {
public const string Name = "myCustomDataSerializer";
public bool CanDeserialize(string value, string typeName, object extensionProvider) {
bool canDeserialize = typeName == typeof(CustomParameterType).FullName;
return canDeserialize;
}
public bool CanSerialize(object data, object extensionProvider) {
return data is CustomParameterType;
}
public object Deserialize(string value, string typeName, object extensionProvider) {
if (typeName == typeof(CustomParameterType).FullName) {
return new CustomParameterType { Value = value };
}
return null;
}
public string Serialize(object data, object extensionProvider) {
var parameter = data as CustomParameterType;
return parameter != null ? parameter.Value : null;
}
}
}
C#using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using DevExpress.XtraReports.Web.Extensions;
using DevExpress.XtraReports.UI;
using Microsoft.AspNetCore.Hosting;
using CustomParameterEditorAspNetCoreExample.PredefinedReports;
using System.Threading.Tasks;
namespace CustomParameterEditorAspNetCoreExample.Services {
public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension {
readonly string ReportDirectory;
const string FileExtension = ".repx";
public CustomReportStorageWebExtension(IWebHostEnvironment env) {
ReportDirectory = Path.Combine(env.ContentRootPath, "Reports");
if (!Directory.Exists(ReportDirectory)) {
Directory.CreateDirectory(ReportDirectory);
}
}
public override Task AfterGetDataAsync(string url, XtraReport report) {
var report2 = report;
return base.AfterGetDataAsync(url, report);
}
public override bool CanSetData(string url) {
// Determines whether or not it is possible to store a report by a given URL.
// For instance, make the CanSetData method return false for reports that should be read-only in your storage.
// This method is called only for valid URLs (i.e., if the IsValidUrl method returned true) before the SetData method is called.
return true;
}
public override bool IsValidUrl(string url) {
// Determines whether or not the URL passed to the current Report Storage is valid.
// For instance, implement your own logic to prohibit URLs that contain white spaces or some other special characters.
// This method is called before the CanSetData and GetData methods.
return Path.GetFileName(url) == url;
}
public override byte[] GetData(string url) {
// Returns report layout data stored in a Report Storage using the specified URL.
// This method is called only for valid URLs after the IsValidUrl method is called.
try {
if (Directory.EnumerateFiles(ReportDirectory).Select(Path.GetFileNameWithoutExtension).Contains(url)) {
return File.ReadAllBytes(Path.Combine(ReportDirectory, url + FileExtension));
}
if (ReportsFactory.Reports.ContainsKey(url)) {
using (MemoryStream ms = new MemoryStream()) {
ReportsFactory.Reports[url]().SaveLayoutToXml(ms);
return ms.ToArray();
}
}
}
catch (Exception ex) {
throw new DevExpress.XtraReports.Web.ClientControls.FaultException("Could not get report data.", ex);
}
throw new DevExpress.XtraReports.Web.ClientControls.FaultException(string.Format("Could not find report '{0}'.", url));
}
public override Dictionary<string, string> GetUrls() {
// Returns a dictionary of the existing report URLs and display names.
// This method is called when running the Report Designer,
// before the Open Report and Save Report dialogs are shown and after a new report is saved to a storage.
return Directory.GetFiles(ReportDirectory, "*" + FileExtension)
.Select(Path.GetFileNameWithoutExtension)
.Union(ReportsFactory.Reports.Select(x => x.Key))
.ToDictionary<string, string>(x => x);
}
public override void SetData(XtraReport report, string url) {
// Stores the specified report to a Report Storage using the specified URL.
// This method is called only after the IsValidUrl and CanSetData methods are called.
var resolvedUrl = Path.GetFullPath(Path.Combine(ReportDirectory, url + FileExtension));
if (!resolvedUrl.StartsWith(ReportDirectory + Path.DirectorySeparatorChar)) {
throw new DevExpress.XtraReports.Web.ClientControls.FaultException("Invalid report name.");
}
report.SaveLayoutToXml(resolvedUrl);
}
public override string SetNewData(XtraReport report, string defaultUrl) {
// Stores the specified report using a new URL.
// The IsValidUrl and CanSetData methods are never called before this method.
// You can validate and correct the specified URL directly in the SetNewData method implementation
// and return the resulting URL used to save a report in your storage.
SetData(report, defaultUrl);
return defaultUrl;
}
}
}
C#using DevExpress.XtraReports.Native;
using Microsoft.AspNetCore.Mvc;
namespace CustomParameterEditorAspNetCoreExample.Controllers {
public class HomeController : Controller {
public IActionResult Index() {
return View();
}
public IActionResult Error() {
Models.ErrorModel model = new Models.ErrorModel();
return View(model);
}
public ActionResult Designer() {
var report = new DevExpress.XtraReports.UI.XtraReport();
report.Extensions[SerializationService.Guid] = CustomDataSerializer.Name;
return View(report);
}
public ActionResult Viewer() {
return View();
}
}
}
Razor<script type="text/html" id="custom-parameter-text-editor">
<div data-bind="dxTextBox: getOptions({ value: value, disabled: disabled }),
dxValidator: { validationRules: $data.validationRules || [] }"></div>
</script>
<script type="text/javascript">
function customizeParameterEditors(s, e) {
if (e.parameter.type ===
"@typeof(CustomParameterType).FullName") {
if(!e.parameter.multiValue && !e.parameter.hasLookUpValues) {
e.info.validationRules = e.info.validationRules || [];
e.info.validationRules.push(
{ type: 'email', message: 'Email parameter value has invalid format.' });
e.info.editor = { header: "custom-parameter-text-editor" };
}
}
}
</script>
@{
var viewerRender = Html.DevExpress().WebDocumentViewer("DocumentViewer")
.Height("1000px")
.ClientSideEvents(configure => configure.CustomizeParameterEditors("customizeParameterEditors"))
.Bind("CustomParameterReport");
@viewerRender.RenderHtml()
}
@section Scripts {
<link href="~/css/dx-reporting-skeleton-screen.css" rel="stylesheet" />
<link rel="stylesheet" href="~/css/viewer.part.bundle.css" />
<script src="~/js/reporting.thirdparty.bundle.js"></script>
<script src="~/js/viewer.part.bundle.js"></script>
@viewerRender.RenderScripts()
}
Razor@model DevExpress.XtraReports.UI.XtraReport
<script type="text/html" id="custom-parameter-editor">
<div data-bind="dxTextBox: { value: value }, dxValidator: { validationRules: [{ type: 'email', message: 'Email is not valid.' }]}"></div>
</script>
<script type="text/javascript">
function reportDesignerInit(sender, e) {
var editor = { header: "custom-parameter-editor" };
sender.AddParameterType({
displayValue: "Custom Type",
specifics: "custom",
value: "@typeof(CustomParameterType).FullName",
valueConverter: function (valueObj) { return valueObj; }
}, editor);
}
function onCustomizeMenuActions(s, e) {
var newReportAction = e.GetById(DevExpress.Reporting.Designer.Actions.ActionId.NewReport);
if (newReportAction) {
newReportAction.clickAction = function () {
s.OpenReport("TemplateReport");
}
}
}
</script>
@{
var designerRender = Html.DevExpress().ReportDesigner("reportDesigner")
.Height("1000px")
.ClientSideEvents(configure =>
{
configure.BeforeRender("reportDesignerInit");
configure.CustomizeMenuActions("onCustomizeMenuActions");
})
.Bind(Model);
@designerRender.RenderHtml()
}
@section Scripts {
<link href="~/css/dx-reporting-skeleton-screen.css" rel="stylesheet" />
<link rel="stylesheet" href="~/css/viewer.part.bundle.css" />
<link rel="stylesheet" href="~/css/designer.part.bundle.css" />
<link rel="stylesheet" href="~/css/ace/ace.bundle.css" />
<script src="~/js/reporting.thirdparty.bundle.js"></script>
<script src="~/js/viewer.part.bundle.js"></script>
<script src="~/js/designer.part.bundle.js"></script>
@designerRender.RenderScripts()
}