Click here to Skip to main content
Click here to Skip to main content
Go to top

Web Background Report Template Class

, 2 May 2007
Rate this:
Please Sign up or sign in to vote.
A set of template classes to run background web reports easily.

Introduction

BackgroundReport is a code template used to run very long reporting functions in a web application (or any other kind of a long procedure like archiving).

Limitation

The current resource model is only viable for a limited number of users on a private web site. If you want to use this BackgroundReport on a public site, you will have to use the common ThreadPool to limit the resource.

User perspective

For the user, the process is pretty simple. He enters all the parameters on a web page and clicks Submit. A popup window opens instantly showing the report progression. When the report is ready, it simply appears on the popup.

Using the code

The BackgroundReport is easy to implement in every application (only two source files). The critical code is hidden in an abstract class (BackgroundRpt.cs) from which a custom report can be derived. So it's easy to reuse for many reports in the same application. It can also be easily customized to match the web site layout.

BackgroundRpt.aspx

This is the Report Popup template page. All the background reports will use the same ASPX file. This file can be customized to match your web site layout.

BackgroundRpt.cs

This is the abstract class from which every background report should derive.

YourReportLauncherPage.aspx

This page launches the background report. Basically, it should call two functions in the Page_Load (see main.aspx for a sample):

//To check if the report instance is actually running :
* static BackgroundReport.CheckReportExist(strReportCode, out strJSOpenWin)
//To create a new background report :
* static BackgroundReport.AddReport( 
         new CYourReport(strReportCode, param1, param2), out strOnLoadScript )

(strJSOpenWin returns a JavaScript command to open the report popup.)

YourReport.cs

Put the report generating function into an override of the CBackgroundRpt class. All specific parameters should be passed into the constructor.

//Simply override to return the HTML result of the report:
* override string CBackgroundRpt.GenerateHTMLReportContent()

Instruction

1. Copy these files into your web project:

  • BackgroundRpt.aspx
  • BackgroundRpt.aspx.cs
  • BackgroundRpt.cs

2. Create a new class which overrides CBackgroundReport

Write your own report regenerating function over GenerateHTMLReportContent().

Look at TestReport.cs for a sample.

3. Create a new ASPX page for the report launcher

Add the HTML code to input the report parameters.

These operations should be placed in the Page_Load function:

  • BackgroundReport.CheckReportExist(...) to check if the report is actually processing.
  • If not, validate the report parameters.
  • If the user parameters are OK, use BackgroundReport.AddReport(...) to create and launch the report.

Look at main.aspx for a sample.

4. Customize "BackgroundReport.aspx.cs"

Modify (or override) the popup report template to match your web site layout.

  • GenerateHTMLHeader()
  • GenerateHTMLFooter()
  • GenerateHTMLReportContent()
  • GenerateHTMLReportProcessing()
  • GenerateHTMLReportCancelled()
  • GenerateHTMLErrorCodeNotFound()

Code listing

BackgroundRpt.cs (abstract class from which your own report should be based)

abstract public class CBackgroundReport
{
  private Thread    m_thread      = null;
  private int       m_nThreadStop = 0;

  private bool      m_bContentReady;
  private DateTime  m_dStartTime, m_dEndTime;
  private string    m_strInstanceCode;
  private string    m_strHTMLContent;
  private Exception m_eProcessError;

  //*****************************************************
  public CBackgroundReport(string strInstanceCode)
  {
     m_bContentReady   = false;
     m_dStartTime      = DateTime.MinValue;
     m_dEndTime        = DateTime.MinValue;
     m_strInstanceCode = strInstanceCode;
     m_strHTMLContent  = null;
     m_eProcessError   = null;
  }

  //*****************************************************
  public void LauchBackgroundProcess() {StartThread();}
  public void AbortBackgroundProcess() {StopThread();}

  //*****************************************************
  private void StartThread()
  {
     if (m_thread != null) 
        throw new ApplicationException("Thread already created!");
     m_thread = new Thread( new ThreadStart(Run) );
     m_nThreadStop = 0;
     m_thread.Start();
  }

  private void StopThread()
  {
     if (m_thread == null) return; //thread already stopped

     Interlocked.Increment(ref m_nThreadStop);
     while ((m_thread.IsAlive) && (m_nThreadStop<10))
     { //wait 5 second for normal termination
        Thread.Sleep(500);
        Interlocked.Increment(ref m_nThreadStop);
     }

     //thread can't stop be himself, kill-it!
     if (m_thread.IsAlive) m_thread.Abort(); 
     m_thread = null;
  }

  //*****************************************************
  private void Run() //Thread delegate function
  {
     m_dStartTime      = DateTime.Now;
     m_dEndTime        = DateTime.MinValue;
     m_strHTMLContent  = "";
     m_eProcessError   = null;
     try
     {
        m_strHTMLContent = GenerateHTMLReportContent();
     }
     catch(ThreadAbortException) {throw;} //don't catch the abort thread
     catch(Exception e)
     {
        m_eProcessError = e;
     }
     finally
     {
        m_dEndTime      = DateTime.Now;
        m_bContentReady = true;
     }
  }

