Using gacutil.exe and Reflection to generate the Exceptions Cheat Sheet

4.25 (8 votes)

The most recent addition to my Cheat Sheets features details of all the Exceptions that can be found in the most commonly used assemblies within ASP.NET development. Compiling this information was an interesting challenge. I could have simply copy-pasted from MSDN, but that would have been extremely tedious. Instead, I ended up with a blend of Linq to XML, Reflection, a dash of Regex and the Global Assembly Cache Tool - gacutil.exe. Here's the full story.

Assemblies in the .NET Framework are discrete deployments of compiled code. Although Assemblies can contain more than one module, each of the .NET Framework assemblies contain just one. Among other things, each assembly contains Types. For the purposes of this exercise, I am only interested in types which are of type Exception. This is the base class from which all other Exceptions derive.

Locally, .NET Assemblies are stored in the Global Assembly Cache (GAC). However, I am not interested in obscure exception classes in assemblies that Web Forms developers won't be using, such as AudioException (found in System.Speech.dll). So I need a way to list all assemblies in the GAC and then select just those I am interested in. Second, I need to identify all objects of type Exception in them, and third, I need to generate some documentation that describes exactly what those exceptions mean, or what causes them for the cheat sheet to have any real value.

Gacutil.exe is a command line tool that comes as part of the .NET Framework SDK. One of the things this tool can do is to list all assemblies registered in the GAC. It will actually list them using their fully qualified name, such as System.Data, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e08. This is very useful, as it is this that will be needed later for when Reflection is used on the assembly.

The following code generates a List of AssemblyName objects. The Gacutil tool is located and fired up using a Process instance.  The Argument switch (a letter "l") tells the tool to simply list items in the GAC. The list is streamed as output which is then read line by line. If a line meets the criteria (it has a version, is mscorlib or begins with System) it is added as an Assembly obect to the collection. The reason for the filtering criteria is that my GAC is full to the brim with all sort of assemblies belonging to control sets I have installed among other things. I don't want a never-ending list.

static List<AssemblyName> AssemblyList()
  var assemblyList = new List<AssemblyName>();
  const string utilPath = @"C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\gacutil.exe";
  using (var process = new Process
                        StartInfo =
                            FileName = utilPath,
                            RedirectStandardOutput = true,
                            UseShellExecute = false,
                            CreateNoWindow = true,
                            Arguments = "/l"
    using (var stream = process.StandardOutput)
      while (!stream.EndOfStream)
        var output = stream.ReadLine();
        if (output.Length > 0)
          AssemblyName an;
            an = new AssemblyName(output);
            if (an.Version != null && (an.Name.StartsWith("mscorlib") 
                                   || an.Name.StartsWith("System")))
          catch (Exception ex)
            an = null;
  return assemblyList;

In this particular case I simply bound an instance of AssemblyList to a CheckBoxList control in a web form:

Assemblies.DataSource = AssemblyList();

From here, I can select the assemblies I want to interrogate. When the page is posted back, I can iterate the CheckBoxList items to see which ones were selected, and add their value to a List<string> (which I call assembyList). This collection is then passed into another method:

List<ExceptionInfo> ListExceptions(IEnumerable<string> assemblyList)
  var exInfo = new List<ExceptionInfo>();
  var assemblies = assemblyList.Select(assemblyName => Assembly.Load(assemblyName));
  foreach (var assembly in assemblies)
    var version = assembly.GetName().Version;
    var module = assembly.GetModules().FirstOrDefault();
    XDocument doc = GetDocForModule(module, version);
    var types = module.GetTypes().Where(t => t.IsSubclassOf(typeof(Exception))).ToList();
    foreach (var t in types)
      var data = new ExceptionInfo
                     AssemblyName = module.Name.Replace(".dll", ""),
                     ExceptionName = t.Name,
                     ExceptionDescription = GetExceptionDescription(t, doc)
  return exInfo.OrderBy(e => e.AssemblyName).ThenBy(e => e.ExceptionName).ToList();


This method returns a List<ExceptionInfo>, which is a collection of a simple custom object:

class ExceptionInfo
  public string AssemblyName { get; set; }
  public string ExceptionName { get; set; }
  public string ExceptionDescription { get; set; }

Each assembly is loaded in turn, and its module identified. The GetDocForModule() method attempts to locate and load an XML document:

static XDocument GetDocForModule(Module module, Version version)
  var file = String.Empty;
  if (version.Major == 2)

    file = string.Format(@"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\en\{0}",
                              module.Name.Replace("dll", "xml"));
  if (version.Major == 3 && version.Minor == 0)
    file = string.Format(@"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\en\{0}",
                              module.Name.Replace("dll", "xml"));
  if (version.Major == 3 && version.Minor == 5)
    file = string.Format(@"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\{0}",
                              module.Name.Replace("dll", "xml"));
  if (file != String.Empty)
    using (var reader = new XmlTextReader(file))
      XDocument doc;
        doc = XDocument.Load(reader);
      catch (IOException ex)
        return null;
      return doc;
  return null;

Most Base Class Library assemblies in the GAC have a matching XML document which contains the xml comments from the source code stripped out and separated. This includes the <summary> information which is used to describe the class, method, event, type etc:


<member name="T:System.Data.Linq.ChangeConflictException">
      Thrown when an update fails because database values have been updated since the client last read them.

Locating these xml documents in the first place was a pain. The actual location depends on the .NET Framework version that the dll belongs to, or was last updated by. They are splattered all over the place. The default locations for versions 2.0, 3.0 and 3.5 can be seen in the file paths in the code above, but if you have MVC installed, or the Chart controls etc separately (pre-4.0) and you want to examine them, you need to located the directory in which the installers have placed the dlls. The xml files should also be there. The assembly version has been passed into the method so that the correct location can be referenced based on this.

Going back to the ListExceptions() method, you can see that each module is examined via Reflection for its types, and if those types are of type Exception, an ExceptionInfo object is created and added to the collection. The AssemblyName and ExceptionName are readily retrieved, but another method - GetExceptionDescription() is responsible for providing a value for the ExceptionDescription property:

static string GetExceptionDescription(Type t, XDocument doc)
  if (doc != null)
    var result =
        member => member.Attribute("name").Value == "T:" + t.FullName);
    if (result.Count() > 0)
      //Need to use CreateReader to get all of the XElement content 
      //otherwise it ignores child elements and attributes
      var xmlreader = result.Elements("summary").First().CreateReader();
      var summary = xmlreader.ReadInnerXml();
      const string pattern = "<see cref=\"[T|P|M|E|N]:";
      summary = Regex.Replace(summary.Trim(), pattern, "");
      return summary.Replace("\" />", "").Replace("<see cref=\"Overload:", "");
  return null;

