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:
-
public
Report1(LINQToSQLDataContext dataContext)
{
//
// Required for telerik Reporting designer support
//
InitializeComponent();
// custom constructor actions
this
.DataContext = context;
// for debugging
detail.ItemDataBound +=
new
EventHandler(detail_ItemDataBound);
this
.detail.ItemDataBinding +=
new
EventHandler(detail_ItemDataBinding);
// force NeedDataSource to fire
this
.DataSource =
null
;
this
.NeedDataSource +=
new
EventHandler(Report1_NeedDataSource);
this
.Error +=
new
ErrorEventHandler(Report1_Error);
// replace the subReport in order to pass it the MyContactMethodController to replace its static DataSource
Report2 emailReport =
new
Report2SubReport(
this
.MyContactMethodController);
this
.Report2SubReport.ReportSource =
new
InstanceReportSource() { ReportDocument = emailReport };
this
.Report2SubReport.ReportSource.Parameters.Add(
new
Telerik.Reporting.Parameter(
"parameter1"
,
"=Fields.ID"
));
this
.Report2SubReport.ReportSource.Parameters.Add(
new
Telerik.Reporting.Parameter(
"effectiveDate"
,
"=Parameters.effectiveDate.Value"
));
}
#region Event Handlers
void
Report1_NeedDataSource(
object
sender, 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 debugging
void
Report1_ItemDataBinding(
object
sender, EventArgs e)
{
int
? id = (((Telerik.Reporting.Processing.ReportItemBase)(sender))).DataObject.RawData
as
int
?;
}
// for debugging
void
detail_ItemDataBinding(
object
sender, EventArgs e)
{
Telerik.Reporting.Processing.ReportSection reportSection = sender
as
Telerik.Reporting.Processing.ReportSection;
HighLevelInfo data = reportSection.DataObject.RawData
as
HighLevelInfo;
_ID = data.ID;
}
// for debugging
void
detail_ItemDataBound(
object
sender, EventArgs e)
{
Telerik.Reporting.Processing.ReportSection reportSection = sender
as
Telerik.Reporting.Processing.ReportSection;
var data = reportSection.DataObject.RawData;
}
void
Report1_Error(
object
sender, ErrorEventArgs eventArgs)
{
throw
new
Exception(
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?