Hosting .NET Reflector in your own Application

You can automate .NET Reflector processes, and run .NET Reflector from within IDEs or other applications. You can even use it as a web control within a browser! . Here Nick shows how to develop a web control that accepts metadata for a method, uses .NET Reflector to disassemble the method and displays the decompiled output in a web page.

We all love .NET Reflector.  Wouldn’t it be nice to harness some of this power in our own applications?

One of the greatest things about .NET Reflector is the ability to write your own addins.  .NET Reflector provides all the hooks needed for you to create your own code extending .NET Reflector in some amazing ways, and many talented folks have contributed some wonderful addins.  Turns out, we can use these same hooks to drive .NET Reflector from our own applications!

This sounds very exciting!  But what can we actually do with it?  Here are a few thoughts:

  • We have a safe place where we have complete control to explore building our own add-in
  • We can build a command line utility to automate specific tasks like running metrics as part of a daily build
  • We can provide a subset of .NET Reflector‘s functionality from within our own apps limiting what is visible
  • We can build a web control that will allow us to view the disassembled source code from a given assembly.

This article will explore this last idea.  Here we will step through creating such a web control. 

What we want to do

We will build a web control that will allow us to harness the power of .NET Reflector to disassemble code and display the result on the web.  We will format the code with style sheets to properly indent the code and provide syntax highlighting.

This can be useful for including sample  code in blogs or articles.  We can allow the reader to select their favorite language without having to create a version in each language.  We can also keep the most current version of the sample code without having to worry about updating the blog entry.  Simply ensure that the latest version of your code is on the site and our web control will handle the rest.

Our control will have the following responsibilities:

  • Load the assembly that we are interested in
  • Create the environment for hosting .NET Reflector
  • Ensure that the generated markup is self-contained and will be well behaved when dropped on a page.
  • Ensure that the generated markup will render properly even when the same control is used multiple times on a single page
  • Changing properties for one instance will not affect the property’s value for other instances.

Structuring Our Code

We will build a web control to handle the heavy lifting of interacting with .NET Reflector.  Our web control will include properties for specifying the path the Assembly, the language to display the code in, the Method to display, and the type that contains the method.  We will need to create several objects to host .NET Reflector in our code.  We will create objects to implement key interfaces that .NET Reflector needs.  These include HtmlFormatter, AssemblyResolver, and LanguageWriterConfiguration.

HtmlFormatter

The HtmlFormatter class will implement the IFormatter interface and will work with the ILanguageWriter objects to format code for display.  Every language supported by .NET Reflector will define an ILanguageWriter, which will be responsible for disassembling code into its respective language.  The language writer will use the HtmlFormatter to handle the actual formatting of the code.  The IFormatter object will be responsible for ensuring that the formatted code is properly indented and handle syntax highlighting.  The IFormatter object does not need to get bogged down in language level details.  We will only have to worry about formatting the code properly for HTML.

We ensure proper indenting by nesting ul elements.  We handle the syntax highlighting with style sheets.  We define a class for every syntax element that we are interested in highlighting.  The various methods required for the IFormatter interface will give us all the information that we need

  • WriteComment will give us the text for the comments.  We will not have very many of these in disassembled code.   The value given will already include the language specific comment syntax.
  • WriteKeyword will give us the text for the keyword.  We do not have to worry about what the keywords are for every language.  This method will be called passing in the appropriate keyword specific to each language.
  • WriteLine will tell us that we have reached the end of a line.
  • WriteLiteral will give regular text to be displayed without any formatting.
  • WriteIndent will be called to indicate that we have to add a new level of indentation
  • WriteOutdent will be called to indicate that we have ended a level of indentation.

We will pass an HtmlTextWriter to the constructor and then use it as needed in the various methods.

The WriteIndent and WriteOutdent methods will combine to handle proper indention:

We will simply add a nested UL to add a level of indention.  The HtmlTextWriter keeps track of every tag that is rendered.  When we are ready to close a level of indentation, the RenderEndTag will close the appropriate ul tag.   We could also use nested div with an appropriate margin-left property.

Decorating the code with the appropriate style information is relatively straightforward.   We explicitly set the class property of a span tag.

Each method required by the interface will make a call to this WriteFormattedText method passing in the appropriate format.

AssemblyResolver