The XDocument is loaded into memory once per module. As the Exceptions are iterated in the ListExceptions() method, corresponding <member> nodes are located within the XML and passed to the variable result. This is an IEnumerable<XElement>. Since there should only be one node that matches, it is passed to an XMLReader so that its entire contents are captured using the ReadInnerXml() method. Failure to do this will mean that <see cref="xxx> attributes are ignored. We want them as part of the string, because they invariable point to other types, methods, members etc as part of the summary. The actual markup is removed through a Regex.Replace and String.Replace combination to leave the bare type, method etc. The final collection of ExceptionInfo objects was iterated to a StringBuilder object in my case, to create the html table that displays the results.

When I started out with the simple idea of compiling a cheat sheet of Exceptions, I had no idea that it would lead me to this. Along the way, I discovered Gacutil.exe for the first time, delved a bit into the strucrture of .NET Framework assemblies, reflected on Reflection, understood a bit more about how VS and Intellisense work, stretched Lambdas a little more than I usually do, and played a bit more with Linq To XML. You might think that the end result is a little pointless, but hopefully you will have found the journey there as interesting as I did.

Date Posted:
Last Updated:
Posted by:
Total Views to date: 8783


- Steve

Quite interesting Mike, but you should get out more ;-)

- vinay

Very good and useful content.

- shailesh

Great article. If you can post source code of this article, it would be great.

Recent Comments

Praveen 12/02/2016 14:22
In response to Migrating Classic ASP To ASP.NET Razor Web Pages Part One- Razor Syntax And Visual Basic
Nicely written article, just what I wanted to get me started, I am going to start working on a this...

Whitney W. 11/02/2016 15:37
In response to Adding A Controller
I am really new to everything and just started programming. I really need help in my project since I...

Fredrik 11/02/2016 13:10
In response to Request.Form Is Empty When Posting To ASPX Page
It worked. Thank you!...

David Valdez 11/02/2016 03:08
In response to Reading Excel Files Without Saving To Disk In ASP.NET
Muchas, muchas, gracias. Thank you so much from Dominican Republic....

Zahid 10/02/2016 00:42
In response to How To Send Email In ASP.NET MVC
Hello Sir, Great post. Just a quick question, is it possible if we can ask a client to save in an...

Al Wilton 07/02/2016 03:11
In response to Windows Authentication With ASP.NET Web Pages
I've been using this advise for quite a while. Today I was setting up a .NET 4.6.1 site and it drove...

Anders 06/02/2016 15:38
In response to iTextSharp - Working with Fonts
Thanks a lot for this excellent series on iTextSharp....

J_R 06/02/2016 02:45
In response to Simple File Download Protection with ASP.NET
Mike, Thank you for taking the time to write this. It really helped me - though I could not out a...

Nemat 04/02/2016 16:24
In response to Solved - The Microsoft.ACE.OLEDB.12.0 provider is not registered on the local machine
Installing Microsoft Access Database Engine 2010 64 bit helped me. Thanks A lot!...

Bill Barbour 03/02/2016 18:34
In response to ASP.NET MVC 5 with EF 6 - Working With Files
Wonderful example. I have it all working. I would like to add the image to each row of the index you...