  //*****************************************************
  public string    GetReportInstanceCode()  {return m_strInstanceCode;}
  public bool      IsContentReady()         {return m_bContentReady;}
  public DateTime  GetStartTime()           {return m_dStartTime;}
  public DateTime  GetEndTime()             {return m_dEndTime;}
  public string    GetHTMLReportContent()   {return m_strHTMLContent;}
  public bool      IsReportError()          {return (m_eProcessError!=null);}
  public Exception GetReportError()         {return m_eProcessError;}
  
  //use inside GenerateHTMLReportContent() for checking Process Cancellation
  protected bool IsProcessStop() {return (m_nThreadStop!=0);}

  abstract  public    string GetReportName();
  abstract  protected string GenerateHTMLReportContent();
}

BackgroundRpt.aspx.cs (web page template for all report popups)

public class BackgroundReport : System.Web.UI.Page
{
  //relative path file name from Application root
  private const string FILENAME        = "BackgroundRpt.aspx"; 
  //web page referesh interval in milliseconds
  private const int    REFRESHINTERVAL = 5000;

  private static Hashtable m_mapReport = new Hashtable();

  //***********************************************************
  // return true if a new report have been added, or false if exist.
  // strJSOpenWin will contain the JavaScript code to open the report.
  static public bool AddReport(CBackgroundReport rpt, out string strJSOpenWin)
  {
     bool bReturn=false;
     strJSOpenWin = "";

     lock(m_mapReport)
     {
        if (!m_mapReport.ContainsKey(rpt.GetReportInstanceCode()))
        {
           m_mapReport.Add(rpt.GetReportInstanceCode(), rpt);
           rpt.LauchBackgroundProcess();
           strJSOpenWin  = MakeJavaOpenWin(rpt.GetReportInstanceCode());
           bReturn = true;
        }
     }

     return bReturn;
  }

  //***********************************************************
  // return true if the reporte related to the InstanceCode already exist.
  // strJSOpenWin will contain the JavaScript code to open the report.
  static public bool CheckReportExist(string strReportInstanceCode, 
                                      out string strJSOpenWin)
  {
     bool bReturn=false;
     strJSOpenWin = "";

     lock(m_mapReport)
     {
        if (m_mapReport.Contains(strReportInstanceCode))
        {
           strJSOpenWin = MakeJavaOpenWin(strReportInstanceCode);
           bReturn = true;
        }
     }

     return bReturn;
  }

  //***********************************************************
  // return true if the report have been cancelled
  static public bool CancelReport(string strCode)
  {
     CBackgroundReport rpt = GetReport(strCode);
     if ((rpt!=null)&&(!rpt.IsContentReady()))
     {
        rpt.AbortBackgroundProcess();
        return true;
     }
     return false;
  }

  //***********************************************************
  static private string MakeJavaOpenWin(string strCode)
  {
     string strPopupUrl  = GetRootFileName() + 
                           "?c=" + HttpUtility.UrlEncode(strCode);
     string strPopupName = "bckrpt" + HttpUtility.UrlEncode(strCode);
     return "window.open('" + strPopupUrl + "', '" + strPopupName + 
            "', 'menubar=no,toolbar=no,location=no,directories=no," + 
            "status=no,scrollbars=yes,resizable=yes,width=600,height=400');";
  }


  //***********************************************************
  static private CBackgroundReport GetReport(string strCode)
  {
     CBackgroundReport rpt=null;

     lock(m_mapReport)
     {
        if (m_mapReport.ContainsKey(strCode)) 
           rpt = (CBackgroundReport)m_mapReport[strCode];
     }

     return rpt;
  }

  static private void RemoveReport(string strCode)
  {
     lock(m_mapReport)
     {
        if (m_mapReport.Contains(strCode)) m_mapReport.Remove(strCode);
     }
  }

