Example T883610
Visible to All Users

Reporting for ASP.NET Core - Inject Data from the Entity Framework Core DbContext into a Report Using the Object Data Source

An ASP.NET Core application with Entity Framework supplies data to a report as a DbContext object. This object operates in the scope of an HTTP request whose lifetime is different from report lifetime. A report is created in the HTTP request context and starts a background thread to get data and create a document. A report needs data after the initial HTTP request is completed. This means a report cannot use the default DbContext instance that the Entity Framework creates in the scope of the HTTP request.

This example demonstrates the approach that addresses the issues described above. The approach has the following requirements:

  • The application needs a repository that supplies data to a report.
  • Repository lifetime exceeds the lifetime of the HTTP request that creates the repository.
  • A repository requests the ScopedDbContextProvider instance to create DbContext on demand.
  • The HTTP request contains information used to filter data. For example, when you use the user ID to restrict access to reports, a repository instantiated within the HTTP request's scope stores the user ID, so it is available in the filter criteria.
  • The repository reads and saves values available in the HTTP request context. The values are stored for later use, so the repository saves the current user ID instead of the context-dependent IUserService object.
  • The repository reads and saves the current user ID in its constructor. The constructor is invoked in the context of the HTTP request and has access to context-dependent data.

NOTE: Before running this example, specify the DefaultConnection field in the project's appsettings.json file.

Implementation Details

Data Repository

The application uses the MyEnrollmentsReportRepository repository implemented in the following file: MyEnrollmentsReportRepository.

The MyEnrollmentsReportRepository repository is a regular POCO repository that supplies source data for the Object Data Source bound to a report. The repository gets the ScopedDbContextProvider as a dependency that creates a separate scope in the context of the current request. A scope created with the ScopedDbContextProvider can access data from the background thread outside the HTTP request.

Object Data Source

Use the Report Wizard to create the Object Data Source with the "schema only" option and bind the report to this data source.

Report Resolver

The Report Resolver performs the following tasks:

  • Instantiates a report.
  • Processes object data sources in a report with a dependency injector object.

The dependency injector requests a data repository as a service from a service provider for the HTTP request and injects the repository into the Object Data Source.

Object Data Source Injector

The ObjectDataSourceInjector is a dependency injector that assigns a data source to a report.

Document Preview in Report Designer

The CustomPreviewReportCustomizationService assigns a data source to a report before the Report Designer generates a document for preview. In addition to that, the CustomWebDocumentViewerOperationLogger service implementation is required for applications hosted on multiple web servers (Web Farm).

Does this example address your development requirements/objectives?

(you will be redirected to DevExpress.com to submit your response)

Example Code

xrefcoredemo/Services/MyEnrollmentsReportRepository.cs
C#
using System; using System.Collections.Generic; using System.Linq; using xrefcoredemo.Data; using xrefcoredemo.Models; namespace xrefcoredemo.Services { public class MyEnrollmentsReportRepository { readonly int studentId; readonly IScopedDbContextProvider<SchoolContext> scopedDbContextProvider; public MyEnrollmentsReportRepository() { // We use this parameterless constructor in the Data Source Wizard only, and not for the actual instantiation of the repository object. throw new NotSupportedException(); } public MyEnrollmentsReportRepository(IScopedDbContextProvider<SchoolContext> scopedDbContextProvider, IUserService userService) { this.scopedDbContextProvider = scopedDbContextProvider ?? throw new ArgumentNullException(nameof(scopedDbContextProvider)); // NOTE: the repository ctor is invoked in the context of http request. At this point of execution we have access to context-dependent data, like currentUserId. // The repository MUST read and store all the required context-dependent values for later use. E.g. notice that we do not store the IUserService (which is context/scope dependent), but read the value of current user and store it. studentId = userService.GetCurrentUserId(); } public StudentDetailsModel GetStudentDetails() { using(var dbContextScope = scopedDbContextProvider.GetDbContextScope()) { var dbContext = dbContextScope.DbContext; var student = dbContext.Students.Find(studentId); var model = new StudentDetailsModel { StudentID = student.ID, FirstMidName = student.FirstMidName, LastName = student.LastName, EnrollmentDate = student.EnrollmentDate }; return model; } } public IList<EnrollmentDetailsModel> GetEnrollments() { using(var dbContextScope = scopedDbContextProvider.GetDbContextScope()) { var dbContext = dbContextScope.DbContext; var student = dbContext.Students.Find(studentId); dbContext.Entry(student).Collection(x => x.Enrollments).Load(); var enrollmentModels = student.Enrollments.Select(x => new EnrollmentDetailsModel { EnrollmentID = x.EnrollmentID, Course = x.Course, Grade = x.Grade.HasValue ? x.Grade.Value.ToString() : "NO GRADE YET" }); return enrollmentModels.ToList(); } } } }
xrefcoredemo/Services/ScopedDbContextProvider.cs
C#
using System; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace xrefcoredemo.Services { public interface IScopedDbContextProvider<T> where T : DbContext { DbContextScope<T> GetDbContextScope(); } // NOTE: This provider isolates the rest of the code from the IServiceProvider. // That way, we can clearly understand that the consumers of IScopedDbContextProvider requre specific scopes... public class ScopedDbContextProvider<T> : IScopedDbContextProvider<T> where T : DbContext { readonly IServiceProvider provider; public ScopedDbContextProvider(IServiceProvider provider) { this.provider = provider ?? throw new ArgumentNullException(nameof(provider)); } public DbContextScope<T> GetDbContextScope() { var scope = provider.CreateScope(); return new DbContextScope<T>(scope); } } public class DbContextScope<T> : IDisposable where T : DbContext { readonly IServiceScope scope; public T DbContext { get; private set; } public DbContextScope(IServiceScope scope) { this.scope = scope ?? throw new ArgumentNullException(nameof(scope)); DbContext = scope.ServiceProvider.GetRequiredService<T>(); } public void Dispose() { scope.Dispose(); } } }
xrefcoredemo/Services/WebDocumentViewerReportResolver.cs
C#
using System; using System.IO; using DevExpress.XtraReports.UI; using DevExpress.XtraReports.Web.Extensions; using DevExpress.XtraReports.Web.WebDocumentViewer; namespace xrefcoredemo.Services { class WebDocumentViewerReportResolver : IWebDocumentViewerReportResolver { IObjectDataSourceInjector DataSourceInjector { get; } ReportStorageWebExtension ReportStorageWebExtension { get; } public WebDocumentViewerReportResolver(ReportStorageWebExtension reportStorageWebExtension, IObjectDataSourceInjector dataSourceInjector) { DataSourceInjector = dataSourceInjector ?? throw new ArgumentNullException(nameof(dataSourceInjector)); ReportStorageWebExtension = reportStorageWebExtension ?? throw new ArgumentNullException(nameof(reportStorageWebExtension)); } public XtraReport Resolve(string reportEntry) { using(MemoryStream ms = new MemoryStream(ReportStorageWebExtension.GetData(reportEntry))) { var report = XtraReport.FromStream(ms); DataSourceInjector.Process(report); return report; } } } }
xrefcoredemo/Services/ObjectDataSourceInjector.cs
C#
using System; using System.ComponentModel.Design; using DevExpress.DataAccess.ObjectBinding; using DevExpress.XtraPrinting.Native; using DevExpress.XtraReports; using DevExpress.XtraReports.Native.Data; using DevExpress.XtraReports.Services; using DevExpress.XtraReports.UI; using Microsoft.Extensions.DependencyInjection; namespace xrefcoredemo.Services { public interface IObjectDataSourceInjector { public void Process(XtraReport report); } class ObjectDataSourceInjector : IObjectDataSourceInjector { IServiceProvider ServiceProvider { get; } public ObjectDataSourceInjector(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } public void Process(XtraReport report) { foreach (var ods in DataSourceManager.GetDataSources<ObjectDataSource>(report, includeSubReports: true)) { if (ods.DataSource is Type dataSourceType) { ods.DataSource = ServiceProvider.GetRequiredService(dataSourceType); } } } } }
xrefcoredemo/Services/CustomPreviewReportCustomizationService.cs
C#
using DevExpress.XtraReports.UI; using DevExpress.XtraReports.Web.ReportDesigner.Services; namespace xrefcoredemo.Services { public class CustomPreviewReportCustomizationService : PreviewReportCustomizationService { readonly IObjectDataSourceInjector objectDataSourceInjector; public CustomPreviewReportCustomizationService(IObjectDataSourceInjector objectDataSourceInjector) { this.objectDataSourceInjector = objectDataSourceInjector; } public override void CustomizeReport(XtraReport report) { objectDataSourceInjector.Process(report); } } }
xrefcoredemo/Services/CustomWebDocumentViewerOperationLogger.cs
C#
using DevExpress.XtraReports.UI; using DevExpress.XtraReports.Web; using DevExpress.XtraReports.Web.WebDocumentViewer; namespace xrefcoredemo.Services { public class CustomWebDocumentViewerOperationLogger: WebDocumentViewerOperationLogger { private readonly IObjectDataSourceInjector objectDataSourceInjector; public CustomWebDocumentViewerOperationLogger(IObjectDataSourceInjector objectDataSourceInjector) { this.objectDataSourceInjector = objectDataSourceInjector; } public override void ReportLoadedFromLayout(string reportId, XtraReport report, out CachedReportSourceWeb cachedReportSourceWeb) { objectDataSourceInjector.Process(report); cachedReportSourceWeb = new CachedReportSourceWeb(report); } } }

Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.