Web Background Report Template Class
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
}