Click here to monitor SSC
Av rating:
Total votes: 11
Total comments: 6


Nick Harrison
Hosting .NET Reflector in your own Application
22 March 2009

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.

 public interface IFormatter

{

    void Write(string value);

    void WriteComment(string value);

    void WriteDeclaration(string value);

    void WriteDeclaration(string value,  object target);

    void WriteIndent();

    void WriteKeyword(string value);

    void WriteLine();

    void WriteLiteral(string value);

    void WriteOutdent();

    void WriteProperty(string name, string value);

    void WriteReference (string value, string description,  object target);

}

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.

 public HtmlFormatter(System.Web.UI.HtmlTextWriter writer)

{

    this.writer = writer;

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

public void WriteIndent()

{

    this.writer.RenderBeginTag(System.Web.UI.HtmlTextWriterTag.Ul);

}

 

public void WriteOutdent()

{

    this.writer.RenderEndTag();

}

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.

private void WriteFormattedText(string text, string format)

{

    writer.AddAttribute(System.Web.UI.HtmlTextWriterAttribute.Class, format);

    writer.RenderBeginTag(System.Web.UI.HtmlTextWriterTag.Span);

    writer.Write(text);

    writer.RenderEndTag();

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

public void WriteDeclaration(string text, object target)

{

    this.WriteDeclaration(text);

}

 

public void WriteKeyword(string text)

{

    this.WriteFormattedText(text, "keyword");

}

 

public void WriteComment(string text)

{

    this.WriteFormattedText(text, "comment");

}

 

public void WriteLiteral(string text)

{

    this.WriteFormattedText(text, "literal");

}

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.

public IAssembly Resolve(IAssemblyReference value, string localPath)

{

    Assembly assembly = Assembly.Load(value.ToString());

    return assemblyManager.LoadFile(assembly.Location);

}

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.

public interface ILanguageWriterConfiguration

{

    // Properties

    string this[string name] { get; }

    IVisibilityConfiguration Visibility { get; }

}

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

public string this[string name]

{

    get

    {

        System.Diagnostics.Debug.WriteLine(name);

        return (string)this.table[name];

    }

 

    set

    {

        this.table[name] = value;

    }

}

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

 private void Disassemble(int languageCode, string assemblyPath,

    string typeName, string methodName, HtmlTextWriter htmlWriter)

{

    serviceProvider = new ApplicationManager(null);

    IAssemblyManager assemblyManager = (IAssemblyManager)serviceProvider.

        GetService(typeof(IAssemblyManager));

    ILanguageManager languageManager =

 (ILanguageManager)serviceProvider.

GetService(typeof(ILanguageManager));

    ITranslatorManager translatorManager =

               (ITranslatorManager)serviceProvider.

        GetService(typeof(ITranslatorManager));

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.

 assemblyManager.Resolver = new AssemblyResolver(assemblyManager);

HtmlFormatter formatter = new HtmlFormatter(htmlWriter); 

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

private LanguageWriterConfiguration ConfigureLangugaeConfiguration()

{

    IVisibilityConfiguration visibilityConfiguration =

        (IVisibilityConfiguration)serviceProvider.

        GetService(typeof(IVisibilityConfiguration));

    LanguageWriterConfiguration configuration =

        new LanguageWriterConfiguration();

    configuration["ShowCustomAttributes"] = "false";

    configuration["ShowDocumentation"] = "true";

    configuration["ShowMethodDeclarationBody"] = "true";

    configuration["ShowNamespaceImports"] = "true";

    configuration["ShowTypeDeclarationBody"] = "true";

 

    configuration.Visibility = visibilityConfiguration;

    return configuration;

}

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.

LanguageWriterConfiguration configuration = ConfigureLangugaeConfiguration();

ILanguageWriter writer = languageManager.Languages[languageCode ].

    GetWriter(formatter, configuration);

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

public interface ITypeDeclaration : ITypeReference,

ICustomAttributeProvider, IDocumentationProvider

{

    // Properties

    bool Abstract { get; set; }

    ITypeReference BaseType { get; set; }

    bool BeforeFieldInit { get; set; }

    IEventDeclarationCollection Events { get; }

    IFieldDeclarationCollection Fields { get; }

    bool Interface { get; set; }

    ITypeReferenceCollection Interfaces { get; }

    IMethodDeclarationCollection Methods { get; }

    ITypeDeclarationCollection NestedTypes { get; }

    IPropertyDeclarationCollection Properties { get; }

    bool RuntimeSpecialName { get; set; }

    bool Sealed { get; set; }

    bool SpecialName { get; set; }

    TypeVisibility Visibility { get; set; }

}

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.

IAssembly assembly = assemblyManager.LoadFile(assemblyPath);

foreach (IModule module in assembly.Modules)

{

    foreach (ITypeDeclaration typeDeclaration in module.Types)

    {

        if (typeDeclaration.Name == typeName)

        {

            foreach (IMethodDeclaration methodDeclaration

                in typeDeclaration.Methods)

            {

                if (methodDeclaration.Name == methodName)

                {

 

                    if (languageCode == 0)

                        writer.WriteMethodDeclaration(methodDeclaration);

                    else

                    {

                        IMethodDeclaration alternateMethodDeclaration =

                        translatorManager.CreateDisassembler("", "").

                        TranslateMethodDeclaration(methodDeclaration);

                        writer.WriteMethodDeclaration

                            (alternateMethodDeclaration);

                    }

                }

            }

        }

    }

}

assemblyManager.Unload(assembly); 

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.

protected override void Render(System.Web.UI.HtmlTextWriter writer)

{

    writer.WriteLine("");

    writer.AddAttribute(HtmlTextWriterAttribute.Class, "code");

    writer.RenderBeginTag(HtmlTextWriterTag.Div);

    Disassemble(this.LanguageCode, this.AssemblyPath,

        this.TypeName, this.MethodName, writer);

    writer.RenderEndTag();

}

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.

<controls>

     <add tagPrefix="reflector" namespace="ReflectorWrapper"

         assembly="ReflectorWrapper"/>

</controls>

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.

<reflector:ReflectorComponent ID = "refMain" LanguageCode = "1"

TypeName = "ReflectorComponent" MethodName = "Disassemble" runat = "server" />

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.

protected void Page_Load(object sender, EventArgs e)

{

    if (!this.IsPostBack)

    {

        ddlLanguages.DataSource = refMain.languageNames;

        ddlLanguages.DataBind();

    }

    string path = Server.MapPath(@"bin\ReflectorWrapper.dll");

    refMain.AssemblyPath = path;

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

protected void ddlLanguages_SelectedIndexChanged(object sender, EventArgs e)

{

    refMain.LanguageCode = ddlLanguages.SelectedIndex;

The Screenshots




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.



This article has been viewed 8791 times.
Nick Harrison

Author profile: Nick Harrison

Nick Harrison is a Software Architect and .NET advocate in Columbia, SC. Nick has over 14 years experience in software developing, starting with Unix system programming and then progressing to the DotNet platform. You can read his blog as www.geekswithblogs.net/nharrison

Search for other articles by Nick Harrison

Rate this article:   Avg rating: from a total of 11 votes.


Poor

OK

Good

Great

Must read
 
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: the zip?
Posted by: Andy Crawford (not signed in)
Posted on: Monday, March 23, 2009 at 6:47 AM
Message: Where would I find the complete example? Nice article.
Andy.Crawford@nscorp.com

Subject: Source of the project
Posted by: Andrew Clarke (view profile)
Posted on: Monday, March 23, 2009 at 5:43 PM
Message: The source of the project can be downloaded from the Speech-bubble at the top of the article. it is a RAR file. Click on the Item 'hosting Reflector.rar'

Subject: Using Reflector
Posted by: Anonymous (not signed in)
Posted on: Wednesday, April 01, 2009 at 5:16 AM
Message: Superb article. Very much worth reading.

Subject: Great !!
Posted by: Strickos9 (view profile)
Posted on: Wednesday, June 24, 2009 at 5:50 AM
Message: Great article, thanks for sharing

Subject: Great !!
Posted by: Strickos9 (view profile)
Posted on: Wednesday, June 24, 2009 at 5:50 AM
Message: Great article, thanks for sharing

Subject: Problem getting methods' code
Posted by: i02fesea (view profile)
Posted on: Saturday, February 26, 2011 at 5:29 AM
Message: Hi Nick, thank you very much for your post. I have tried to use your code in order to do a small work for the university, but I have a problem. The program fails when I set the configuration["ShowMethodDeclarationBody"] = "true";. If this property is equal to false the program work, but this can disassembly the methods' body. Can you tell me if there is any work arround for this problem? Thank you in advance

 






recommended site pinvoke

PInvoke.net is a user-driven wiki which provides .NET developers with native method signatures, so they don't have to spend time writing them from scratch.




TortoiseSVN and Subversion Cookbook Part 3: In, Out, and Around
 Subversion doesn't have to be difficult, especially if you have Michael Sorens's guide at hand. After... Read more...

Feature Usage Reporting in Early Access Programs
 After doing Web development, you can get very used to the luxury of having basic information about your... Read more...

Feature Usage Reporting in Early Access Programs
 After doing Web development, you can get very used to the luxury of having basic information about your... Read more...

TLS/SSL and .NET Framework 4.0
 The Secure Socket Layer is now essential for the secure exchange of digital data, and is most generally... Read more...

SmartAssembly: Eating Our Own Dogfood
 Quite often at Red Gate, we are some of our own most enthusiastic software-users. SmartAssembly is a... Read more...

A Complete URL Rewriting Solution for ASP.NET 2.0
 Ever wondered whether it's possible to create neater URLS, free of bulky Query String parameters?... Read more...

Visual Studio Setup - projects and custom actions
 This article describes the kinds of custom actions that can be used in your Visual Studio setup project. Read more...

.NET Application Architecture: the Data Access Layer
 Find out how to design a robust data access layer for your .NET applications. Read more...

Web Parts in ASP.NET 2.0
 Most Web Parts implementations allow users to create a single portal page where they can personalize... Read more...

Configuring Forms Authentication in SharePoint 2007
 Damon Armstrong provides a step-by-step guide to the processes, quirks and pitfalls of setting up... Read more...

Over 400,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.

Join Simple Talk