We have some very complex reports that must be fed by a data controller class library. The Telerik ObjectDataSource the is used to represent controller methods as data sources in the designer assume that the data access methods are static. Unfortunately, our controllers do not have parameter-less constructors. It has become more challenging to cope with this limitation ever since the SubReport ReportSource could be something other than a Telerik Report. I have struggled to find the best way to replace Telerik SubReport ObjectDataSources programmically in your TR Q1 2013 SP1 release. I am hoping that someone who has a strong opinion will evaluate our approach because Google hasn't revealed a clear answer to us.
Facts and Assumptions About Telerik Reporting
Let's make sure that I have my facts straight:
All true? If not, please explain.
Hypothetical Report
Now suppose we have a nested stack of Telerik Reports where Report1 includes SubReport2 to instantiate Report2 that in turn includes SubReport3 to instantiate Report3. Report1 is instantiated by an ASP.NET page and bound to the Telerik ReportViewer. Here's what we would do to replace the ObjectDataSource at run-time:
Facts and Assumptions About Telerik Reporting
Let's make sure that I have my facts straight:
- The recommended way to represent a controller method as a data source in the Telerik Reports designer is the ObjectDataSource.
- The Telerik ObjectDataSource assumes that the controller method it calls is static.
- The recommended way to override a data source programmatically is to bind to the SubReport's NeedDataSource event.
- A Report will only fire its NeedDataSource event if the Report's DataSource property is set to null.
- A Telerik SubReport acts like a placeholder control. It could represent a Telerik Report or a blob of text. This flexibility makes it impossible to directly access the DataSource of a Report that is instantiated by a SubReport at compile time. Instead, you must programmatically instantiate the Report, change its properties and then replace the SubReport.ReportSource with the new Report instance.
All true? If not, please explain.
Hypothetical Report
Now suppose we have a nested stack of Telerik Reports where Report1 includes SubReport2 to instantiate Report2 that in turn includes SubReport3 to instantiate Report3. Report1 is instantiated by an ASP.NET page and bound to the Telerik ReportViewer. Here's what we would do to replace the ObjectDataSource at run-time:
- When we instantiate Report1, we use a custom constructor that receives a LINQ to SQL data context as an argument. Given a data context, we instantiate our controllers inside Report1.cs. In the Report1 ItemDataBinding event handler, we replace SubReport2.ReportSource like so:
-
publicReport1(LINQToSQLDataContext dataContext){//// Required for telerik Reporting designer support//InitializeComponent();// custom constructor actionsthis.DataContext = context;// for debuggingdetail.ItemDataBound +=newEventHandler(detail_ItemDataBound);this.detail.ItemDataBinding +=newEventHandler(detail_ItemDataBinding);// force NeedDataSource to firethis.DataSource =null;this.NeedDataSource +=newEventHandler(Report1_NeedDataSource);this.Error +=newErrorEventHandler(Report1_Error);// replace the subReport in order to pass it the MyContactMethodController to replace its static DataSourceReport2 emailReport =newReport2SubReport(this.MyContactMethodController);this.Report2SubReport.ReportSource =newInstanceReportSource() { ReportDocument = emailReport };this.Report2SubReport.ReportSource.Parameters.Add(newTelerik.Reporting.Parameter("parameter1","=Fields.ID"));this.Report2SubReport.ReportSource.Parameters.Add(newTelerik.Reporting.Parameter("effectiveDate","=Parameters.effectiveDate.Value"));}#region Event HandlersvoidReport1_NeedDataSource(objectsender, EventArgs e){Telerik.Reporting.Processing.Report report = (Telerik.Reporting.Processing.Report)sender;var id = report.Parameters["ID"].Value;_ID = Convert.ToInt32(ufdID);var effectiveDT = report.Parameters["effectiveDate"].Value;_effectiveDate = Convert.ToDateTime(effectiveDT);this.DataSource =this.MyDataController.GetHighLevelInfo(_ID, _effectiveDate);}// for debuggingvoidReport1_ItemDataBinding(objectsender, EventArgs e){int? id = (((Telerik.Reporting.Processing.ReportItemBase)(sender))).DataObject.RawDataasint?;}// for debuggingvoiddetail_ItemDataBinding(objectsender, EventArgs e){Telerik.Reporting.Processing.ReportSection reportSection = senderasTelerik.Reporting.Processing.ReportSection;HighLevelInfo data = reportSection.DataObject.RawDataasHighLevelInfo;_ID = data.ID;}// for debuggingvoiddetail_ItemDataBound(objectsender, EventArgs e){Telerik.Reporting.Processing.ReportSection reportSection = senderasTelerik.Reporting.Processing.ReportSection;var data = reportSection.DataObject.RawData;}voidReport1_Error(objectsender, ErrorEventArgs eventArgs){thrownewException(string.Format("{0} Telerik Report Exception: {1}",this.Name, eventArgs.Exception.ToString()), eventArgs.Exception);}#endregion Event Handlers
2. In Report2:
3. In Report3:
Is there a better way to implement this hypothetical report?
public Report2(DataController dc){ // // Required for telerik Reporting designer support // InitializeComponent(); // // TODO: Add any constructor code after InitializeComponent call // // save controller for the NeedDataSource event handler this.MyDataController = dc; // force NeedDataSource to fire this.DataSource = null; this.NeedDataSource += new EventHandler(Report2_NeedDataSource); this.Error += new ErrorEventHandler(Report2_Error); // replace the subReport in order to pass it the MyDataController Report3 report3 = new Report3(this.MyDataController); // force NeedDataSource to fire report3.DataSource = null; this.Report3SubReport.ReportSource = new InstanceReportSource() { ReportDocument = report3 }; this.Report3SubReport.ReportSource.Parameters.Add(new Telerik.Reporting.Parameter("parameter1", "=Parameters.parameter1.Value")); this.Report3SubReport.ReportSource.Parameters.Add(new Telerik.Reporting.Parameter("parameter2", "=Parameters.effectiveDate.Value")); this.Report3SubReport.ReportSource.Parameters.Add(new Telerik.Reporting.Parameter("parameter3", "=Fields.ID")); this.Report3SubReport.ReportSource.Parameters.Add(new Telerik.Reporting.Parameter("parameter4", "=Fields.Name"));} private DataController MyDataController { get; set; } private void Report2_NeedDataSource(object sender, EventArgs e){ int parameter1 = Convert.ToInt32(this.ReportParameters["parameter1"].Value); DateTime effectiveDate = Convert.ToDateTime(this.ReportParameters["effectiveDate"].Value); List<DetailInfo> detailList = this.MyDataController.GetDetailInfoList(parameter1, effectiveDate); this.DataSource = detailList;} void Report2_Error(object sender, ErrorEventArgs eventArgs){ throw new Exception(string.Format("{0} Telerik Report Exception: {1}", this.Name, eventArgs.Exception.ToString()), eventArgs.Exception);}public Report3(DataController dc){ // // Required for telerik Reporting designer support // InitializeComponent(); // // TODO: Add any constructor code after InitializeComponent call // // save controller for the NeedDataSource event handler this.MyDataController = dc; // force NeedDataSource to fire this.DataSource = null; this.NeedDataSource += new EventHandler(Report3SubReport_NeedDataSource); this.Error += new ErrorEventHandler(Report3SubReport_Error);}private DataController MyDataController { get; set; }private void Report3SubReport_NeedDataSource(object sender, EventArgs e){ int parameter1 = Convert.ToInt32(this.ReportParameters["applicantUserFormDetailID"].Value); DateTime effectiveDate = Convert.ToDateTime(this.ReportParameters["effectiveDate"].Value); int parameter3 = Convert.ToInt32(this.ReportParameters["skillCategoryID"].Value); List<MoreDetailInfo> moreDetailInfoList = this.MyDataController.GetMoreDetailInfo(parameter1, parameter2, effectiveDate); // bind datasource directly to my table. Binding to this.DataSource leaves the table blank. this.MyTable.DataSource = moreDetailInfoList;}void Report3SubReport_Error(object sender, ErrorEventArgs eventArgs){ throw new Exception(string.Format("{0} Telerik Report Exception: {1}", this.Name, eventArgs.Exception.ToString()), eventArgs.Exception);}Is there a better way to implement this hypothetical report?