CustomAssemblyResolver will implement the IAssemblyResolver interface. This object will be responsible for loading any assemblies that .NET Reflector needs.  .NET Reflector will normally take care of this by allowing the user to search for the Assembly in question.  Since .NET Reflector will not have control over the UI, we need to provide this functionality, but we will not have a UI that will allow us to prompt the user for any missing assembly references.  Fortunately, we will have complete control over the various assemblies that we will be interested in, so we will know where every dependent assembly is.

The IAssemblyResolver interface requires only a single method.  The Resolve method will accept an IAssemblyReference parameter and a local path parameter and will return the IAssembly that resolves to the IAssemblyReference.

LanguageWriterConfiguration

LanguageWriterConfiguration will implement the ILanguageWriterConfiguration interface.  This interface requires a Visibility property that will be of type IVisibilityConfiguration and an index property that will provide boolean value for different configuration settings.

Unfortunately, this is not the best design for an interface.  There is no guidance as to what indexes are possible.  I wrote out the index values that were passed in and formed a list of the values that need to be handled.   These include:

  • ShowCustomAttributes
  • ShowDocumentation
  • ShowMethodDeclarationBody
  • ShowNamespaceImports
  • ShowTypeDeclarationBody

Alternately, you may decide to always return true and not worry about the details.

Mechanics of Hosting .NET Reflector

Now that we have our supporting cast of characters, we are ready to play.  All of the fun happens in the Disassemble method.

We start by initializing the services provided by .NET Reflector

We will use the assemblyManager to load the assembly that we are interested in and to handle any assemblies that need to be resolved.  The language manager will give us access to the languages that are supported.  The translatorManager will come into play later when we start disassembling code.

Next, we will hook up the assembly resolver and the assembly manager, and hook up the formatter with the html text writer.

We need to associate the IVisibilityConfiguration with the LanguageWriterConfiguration. 

Depending on your implementation needs, you may want to explicitly set the values for these properties in the implementation.

Finally, we are ready to get the language writer.  For our web control, we define a languageCode property that will index into the Languages property of the language manager to tell us which language we are using.  The language manager will handle associating our formatter and configuration settings to the writer.

Now that we have all the pieces in place, we are ready to walk through the code that we are disassembling.   We will follow the code hierarchy:

  • Modules
    • Type declarations
      • Methods

Since we only interested in methods, we will stop here.  If you were interested, you could also investigate the additional properties of the TypeDeclaration interface

Here we walk through the hierarchy searching for the type and method that we are interested in.   Since there are no “get” methods in the ItypeDeclaration interface as there are with Reflection, we will have to loop through the Methods property.   Similarly, we have to loop through the Types property of the Imodule interface.

Once we have found the method that we are interested in, we are ready to disassemble.  We check to see if the selected language is not IL.  The method declaration from the “for each” loop is suitable to be passed to the language writer for IL.  For all other languages, we will need to create a new method declaration based on the original method declaration.  This will create an object populated with all the details that the language writer will need to produce code.

The language writer will make the appropriate calls to our HtmlFormatter to write out the code properly formatted.

The final piece for our web control is the Render method.  We will surround the disassembled code with div tag styled as code and let the Disassemble method do the rest.

Pulling It All Together

There are few steps needed to piece all of this together in a web application.    We start by defining the tag for our new control.   Here we define that any web control in the ReflectorWrapper namespace will have the tag prefix .NET Reflector.

Once we have added this entry to the web.config file, we are ready to add the control to our web pages.  The LanguageCode specifies which language to use.    LanguageCode = “1” means to use C# as the language.

Specifying the AssemblyPath can be a bit more complicated.   The easiest way to handle this is in the code behind.  Here we use the Server.MapPath function to build the location to the assembly that we are interested in.   We are also binding the list of supported languages to a drop down list so the user can switch languages around.

When the user switches the language, all we need to do is change the language code.

The Screenshots

676-Csharp.jpg


676-vb.jpg

Taking this Further

This is a simple control intended to give a sampling of what is possible.  You can easily extend it to add more features and make it more useful.

  • Add support for properties, events, etc
  • Embed the language selection as part of the control
  • Build a custom designer
  • Define something more meaningful for the Language Code

I would love to hear how others are using .NET Reflector in their own applications.