Best way to replace a Telerik Reporting ObjectDataSource programmatically in a SubReport?

5 posts, 0 answers
  1. Carl
    Carl avatar
    5 posts
    Member since:
    Aug 2010

    Posted 17 Jun 2013 Link to this post

    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:

    1. The recommended way to represent a controller method as a data source in the Telerik Reports designer is the ObjectDataSource. 
    2. The Telerik ObjectDataSource assumes that the controller method it calls is static.  
    3. The recommended way to override a data source programmatically is to bind to the SubReport's NeedDataSource event.
    4. A Report will only fire its NeedDataSource event if the Report's DataSource property is set to null.
    5. 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:

    1. 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:
    2. 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:
    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);
    }


    3.  In Report3:
    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?
     


  2. KS
    KS avatar
    165 posts
    Member since:
    Oct 2012

    Posted 21 Jun 2013 Link to this post

    Hi,

    Doubtfully the object data source wraps only static objects and the SubReport can contain anything else than a report. Otherwise, yes data can be set on NeedDataSource.

    Why dont you use ObjectDataSource wrapping the objects from your model, create the reports (nested collection can be set as data source to items with binding expressions), then before displaying the report iterate the items having DataSource and change it with the retrieved with the controller data. Of course for SubReports you have to dig through its Reportsource to get the underlying report.

    - KS
  3. Carl
    Carl avatar
    5 posts
    Member since:
    Aug 2010

    Posted 21 Jun 2013 Link to this post

    Hey, KS,

    May I ask what you mean by "Doubtfully the object data source wraps only static objects"?  Can you explain how to configure a Telerik ObjectDataSource so that it instantiates it's designated class with parameters such as a data context?

    You also asked the question "Why don't you...".  The answer is that I neither understand the benefits of your proposed alternative nor exactly how to do what you propose.

    --cak
  4. KS
    KS avatar
    165 posts
    Member since:
    Oct 2012

    Posted 23 Jun 2013 Link to this post

    Hi Carl,

    The documentation says POCO objects can be wrapped with ObjectDataSource and parameters can be used to pass values - http://www.telerik.com/help/reporting/object-data-source-using-expressions.html. In case of EF, the  ObjectDataSource has to be used with the ObjectContext. 

    However my point was that whatever data source is set while creating the report (means you can use ObjectDataSource to an object to create a report not necessarily the method retrieving the actual data), it can be changed before displaying, printing or exporting the report instead of in the report. As I said before inner items having DataSources can be found in the report and manipulated the same way.

    -KS


  5. Jim
    Jim avatar
    52 posts
    Member since:
    Apr 2009

    Posted 02 Apr 2015 in reply to Carl Link to this post

    Carl - thank you for posting how you approach this topic with concrete examples. I know you were hoping for a discussion of better approaches, but currently your code is the best example I can find of implementing objectDataSources down into the subReports.
Back to Top