Introduction
Effective debugging is a key part of effective software development. If you do not realize this statement or even do not appreciate this axiom in software development, this article would not benefit you much. If you are still reading, this article aims to provide a simple but powerful approach (not ready solution) for debugging contents of objects in ASP.NET. Actually you could use this approach for any other language or .NET area by simply using another type of control/debugging logic.
Background
Often in the code, you have an object having contents you would like to see at a specific run time or when the page is loaded. Often you do think that some data structures held specific values yet your program behaves in a way telling you that your assumptions were wrong, but you could not see it because you did not have a proper means for seeing what was in those data structures.
The Approach
The approach consists in using a simple control which renders the contents of a string
, which happen to be nicely formatted HTML string
, produced by dedicated debugger classes, which contain the logic of converting the contents of custom objects (DataSets, DataReaders, YourFacyCoolTypes) to a simple HTML user-friendly rendering string
.
The Code
The code consists of only one simple control you would simply copy paste to your App_Code folder (and probably rename the namespaces ;)). Here it is:
using System;
using System.Web;
namespace GenApp.Gui.Controls
{
public class DebugDumper : System.Web.UI.Control
{
#region Text
private string _Text;
public string Text
{
get
{
return System.Convert.ToString(
HttpContext.Current.Session["global." +
this.ID + "Text"]);
}
set
{
_Text = value;
HttpContext.Current.Session["global." +
this.ID + "Text"] = value;
}
}
#endregion Text
public DebugDumper(string msg, string debugString)
{
this.Text = " " + msg + " " + debugString;
}
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
writer.Write("<div><p>");
string echo = this.Text ?? " null debug string passed " ;
writer.Write( echo );
writer.Write("</p></div>");
base.Render(writer);
}
}
}
Example HtmlDebugger Class
Now the key point here is to realize that here I am using my HtmlDebugger
class which "knows" how to debug DataSet
objects, IDataReader
objects and a custom list of my application which contains a custom Msg
object just for demonstration purposes. (Delete the DebugMsgList
, while copy pasting since you will not know what is the Msg
class of my app). Most probably you do have your own debug classes which take specific objects as parameters and do return string
s showing their content. One good example would be the Object Dumper by Piero Viano. Most probably your debugging classes do debug more nicely the objects in your app, but for the purpose of this article, I would have to show my HtmlDebugger
class:
using System;
using System.Text.RegularExpressions;
using System.Data;
using System.Collections.Specialized;
using System.Text;
namespace GenApp.Utils.Log
{
public class HtmlDebugger
{
public static string DebugDataSet(string msg, DataSet ds)
{
StringBuilder sb = new StringBuilder();
sb.Append("<p> START " + msg + "</p>");
if (ds == null)
return msg + " null ds passed ";
if (ds.Tables == null || ds.Tables.Count == 0)
return msg + " no tables in ds ";
sb.Append("<p> DEBUG START --- " + msg + "</p>");
foreach (System.Data.DataTable dt in ds.Tables)
{
sb.Append("================= My TableName is " +
dt.TableName + " ========================= START");
sb.Append("<table>\n");
int colNumberInRow = 0;
foreach (System.Data.DataColumn dc in dt.Columns)
{
sb.Append(" <th> ");
sb.Append(" |" + colNumberInRow + "| ");
sb.Append(dc.ColumnName + " </th> ");
colNumberInRow++;
}
int rowNum = 0;
foreach (System.Data.DataRow dr in dt.Rows)
{
string strBackGround = String.Empty;
if (rowNum% 2 == 0)
strBackGround = " bgcolor=\"#D2D2D2\" ";
sb.Append("\n " + rowNum + "<tr " + strBackGround + " >");
int colNumber = 0;
foreach (System.Data.DataColumn dc in dt.Columns)
{
sb.Append("<td> |" + colNumber + "| ");
sb.Append(dr[dc].ToString() + " </td>");
colNumber++;
}
rowNum++;
sb.Append("</tr>");
}
sb.Append(" \n");
sb.Append("</table>");
}
sb.Append("<p> DEBUG END--- " + msg + "</p>");
return sb.ToString();
}
public static string DebugMsgList
(string msg, System.Collections.Generic.List<GenApp.Dh.Msg> listMsgs)
{
System.Text.StringBuilder echo = new System.Text.StringBuilder();
if (listMsgs == null)
return "null listMsgs passed for debugging ";
if (listMsgs.Count == 0)
return "listMsgs.Count == 0";
echo.Append("<table>");
for (int msgCounter = 0; msgCounter < listMsgs.Count; msgCounter++)
{
GenApp.Dh.Msg objMsg = listMsgs[msgCounter];
string strBackGround = String.Empty;
if (msgCounter % 2 == 0)
strBackGround = " bgcolor=\"#D2D2D2\" ";
echo.Append("<tr" + strBackGround + ">");
echo.Append("<td>msg.MsgKey</td> <td> " + objMsg.MsgKey + "</td>");
echo.Append("<td>msg.MsgId</td><td>" + objMsg.MsgId + "</td>");
echo.Append("</tr>");
}
echo.Append("</table>");
return echo.ToString();
}
public static string DebugIDataReader(string msg, IDataReader rdr)
{
StringBuilder sb = new StringBuilder();
if (rdr == null)
return " <p> IDataReader rds is null </p>";
sb.Append("DEBUG START ---" + msg);
sb.Append("<table>");
int counter = 0;
while (rdr.Read() )
{
string strBackGround = String.Empty;
if (counter % 2 == 0)
strBackGround = " bgcolor=\"#3EBDE8\" ";
sb.Append("<tr" + strBackGround + ">");
for (int i = 0; i < rdr.FieldCount; i++)
{
sb.Append("<td>");
sb.Append(rdr[i].ToString() + " ");
sb.Append("<td>");
}
sb.Append("</br>");
sb.Append("</tr>");
counter++;
}
sb.Append("<table>");
sb.Append("DEBUG END ---" + msg);
return sb.ToString();
}
public static string DumpListDictionary(string msg , ListDictionary list)
{
if (list == null)
return "<p> null list passed </p>";
if (list.Count == 0)
return "<p> list.Count = 0 </p> ";
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append("<p> START DUMP " + msg + " </p>");
sb.Append("<table>");
int counter = 0;
foreach (object key in list.Keys)
{
string strBackGround = String.Empty;
if (counter % 2 == 0)
strBackGround = " bgcolor=\"#D2D2D2\" ";
sb.Append("<tr" + strBackGround + "><td> key - </td><td> " + key.ToString());
sb.Append("</td><td>===</td><td>value - </td><td> " + list[key] + "</td></br></tr>");
counter++;
}
sb.Append("</table>");
sb.Append("<p> END DUMP " + msg + " </p>");
return sb.ToString();
}
}
}
Using the Code
You would use the code from the page's code behind as follows:
protected override void CreateChildControls()
{
base.CreateChildControls();
panTxtHolder.Controls.Add(
new GenApp.Gui.Controls.DebugDumper("some debug msg ",
GenApp.Utils.Log.HtmlDebugger.DebugDataSet(
"another debug msg", dsForUserDetails)));
}
Here panTextHolder
is the Panel
I have already populated in the page of this code behind.
dsForUserDetails
is my DataSet
I want to debug during the call.
Points of Interest
It would be nice to implement a control, which sits on the top of the site master and has the true, false radio button to see debugging info with the according event handler so that a user entitled to see debugging info would be able to switch between regular and debugging view. I wonder also if OnDataBind
would be a better place to implement the adding of the text to the control... If you have some thoughts, please comment below. It would be nice to implement a custom log4net appender based on this approach. It would be even nicer that this would be triggered by dynamic logging settings - e.g. when the app is in production and something is really wrong, the admin would impersonate the person having the problems and dynamically enable the debugging and provide the software developer maintaining the app the valuable info. If you have implemented something like this, I would be glad to hear what your approach was for solving the issue.
History
- 29th June, 2009: Initial post