Description:
A separate HTTP handler module is used by our End-User Report Designer for ASP.NET to open/save a report to custom report storage (ReportStorageWebExtension). So, the HttpContext.Session and HttpContext.User properties are not available when your custom report storage methods are executed.
Besides, the ASP.NET HTML5 Document Viewer control generates a previewed report's documents in a separate thread. So, HttpContext is not available while the report document is being created: the HttpContext.Current method will return the NULL value in the report events' code.
This article answers the following questions:
- How to access the values that are stored in HttpContext/Session in a ReportStorageWebExtension class descendant's code when the report is opened/saved in the End-User Report Designer for ASP.NET control?
- How to access the values that are stored in HttpContext/Session in the report's code behind when the report is being previewed in the HTML5 Document Viewer control?
- How to access the values that are stored in HttpContext/Session in the data class code when the report is bound to an ObjectDataSource and being previewed in the HTML5 Document Viewer control?
See the T341232: How to access the values stored in HttpContext/Session while working with the ASP.NET HTML5 Document Viewer and End-User Report Designer controls code example that demonstrates the approaches described in this article.
Answer:
1. How to access the values that are stored in HttpContext/Session in a ReportStorageWebExtension class descendant's code when the report is opened/saved in the End-User Report Designer for ASP.NET control?
Starting with version 15.2.5, the HTTP handler module that works with the designer's report storage extension provides a built-in capability to accept the Session state when a request is handled.
To enable the Session state support, set the ReportDesignerBootstrapper.SessionState property to the "SessionStateBehavior.Required" value when your application is started. To do that, add the following code to your application global class' (Global.asax) code behind:
C#public class Global : System.Web.HttpApplication {
protected void Application_Start(object sender, EventArgs e) {
DevExpress.XtraReports.Web.ReportDesigner.Native.ReportDesignerBootstrapper.SessionState = SessionStateBehavior.Required;
DevExpress.XtraReports.Web.QueryBuilder.Native.QueryBuilderBootstrapper.SessionState = System.Web.SessionState.SessionStateBehavior.Required;
...
}
...
}
Then register the designer's HTTP handler (DXXRD.axd) in the application's configuration file (Web.config) (add this handler registration line to the httpHandlers/handlers collections after the ASPxHttpHandlerModule (DX.ashx) registration) :
XML<system.web>
...
<httpHandlers>
...
<add type="DevExpress.Web.ASPxHttpHandlerModule, DevExpress.Web.v15.2, Version=15.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" verb="GET,POST" path="DXXRD.axd" validate="false" />
</httpHandlers>
</system.web>
<system.webServer>
...
<handlers>
...
<add type="DevExpress.Web.ASPxHttpHandlerModule, DevExpress.Web.v15.2, Version=15.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" verb="GET,POST" path="DXXRD.axd" name="WebReportDesignerHandler" preCondition="integratedMode" />
</handlers>
</system.webServer>
After doing that, the HttpContext.Session and HttpContext.User properties will be available in your custom report storage extension's code.
2. How to access the values that are stored in HttpContext/Session in the report's code behind when the report is being previewed in the HTML5 Document Viewer control?
As the HTML5 Document Viewer control generates a previewed report's documents in a separate thread, it is not possible to access the current request data when the report document is being generated. That's because the HttpContext object is being cleared after the request is handled by the web server, so it is not possible to access this cleared object's data from the background thread that is used for the report generation.
The only way to workaround this behavior is to pass the values from your HttpContext/Session to your report before report creation is started. Use the report parameters for this purpose. Set these parameter's Visible property to false to hide them from the viewer's parameters panel. Then, specify the parameter values by using the values from the HttpContext/Session before opening your report in a viewer. For example, use the following code in your ASP.NET WebForms page's event handler:
C#XtraReport report = new MyReportClassName();
report.Parameters["MyReportParameterName"].Value = Session["MySessionFieldName"];
...
ASPxWebDocumentViewer1.OpenReport(report);
Then, use this parameter's value in your report event handlers code or bind your report fields to this parameter value.
3. How to access the values that are stored in HttpContext/Session in the data class code when the report is bound to an ObjectDataSource and being previewed in the HTML5 Document Viewer control?
As was described in the previous answer, it is not possible to access the current request data when the report document is being generated. So, the only way to pass the values from your HttpContext/Session to a data class used by the ObjectDataSource control is to use the report parameters.
Use the approach described in the previous answer to add the parameters to your report and pass the values from the HttpContext/Session to these parameters. Then, add parameters to your data class constructor or to a method that is used to select data. After that, add corresponding parameters to the ObjectDataSource.Constructor.Parameters (in case parameters are added to your data class constructor) or ObjectDataSource.Parameters (in case parameters are added to a method that is used to select data) collection.
After adding the data source's parameters, it is necessary to map them to your report parameters. To do that, run the Parameters collection editor from the property grid, enable the check box in the Expression column and select your report's parameter in the Value column's drop-down. See the How to: Bind a Report to an Object Data Source > Retrieve the Actual Data help topic for more details.
Hello. How can i use this approach to access httpcontext from ReportStorageWebExtension using Asp .net core specifically? Looks like
DevExpress.AspNetCore assembly does not contain ReportDesignerBootstrapper class and others.
Hi Manish,
Thank for your question. Indeed, this class in not available .NET Core applications. There, you're supposed to use the dependency injection system to access services that allow you to get data related to the current user. Unfortunately, currently it is not possible to inject ASP.NET Core services to ReportStorageWebExtension, but we're hoping to eliminate this limitation when we get out of CTP. At the moment, as an immediate workaround, consider passing an instance of the IHttpContextAccessor class (HttpContextAccessor.HttpContext.Session) down to your custom storage implementation as follows:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { ... app.UseDevExpressControls(); DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension.RegisterExtensionGlobal(new MyCustomStorageWebExtension(app.ApplicationServices.GetService<IHttpContextAccessor>())); }
I hope it helps.
Regards,
Yaroslav
Hi Yaroslav,
I too am facing this issue. Without DI support we cannot access our DB Context, Session info, business logic Services… It's a real pain to try to work around all of these issues. Is there any update / timescales on when the .NET Core implementation of ReportStorageWebExtension will support all of the above?
Thank you,
Hi,
For now, the situation is still the same. So, I cannot give you any estimates or timestamps on when the dependency injection will be fully supported by our web report storage service.
Hello,
I think it is also possible to create an interface for your ReportStorageWebExtension, register it as a service, and then register the service as a global extension
public void ConfigureServices(IServiceCollection services) { services .AddSingleton<IXtraReportStorage, XtraReportStorage>() .ConfigureReportingServices(); ReportStorageWebExtension.RegisterExtensionGlobal( (ReportStorageWebExtension)services.BuildServiceProvider().GetService(typeof(IXtraReportStorage))); }
public class XtraReportStorage : ReportStorageWebExtension, IXtraReportStorage { private readonly IHttpContextAccessor _httpContextAccessor; public XtraReportStorage(IHttpContextAccessor accessor) { _httpContextAccessor = accessor; } }
Then ASP.Net Core will inject the IHttpContextAccessor into your ReportStorageWebExtension.
Hope this helps
Hi Benjamin,
Thank you for sharing your solution with us. Yes, this solution is absolutely correct for ASP.NET Core projects that use DevExpress components of version 19.1.5+. Earlier versions of our ASP.NET Core reporting components do not support the dependency injection for the report storage.
I came across this topic while I was attempting to achieve a similar functional requirement - our Report Designer managed additional state (in our case, a "tenant" who owned the report design) which needed to be passed to the server during save.
I discussed a similar concern here, which was geared more at the
GetUrls
method of theReportStorageWebExtension
, where I came up with a different solution (injecting custom header information into every DevExpress HTTP request)https://supportcenter.devexpress.com/ticket/details/t348757
This topic was originally raised about 5 years ago here:
https://supportcenter.devexpress.com/ticket/details/t440064
And it has been superseded by this current topic you are reading now - which is not really the same - as it's talking about using server session state to achieve these needs.
Unfortunately, introducing statefulness to an application server is a heavy-handed solution, and massive architectural change, which comes with some undesirable side-affects, impacting the entire application.
I have devised a better solution, and I'm surprised the DX support guys haven's suggested this.
(I tried and failed to get this solution to work the first time around, hence my alternate solutions, but I have just recently gotten it to work, so here it is).
XtraReport
has anExtensions
property, which is a key value dictionary, allowing users to store and retrieve any values they like.Because the
ReportStorageWebExtension.SetData
andReportStorageWebExtension.SetNewData
methods both receive theXtraReport
, you technically have the ability to pass numerous arguments, serialized as strings.Here is an example of how to achieve this, in high-level Angular TS and C# code.
On the client, register a handler for 'ReportSaving', and use it to inject your custom arguments - technically because they are persisted in the report state for the duration of the designer session - you could inject them at any point, it doesn't have to be 'last minute' like I've done for demonstration purposes here.
<dx-report-designer #designer height="1024px"> <dxrd-callbacks (ReportSaving)="OnReportSaving($event.args.Report, $event.args.Url, $event.component)"> </dxrd-callbacks> </dx-report-designer>
/// Helper method to add custom parameters to the report's extensions dictionary setExtensionValue(reportViewModel: ReportViewModel, key: string, value: string) { var existing = ko.utils.arrayFirst(reportViewModel.extensions(), (x) => x.key() == key); if (existing) reportViewModel.extensions.remove(existing); var newDataSerializer = new ExtensionModel({}); newDataSerializer.key = ko.observable(key); newDataSerializer.value = ko.observable(value); reportViewModel.extensions.push(newDataSerializer); } OnReportSaving(reportViewModel: ReportViewModel, url: string, component: DxReportDesignerComponent) { this.setExtensionValue(reportViewModel, 'MyCustomArgument', 'AnythingYouLike'); }
public class CustomReportStorageWebExtension : ReportStorageWebExtension { /// <summary> /// Helper to extract a mandatory value from an XtraReport Extensions dictionary /// </summary> private T GetExtensionValue<T>(XtraReport report, string key) { string stringValue = null; if (report.Extensions.TryGetValue(key, out stringValue)) { var converter = TypeDescriptor.GetConverter(typeof(T)); return (T)converter.ConvertFromString(stringValue); } return default(T); } /// <summary> /// Helper to extract an optional value from an XtraReport Extensions dictionary /// </summary> private Nullable<T> GetNullableExtensionValue<T>(XtraReport report, string key) where T : struct { if (report.Extensions.ContainsKey(key)) return new Nullable<T>(GetExtensionValue<T>(report, key)); return default(Nullable<T>); } public override void SetData(XtraReport report, string url) { // grab your custom argument var myCustomArgument = GetNullableExtensionValue<string>(report, "MyCustomArgument"); // ...do whatever custom logic you like with it... // clear the extensions to ensure custom argument is not persisted (unless you want it to be?) report.Extensions.Clear(); SaveToSql(report); } }
Remember that your custom extensions are embedded in the report, so they will be persisted on the server unless you remove them from the
XtraReport.Extensions
dictionary before sending them off to disk/sql etc.Happy coding!
Hello,
Thank you for sharing your solution with us. However, I would like to warn you that the Extensions property is intended to serialize custom objects (e.g. services, data types, etc.) along with the report. So, the report tries to load object by using the values that are stored in this property. Thus, using this collection for storing key-value pairs may lead to unexpected behavior or even exceptions, as this collection was not intended to be used in this way.
At the same time, you can use other properties for embedding this information into a report. For example you can use the report's Tag property or even its parameters for this purpose. But I do not recommend you to store any secure data (e.g. the tenant ID) in the report. This is because the report is passed to client and back to the sever, so the attacker may get access to this information and change it.
Finally, I would like to mention that in ASP.NET Core applications all reporting services support dependency injection. So, you can inject desired services to the reporting services what will help you to get some user-related data (e.g. the tenant ID).