Demonstrating Attributes and Reflection in .NET - Bug Tracking Attribute
(Page 7 of 8 )
Consider the following scenario. You recently released a patch to your latest Wizbang product, and one of your developers "fixed" a bug but introduced four more showstoppers that got past your QA department. Unfortunately the code modifications made to "fix" the bug span multiple assemblies and you don't know where to start.
What if you could run a utility program that examined the source code and provided a listing of all the affected code related to this bug? Given your programmers have some discipline in using attributes to annotate source code, determining code changes could be accomplished at the click of a button.
The Issue AttributeLet's define a custom .NET attribute that allows developers to identify areas of code affected by a bug fix. Our custom attribute will specify the issue number, name of the programmer, and a description of the change.
Developers should have the ability to place these attributes on classes, methods, properties, enumerations, etc. Here is the attribute's definition:
[AttributeUsage(AttributeTargets.All,
AllowMultiple = true, Inherited = true)]
Public class BugFixDetails : System.Attribute
{
private string _issueID;
private string _programmerName;
private string _description;
public string IssueID
{
get
{
return _issueID;
}
set
{
_issueID = value;
}
}
public string ProgrammerName
{
get
{
return _programmerName;
}
set
{
_programmerName = value;
}
}
public string Description
{
get
{
return _description;
}
set
{
_description = value;
}
}
public BugFixDetails(string ID, string programmer, string desc)
{
this.IssueID = ID;
this.ProgrammerName = programmer;
this.Description = desc;
}
}Note that the AttributeUsage attribute allows this attribute to be applied to any of the areas supported by .NET attributes. Here is some sample code containing the BugFixDetails attribute.
[BugFixDetails("1", "John Q. Programmer",
"Changed name of class from Test to Test1")]
public class Test1
{
[BugFixDetails("2", "Suzie B. BugFree",
"Changed _var1 from type object to string")]
private string _var1;
private string _var2;
[BugFixDetails("2", "Suzie B. BugFree",
"Initialized variables in constructor")]
public Test1()
{
_var1 = "init value 1";
_var2 = "init value 2";
}
[BugFixDetails("3", "John Q. Programmer",
"Altered code to account for framework exception being thrown")]
[BugFixDetails("4", "John Q. Programmer",
"Added in code to rethrow exception being thrown from framework")]
public void Method1(int a, int b)
{
. . .
}
. . .
}The Issue ViewerNow let's look at a sample application that loads a .NET assembly and searches for all BugFixDetails attributes matching a particular Issue ID. The screen shot below shows the results of a query for Issue ID #1. Note that the source code download contains a sample class library annotated with BugFixDetails attributes.

The sample application above opens up the assembly, examines it for BugFixDetails attributes and lists the following:
- Scope - The element that the code change was applied to (i.e. class, method, enumeration, property, etc).
- Name - The name of the element (method name, class name, etc).
- Programmer - The name of the programmer.
- Description - A description of the bug
When the user clicks the Find Details for Issue button, the following code is executed:
private void btnFindIssueDetails_Click(object sender,
System.EventArgs e)
{
try
{
// make sure they supplied an issue id
if(tbIssueID.Text.Trim() == "")
{
MessageBox.Show("You must specify an issue ID.");
return;
}
if(tbAssemblyFileName.Text.Trim() == "")
{
MessageBox.Show("You must specify the path and " +
"file name of the assembly to inspect.");
return;
}
// clear all the items in the list
listView1.Items.Clear();
// load the assembly so that we can
// interrogate it
Assembly assem =
System.Reflection.Assembly.
LoadFrom(tbAssemblyFileName.Text);
// iterate through each type in the assembly
// we loaded above
foreach(Type t in assem.GetTypes())
{
// add class level issue
// descriptions for this issue id
this.AddIssueDetailsForClass(t,
tbIssueID.Text);
// add method level issue
// descriptions for this issue ID
this.AddIssueDetailsForMethods(t,
tbIssueID.Text);
// add property level issue
// descriptions for this issue ID
this.AddIssueDetailsForProperties(t,
tbIssueID.Text);
// add constructor level issue
// descriptions for this issue ID
this.AddIssueDetailsForConstructors(t,
tbIssueID.Text);
}
}
// catch any exceptions
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}Once the code verifies that the Issue ID and assembly information is entered correctly, it loads the specified assembly and iterates through all of the types.
Next, the code adds details for bug fixes within classes, methods, properties, and constructors. Let's examine the method that locates custom BugFixDetails attributes at the method level.
private void AddIssueDetailsForMethods(Type t,
string issueID)
{
// iterate through all the methods of this object
foreach(MethodInfo mi in t.GetMethods())
{
// grab the custom attributes for each method
foreach(Attribute att in
mi.GetCustomAttributes(false))
{
// cast to our custom attribute
BugFixDetails dets =
att as BugFixDetails;
if(dets != null)
{
// do these issue IDs match?
if(dets.IssueID == issueID)
{
// add this issue to the list
this.AddIssueItemToList("Method",mi.Name,
dets.ProgrammerName,dets.Description);
}
}
}
}
}The AddIssueDetailsForMethods first iterates through all of the methods within the type, then through all the custom attributes for each method. After verifying the custom attribute is indeed a BugFixDetails attribute, the method looks at the IssueID of the attribute and, if the Issue IDs match, adds the item to a ListView.
The code that adds the item to the ListView is listed below:
private void AddIssueItemToList(string strScope,
string strName,
string strProgrammer,
string strDescription)
{
// create the listview item for this issue
ListViewItem lvi = new ListViewItem(strScope);
// add all the sub items
lvi.SubItems.Add(strName);
lvi.SubItems.Add(strProgrammer);
lvi.SubItems.Add(strDescription);
// lets add this to our list.
listView1.Items.Add(lvi);
}The above code block simply creates a new ListViewItem, populates it with the bug details, and adds the item to the ListView control.
Next: Conclusion >>
More ASP.NET Articles
More By Wrox Team