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):
* static BackgroundReport.CheckReportExist(strReportCode, out strJSOpenWin)
* 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.
* 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;
Interlocked.Increment(ref m_nThreadStop);
while ((m_thread.IsAlive) && (m_nThreadStop<10))
{
Thread.Sleep(500);
Interlocked.Increment(ref m_nThreadStop);
}
if (m_thread.IsAlive) m_thread.Abort();
m_thread = null;
}
private void Run()
{
m_dStartTime = DateTime.Now;
m_dEndTime = DateTime.MinValue;
m_strHTMLContent = "";
m_eProcessError = null;
try
{
m_strHTMLContent = GenerateHTMLReportContent();
}
catch(ThreadAbortException) {throw;}
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;}
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
{
private const string FILENAME = "BackgroundRpt.aspx";
private const int REFRESHINTERVAL = 5000;
private static Hashtable m_mapReport = new Hashtable();
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;
}
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;
}
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);
}
}
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())
{
sbContent.Append(GenerateHTMLReportContent(rpt));
RemoveReport(rpt.GetReportInstanceCode());
bAutoRefresh = false;
}
else
{
if (strCmd=="cancel")
{
CancelReport(strCode);
RemoveReport(strCode);
sbContent.Append(GenerateHTMLReportCancelled(rpt));
bAutoRefresh = false;
}
else
{
sbContent.Append(GenerateHTMLReportProcessing(rpt));
bAutoRefresh = true;
}
}
}
else
{
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)
{
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");
}
virtual protected string GenerateHTMLReportContent(CBackgroundReport rpt)
{
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();
}
virtual protected string GenerateHTMLReportProcessing(CBackgroundReport rpt)
{
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>";
}
virtual protected string GenerateHTMLReportCancelled(CBackgroundReport rpt)
{
return "Report <strong>[" + rpt.GetReportName() + "]</strong> canceled.";
}
virtual protected string GenerateHTMLErrorCodeNotFound()
{
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)
{
InitializeComponent();
base.OnInit(e);
}
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
}