  //***********************************************************
  // use for debugging information
  static public string GetDebugList()
  {
     StringBuilder sb = new StringBuilder();
     lock(m_mapReport)
     {
        for (IDictionaryEnumerator e=m_mapReport.GetEnumerator(); e.MoveNext();)
        {
           CBackgroundReport rpt = (CBackgroundReport)e.Value;
           sb.Append("<strong>" + rpt.GetReportInstanceCode() + "</strong> " +
                     "[" + rpt.GetReportName() + "] " +
                     "start at : " + rpt.GetStartTime().ToString() + 
                     " - " + (rpt.IsContentReady() ? 
                     "COMPLETED" : "RUNNING") + ""));
        }
     }
     return sb.ToString();
  }

  //***********************************************************
  private void Page_Load(object sender, System.EventArgs e)
  {
     StringBuilder sbContent = new StringBuilder();
     bool bAutoRefresh       = false;
     CBackgroundReport rpt   = null; 
     string strCode          = (Request.QueryString["c"]   != null ? 
                                Request.QueryString["c"]   : "");
     string strCmd           = (Request.QueryString["cmd"] != null ? 
                                Request.QueryString["cmd"] : "");

     rpt = GetReport(strCode);
     if (rpt!=null)
     {
        if (rpt.IsContentReady())
        {//reporte is completed and ready to be displayed
           sbContent.Append(GenerateHTMLReportContent(rpt));
           //remove report from memory after use
           RemoveReport(rpt.GetReportInstanceCode()); 
           bAutoRefresh = false;
        }
        else
        {
           if (strCmd=="cancel")
           { //receive command to cancelle the report
              CancelReport(strCode);
              RemoveReport(strCode);
              sbContent.Append(GenerateHTMLReportCancelled(rpt));
              bAutoRefresh = false;
           }
           else
           { //report in processing, waiting page with cancel option
              sbContent.Append(GenerateHTMLReportProcessing(rpt));
              bAutoRefresh = true;
           }
        }            
     }
     else
     { //report code not found, error page
        sbContent.Append(GenerateHTMLErrorCodeNotFound());
        bAutoRefresh = false;
     }
     Response.Write( GenerateHTML(sbContent, bAutoRefresh) );
  }

  //***********************************************************
  private string GenerateHTML(StringBuilder sbContent, bool bAutoRefresh)
  {
     StringBuilder sb = new StringBuilder();
     GenerateHTMLHeader(sb, bAutoRefresh);
     sb.Append(sbContent);
     GenerateHTMLFooter(sb);
     return sb.ToString();
  }

  //***********************************************************
  virtual protected void GenerateHTMLHeader(StringBuilder sb, bool bAutoRefresh)
  { //TODO : customize as you like
     sb.Append("<html />\r\n");
     sb.Append("<script></script>\r\n");
     sb.Append("<center><a href=/"javascript:window.close();/">[ close ]</a></center>");
     sb.Append("\r\n");
     sb.Append("\r\n");
  }

  //***********************************************************
  // called when the report content is ready to be displayed
  virtual protected  string GenerateHTMLReportContent(CBackgroundReport rpt)
  { //TODO : customize as you like
     StringBuilder sb = new StringBuilder();
     sb.Append(rpt.GetReportName());
     if (rpt.IsReportError()) 
     {
        sb.Append("<strong>The report have generated an error :</strong>" + 
                  rpt.GetReportError().ToString().Replace("\r","<BR>") + "<BR>");
     }
     sb.Append(rpt.GetHTMLReportContent());
     return sb.ToString();
  }

  //***********************************************************
  // called when the report is actually processing
  virtual protected  string GenerateHTMLReportProcessing(CBackgroundReport rpt)
  { //TODO : customize as you like
     TimeSpan ts = DateTime.Now.Subtract(rpt.GetStartTime());
     return "Generating report <strong>[" + rpt.GetReportName() + "]</strong> " +
            " for <strong>[" + ts.ToString() + "]</strong> ... " + 
            "<a href=/"+ GetRootFileName() + "?cmd=cancel&c=" + 
            HttpUtility.UrlEncode(rpt.GetReportInstanceCode()) + ">[Cancel Report]</a>";
  }

  //***********************************************************
  // called after the report have benn called
  virtual protected  string GenerateHTMLReportCancelled(CBackgroundReport rpt)
  { //TODO : customize as you like
     return "Report <strong>[" + rpt.GetReportName() + "]</strong> canceled.";
  }

  //***********************************************************
  // called when the report code doesn't exist
  virtual protected string GenerateHTMLErrorCodeNotFound()
  { //TODO : customize as you like
     return "<strong>Report completed or undefined!</strong>";
  }

  //***********************************************************
  static protected string GetRootFileName()
  {
     string strRoot = System.Web.HttpContext.Current.Request.ApplicationPath;
     if( !strRoot.EndsWith("//") ) strRoot  += "//";
     return strRoot + FILENAME ;
  }

  #region Web Form Designer generated code
  override protected void OnInit(EventArgs e)
  {
     //
     // CODEGEN: This call is required by the ASP.NET Web Form Designer.
     //
     InitializeComponent();
     base.OnInit(e);
  }
    
  /// <summary />
  /// Required method for Designer support - do not modify
  /// the contents of this method with the code editor.
  /// </summary />
  private void InitializeComponent()
  {    
     this.Load += new System.EventHandler(this.Page_Load);
  }
  #endregion
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

ajrarn
Software Developer (Senior)
Canada Canada
I'm the operator with my pocket calculator.

Comments and Discussions

 
QuestionIt is how to control pop-up window? Pinmemberforgetuu20-Apr-09 1:53 
AnswerRe: It is how to control pop-up window? Pinmemberforgetuu20-Apr-09 1:56 
QuestionThreading Question: Is this safe? PinmemberDewey3-May-07 17:05 
AnswerRe: Threading Question: Is this safe? Pinmemberajrarn4-May-07 2:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 2 May 2007
Article Copyright 2007 by ajrarn
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid