This is a migrated thread and some comments may be shown as answers.

Dependency Injection into Report

9 Answers 535 Views
General Discussions
This is a migrated thread and some comments may be shown as answers.
Guillaume
Top achievements
Rank 1
Guillaume asked on 17 Mar 2017, 02:07 PM

I want to inject an interface into my Report instance, using my IOC container.

This interface will be used by the report to get data,

ex:

IQueryHandler<GetMyReportQuery, IEnumerable<MyReportData>> _queryHandler = <injected instance>.

IEnumerable<MyReportData> data = _queryHandler.Handle(new GetMyReportQuery {

param1 = <param1 enterred by user>,

param2 = <param2 enterred by user>,

});

 

I've seen somewhere suggested to use a custom report resolver, and instantiate an InstanceReportSource within, but this is no use as parameters are not available in the custom report resolver.

I've seen also suggested to use an ObjectDataSource, but it doesn't work as it needs a parameter less constructor to instantiate the business object, which prevent me from injecting my dependencies in the constructor.

So I'm a bit clueless how are we supposed inject our dependencies with Telerik reporting.

Please advise, thanks.

9 Answers, 1 is accepted

Sort by
0
Guillaume
Top achievements
Rank 1
answered on 17 Mar 2017, 03:05 PM
I am using js ReportViewer with the Web API Rest Service backend.
0
Guillaume
Top achievements
Rank 1
answered on 20 Mar 2017, 01:10 PM

Here is the solution I've used, hope it can help others.

In my report file:

  • I add a second ctor to be used by my IOCContainer
  • The default parameterless ctor is still used by the designer
  • Use the NeedDataSource event to execute my query and bind the result to the processingReport.DataSource. Here I have access to the parameters using the processingReport. 
  • OPTIONAL: In the designer, I bind the report to an ObjectDataSource, which is binding to the GetPreviewData() method. This is used to show dummy data when previewing.
public partial class F0201Report : Telerik.Reporting.Report, IReport<F0201Model>
    {
        private readonly IReportQueryHandler<F0201Query, F0201Model> _queryHandler;
 
        // Called by designer
        public F0201Report()
        {
            InitializeComponent();
            this.ReportParameters.ForEach(x => x.Visible = true);
        }
 
        public F0201Report(IReportQueryHandler<F0201Query, F0201Model> queryHandler)
        {
            InitializeComponent();
            this.ReportParameters.ForEach(x => x.Visible = false);
 
            _queryHandler = queryHandler;
            this.DataSource = null; // will be set in NeedDataSource event
        }
 
        private void F0201Report_NeedDataSource(object sender, System.EventArgs e)
        {
            var processingReport = (Telerik.Reporting.Processing.Report)sender;
            var model = _queryHandler.Handle(new F0201Query()
            {
                RequestNumber = processingReport.Parameters["requestNumber"].Value?.ToString()
            });
             
            processingReport.DataSource = model.Data;
        }
 
        /// <summary>
        /// Called by the ObjectDataSource used in Designer for preview
        /// </summary>
        public IEnumerable<F0201Dto> GetPreviewData(string requestNumber = null)
        {
            var list = new List<F0201Dto>()
            {
                new F0201Dto() {RequestNumber = "1"},
                new F0201Dto() {RequestNumber = "2"},
                new F0201Dto() {RequestNumber = "3"}
            };
 
            if (requestNumber != null)
            {
                list = list.Where(x => x.RequestNumber == requestNumber).ToList();
            }
 
            return list;
        }
 
         
    }

 

Next I created a Custom ReportResolver which use my container to get the Report instance, and return an InstanceReportSource.

public class ReportResolver : IReportResolver
    {
        private readonly IIocManager _iocManager;
 
        public ReportResolver()
        {
            _iocManager = IocManager.Instance;
        }
 
        public ReportSource Resolve(string reportId)
        {
            var reportType = Type.GetType(reportId);
 
            if (reportType == null) throw new UserFriendlyException($"Could not find a corresponding report for type '{reportId}'.");
 
            var report = (Report)_iocManager.IocContainer.Resolve(reportType);
 
            var reportSource = new InstanceReportSource { ReportDocument = report };
            return reportSource;            
        }

 

 

 

0
Stef
Telerik team
answered on 22 Mar 2017, 10:16 AM
Hi Guillaume,

Thank you for sharing your solution.
Please note that the resolver is called on registering a viewer, on getting information about the requested report's ReportParameters collection and on creating the document in the requested format (HTML, PDF, etc.) - REST Service Resolver Usage.

On each call you have to return the same report instance.


In general, reports are strongly typed and you need to provide exact data schema to be used by the report. I can suggest you to create a data service that returns the data and loads it in a specific business object that can be used via ObjectDataSource e.g. Connecting Telerik Reporting to OData feeds. You can make the type injection on creating the data service to know which type( business object) to be used as data source.

Other approach is to use only the data items' NeedDataSource events and a generic data access layer that handles internally which type to be used. Then you can use the default parameterless constructor of the report.

Regards,
Stef
Telerik by Progress
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Guillaume
Top achievements
Rank 1
answered on 22 Mar 2017, 11:29 AM

[quote]Please note that the resolver is called on registering a viewer, on getting information about the requested report's ReportParameters collection and on creating the document in the requested format (HTML, PDF, etc.) - REST Service Resolver Usage.

On each call you have to return the same report instance.[/quote]

Those are 3 different web requests, right ? How should I correlate them to make sure to use the same Report instance ?

What are the consequences of using a different report instance, like in my solution ?

[quote]In general, reports are strongly typed and you need to provide exact data schema to be used by the report. I can suggest you to create a data service that returns the data and loads it in a specific business object that can be used via ObjectDataSource e.g. Connecting Telerik Reporting to OData feeds. You can make the type injection on creating the data service to know which type( business object) to be used as data source.[/quote]

I'm not clear on this one. I am using strongly type binding. And no, I don't want to add OData into the mix.

[quote]Other approach is to use only the data items' NeedDataSource events and a generic data access layer that handles internally which type to be used. Then you can use the default parameterless constructor of the report.[/quote]

No I can't, the queryHandler have dependencies to be injected. The queryHandler need to be resolved by the container.

0
Stef
Telerik team
answered on 24 Mar 2017, 05:40 PM
Hi Guillaume,

Each request will send the string set as reportViewer.ReportSource.Report to the service's Resolver.Resolve method. You can add custom information like id or else letting you know what the expected result is. If you return a different report instance during these calls, there will be bubbled an error in the viewer with description what broke the report processing.

The idea of the example with the OData feed is that you can have the queryHandler in a data access layer that returns the final data object. Then you need an instance of the data access layer and the method that returns the ready data object. The reporting engine will create an instance of the class specified by the ObjectDataSource.Datasource (the data access layer) and it will execute the method specified by the ObjectDataSource.DataMember property (the method returning the final data object)).

Regards,
Stef
Telerik by Progress
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Guillaume
Top achievements
Rank 1
answered on 24 Mar 2017, 06:36 PM

[quote]

Each request will send the string set as reportViewer.ReportSource.Report to the service's Resolver.Resolve method. You can add custom information like id or else letting you know what the expected result is. If you return a different report instance during these calls, there will be bubbled an error in the viewer with description what broke the report processing.

[/quote]

Sorry, I am so confused by your answer. The reportviewer makes 3 differents web requests, that is not possible for the backend to return the same report instance each time. Multiple users could request the same report with different parameters. The requests could even go to a different server in a web farms scenario.

The ReportResolver I published is working fine, I get no such error you describe.

I'm still clueless what you recommend to change in my ReportResolver.

[quote]

The reporting engine will create an instance of the class specified by the ObjectDataSource.Datasource (the data access layer) and it will execute the method specified by the ObjectDataSource.DataMember property (the method returning the final data object)).

[/quote]

There seems to be a misunderstanding on dependency injection... my data access layer class has constructor dependencies that need to be satisfied, thus it cannot be set in ObjectDataSource.Datasource since that require parameter-less constructor.

We follow the composition root pattern (http://blog.ploeh.dk/2011/07/28/CompositionRoot/)

I don't want to use Service Locator anti-pattern.

0
Stef
Telerik team
answered on 29 Mar 2017, 12:24 PM
Hello Guillaume,

The idea is that reports are not designed to use Dependency Injection pattern, nor data source components are.

You can create custom constructor in the report and use it to instantiate the report per your needs. To use other than the default report's constructor, you need a custom resolver for the Reporting REST service. Each viewer requests reports via string message that is received by the service's resolver (in the Resolve method). Each viewer can send customized information in the string, which can include a generated by you viewer ID letting you recognize which viewer sends the request and what report instance should be returned by the resolver.

You can check the above information by adding a custom resolver in the Reporting REST Service and debugging the resolver's Resolve method and tracing the Network traffic (sent requests). You will notice the viewer sends requests including an id we generate per viewer, that lets know the service whose requests are handled. From that point, it is important the service to resolve the received string information to the same report for the current client.

Regards,
Stef
Telerik by Progress
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Guillaume
Top achievements
Rank 1
answered on 29 Mar 2017, 12:56 PM

And you point is ... ?

All this is done in the resolver I posted, right ?

When you replied to my original post, I was under the impression you were suggesting some correction... but still I have no idea what they are.

0
Stef
Telerik team
answered on 29 Mar 2017, 04:46 PM
Hello Guillaume,

The idea was to notify you how and when the Reporting REST Service calls its ReportResolver, and that it is required to return the same report instance as a response to all calls.

Also I wanted to let you know of other data-binding approaches, where the dependency injection pointing the exact type of data you will use can happen in another data access layer, where you can use the strongly typed data object filled with data via ObjectDataSource. Illustrated with the OData feed example.

Regards,
Stef
Telerik by Progress
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
Tags
General Discussions
Asked by
Guillaume
Top achievements
Rank 1
Answers by
Guillaume
Top achievements
Rank 1
Stef
Telerik team
Share this question
or