How to download .NET Reflector 6 for free

If you like, you can skip the introduction and jump to Downloading .NET Reflector.

A brief history of .NET Reflector

In the beginning

In case you somehow missed it, .NET Reflector is a class browser, analyzer, and decompiler for .NET assemblies. Originally created by Lutz Roeder, it proved to be an extremely useful tool, making lists such as MSDN’s Ten Must-Have .NET Tools Every Developer Should Download Now. Best of all, .NET Reflector was released to the community for free, with Roeder developing and supporting the tool for over 8 years.

The downfall

However, in August 2008, Red Gate Software bought .NET Reflector. At the time, Roeder was told that Red Gate will continue to provide the free community version. Red Gate themselves stated that The first thing we are doing is continuing to offer the software to the community for free downloading. It didn’t stay that way. In February 2011, Red Gate announced that from version 7, .NET Reflector would no longer be free.

This might have been acceptable, but for the fact that free copies .NET Reflector expired after several months. Upon expiry, users were prompted to update to the latest version; if they declined, .NET Reflector would simply exit, although some versions contained a time-bomb that completely deleted .NET Reflector if it was too old. Effectively, all existing free users of .NET Reflector would be forced to either stop using .NET Reflector, or pay for version 7. The community backlash was immediate.

Scorched earth

After two months, Red Gate finally relented, partially reversing their decision to disable the free version of .NET Reflector. However, rather than once more releasing a free community edition, they instead only enabled existing free versions of .NET Reflector to update to a version that does not expire. For those users who had a self-deleting version, Red Gate offered no help. They also offered no way for new users to download the free version of .NET Reflector.

To date, Red Gate’s promise of maintaining a free community version remains broken. It has been more than a year since they first announced that they were going back on their word, but nothing has changed. While there are now more alternatives to .NET Reflector than ever, it is unfortunate that this whole situation came about in the first place.

A brief history of LookingGlass

Like most things developers create, LookingGlass was born out of curiosity. The question: Is it possible to download .NET Reflector from Red Gate without already having a copy? After all, old versions need a way to download updates somehow. Updating an old copy of .NET Reflector while running Wireshark quickly provided the answer: .NET Reflector makes two HTTP requests to Red Gate’s servers, the first to determine the URL of the update, and the second to actually download it.

LookingGlass is a small program that reproduces .NET Reflector’s requests to Red Gate’s servers and saves the result to a file. A few hacks were necessary to perfectly emulate how .NET Reflector makes its HTTP requests (See line 87), including not reusing the HTTP connection despite the Keep-Alive headers (.NET Reflector’s built-in updater launches as a separate process). From Red Gate’s side, it should be impossible to tell the difference between the two.

Downloading .NET Reflector

Simply save the below code as a file (For example, LookingGlass.cs), compile it and run the resulting program. It will save the downloaded file to the same directory as the program itself. If necessary, you can download Visual C# Express for free, or use the C# compiler (csc.exe) that is included with every version of the .NET framework to compile this code. LookingGlass should work perfectly on .NET 2.0 and above.

// This is free and unencumbered software released into the public domain.
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;

namespace LookingGlass
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
                QueryDownloadUrl(@"http://reflector.red-gate.com/Reflector.version");
            else
                Download(args[0]);
        }

        static void QueryDownloadUrl(string versionUrl)
        {
            Console.WriteLine("Phase 1: Getting download URL...");

            HttpWebRequest request = GetHttpWebRequest(versionUrl);
            string downloadUrl = null;
            string data;

            using (WebResponse response = request.GetResponse())
            {
                data = ReadResponse(response);

                foreach (string line in data.Split('\n'))
                {
                    if (line.StartsWith("http", StringComparison.OrdinalIgnoreCase))
                    {
                        // If this version number stops working, search for other known .NET Reflector version numbers; Red Gate's own forums mention a few
                        downloadUrl = line.Trim('\r').Replace("{Client}", "Reflector").Replace("{Version}", "6.1.0.11");
                        break;
                    }
                }
            }

            if (downloadUrl == null)
            {
                Console.WriteLine("Failed to find download URL. Returned data was:");
                Console.WriteLine(data);
                Console.ReadLine();
            }
            else
            {
                Console.WriteLine("Launching download process...");
                Process.Start(Process.GetCurrentProcess().MainModule.FileName, downloadUrl);
                Console.WriteLine("Phase 1 complete.");
            }
        }

        static void Download(string downloadUrl)
        {
            Console.WriteLine("Phase 2: Downloading...");

            HttpWebRequest request = GetHttpWebRequest(downloadUrl);
            string path;

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                path = GetDownloadFilename(response);

                if (response.StatusCode == HttpStatusCode.OK && !string.IsNullOrEmpty(path))
                {
                    Console.WriteLine("Saving to {0}...", path);
                    SaveResponse(response, path);
                    Console.WriteLine("Done!");
                }
                else
                {
                    Console.WriteLine("Download failed; server returned status code {0}. Returned data was:", response.StatusCode);
                    Console.WriteLine(ReadResponse(response));
                    Console.ReadLine();
                }
            }
        }

        static HttpWebRequest GetHttpWebRequest(string url)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Headers.Add("Cache-Control: no-cache, no-store, max-age=0");
            // Emulate the manner in which the headers are handled
            FieldInfo usesProxySemantics = typeof(ServicePoint).GetField("m_ProxyServicePoint", BindingFlags.NonPublic | BindingFlags.Instance);
            usesProxySemantics.SetValue(request.ServicePoint, true);
            return request;
        }

        static string GetDownloadFilename(HttpWebResponse response)
        {
            string contentDisposition = response.Headers["content-disposition"];

            if (string.IsNullOrEmpty(contentDisposition))
                return null;

            string filenameField = "filename=";
            int index = contentDisposition.IndexOf(filenameField, StringComparison.CurrentCultureIgnoreCase);

            if (index < 0)
                return null;

            return contentDisposition.Substring(index + filenameField.Length).Trim('"');
        }

        static string ReadResponse(WebResponse response)
        {
            using (Stream netStream = response.GetResponseStream())
            {
                using (StreamReader reader = new StreamReader(netStream))
                {
                    return reader.ReadToEnd();
                }
            }
        }

        static void SaveResponse(WebResponse response, string path)
        {
            byte[] buffer = new byte[4 * 1024];
            int bytesRead;

            using (Stream netStream = response.GetResponseStream())
            {
                if (File.Exists(path))
                    File.Delete(path);

                using (FileStream fileStream = File.Create(path))
                {
                    while ((bytesRead = netStream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        Console.Write(".");
                        fileStream.Write(buffer, 0, bytesRead);
                    }
                }
            }

            Console.WriteLine();
        }
    }
}

Where to from here?

Once you have .NET Reflector up and running, be sure to try out some of the many free community add-ins available on .NET Reflector’s site and Codeplex. Tell your friends about it. And above all, don’t always believe a company when they tell you they’ll keep a product free.

The Legend of FinkyPieheimer@zoobatz.com

Occasionally, you want a quick disposable email address for registering on suspicious websites, or in my case, for testing your own website’s registration system. While there are several options, I use Mailinator because you don’t need to set up anything first. You just enter whatever you want, followed by @mailinator.com as your email address, and it just works.

This is what I was doing last week when I noticed that all the email sent by my website’s registration system was apparently from FinkyPieheimer@zoobatz.com. That’s definitely not what I set, and I confirmed that a valid from address was being used. The only recent change was that a friendly display name was now being included, instead of just the email address by itself. So what gives?

Upon examining the raw email (When viewing an email in Mailinator, click the Text View button), I noticed a linebreak in the From header.

...
MIME-Version: 1.0
From: "Quux Baz"
 
To: test@mailinator.com
...

While that’s unusual, other email programs and systems handle it just fine. Mailinator, however, doesn’t like it one bit. It seems to regard it as invalid, and as such, replaces the field with FinkyPieheimer@zoobatz.com as a sort of placeholder.

So what can you do about it? If you’re just registering on a website, don’t worry about it. If you’re working on your own website, see if you can change how the From email address is set.

In my case, I was using .NET’s SmtpClient, MailMessage and MailAddress classes. I found a StackOverflow question about the strange carriage return & line feed, which only appears when you set the DisplayName property of a MailAddress instance. This only happens under .NET 4.0, and you can see the hardcoded “\r\n ” are the end of the MailAddress.Encode(Int32) method. To resolve this, either convert the project to .NET 3.5, remove the display name, or, if you have an intermediate delivery system, add a processing step to fix this after it leaves the .NET SmtpClient.

Announcing Manta

It’s taken much too long, but I’ve finally created an online project for Manta, which until recently didn’t even have an official name. Manta is an open source .NET library that currently features a variety of I/O- and web-related classes that simplify and ease solving hard problems.

On the I/O side, Manta offers support for managing hardlinks, reparse points, junctions, symbolic links. It also comes with unit tests and documented methods and classes.

On the web side, Manta enables easy manipulation of URLs, RSS feeds and phpBB posts. It too features unit tests and documentation.

You can find out more about Manta or download it from CodePlex.

Crouching Enumerator, Hidden Boxing

A few months ago, I was playing around with a simple C# permutation generator to build a word list, with each character position having its own list of characters to iterate through:

List.Enumerator enumerator = characterList.GetEnumerator();

while (condition) {
	if (!enumerator.MoveNext()) {
		enumerator.Reset();
		enumerator.MoveNext();
	}

	Consume(enumerator.Current);
}

Oddly, I was getting a compilation error on line 5, as enumerator doesn’t have a Reset() method despite the IEnumerator interface defining one. MSDN quickly cleared things up, though, revealing that List<T>.Enumerator explicitly implements the method as void IEnumerator.Reset(), which is implicitly private. You can still call private interface methods if you first cast to that interface, so I changed line 5 to the following:

		((IEnumerator) enumerator).Reset();

I thought that was the end of it, but bizarely, the second call to MoveNext() on line 6 also returns false. I confirmed that the private Reset() method actually implements reset functionality, which it certainly does, yet it seemed to have no effect.

The answer lies in the fact that List<T>.Enumerator is a struct. This means that behind the scenes, the cast to IEnumerator is creating a boxed copy of enumerator, and that’s what’s being reset. The original is left untouched, so the call to MoveNext() will naturally return false. Rather than trying to keep the boxed copy that you get from the cast, the correct solution is to use IEnumerator<T> from the outset, rather than List<T>.Enumerator:

IEnumerator enumerator = characterList.GetEnumerator();

while (condition) {
	if (!enumerator.MoveNext()) {
		enumerator.Reset();
		enumerator.MoveNext();
	}

	Consume(enumerator.Current);
}

All this drama could have been avoided if I hadn’t checked the return type for List<T>.GetEnumerator() and used that. So much for more explicit typing being helpful.

Still, it’s rather odd that List<T>.Enumerator is both public and a struct. The former encourages its direct use, and the latter results in the problem I was experiencing. Sadly, this design is constant throughout the System.Collections.Generic namespace. By contrast, the equivalent non-generic collection is ArrayList, and there, the GetEnumerator() method returns an IEnumerator, which is implemented by a private class nested within the ArrayList type – a design that is constant throughout the rest of the System.Collections namespace, and is, in my opinion, better for everyone.

The Joy of ASP.NET, Visual Studio and Proxies

Some time ago, the web project I was working on started refusing to open. Every time I tried to reload the ASP.NET project, Visual Studio (In this case, VS.NET 2003, but it may affect 2005 and 2008) gave a strange error message:

The Web server reported the following error when attempting to create or open the Web project located at the following URL:
‘http://localhost:/xyz/ACME.XYZ.Widget’. ‘The connection with the server was reset’.

The project files hadn’t changed and I hadn’t made any changes to IIS recently. In my search for a solution, I come across someone else having problems creating ASP.NET projects from Visual Studio on a network that used a proxy, getting the message ‘A connection with the server could not be established’ – this proved to be the key to the answer. In short, Visual Studio needs to support environments where your ASP.NET project is not hosted on your own machine, so it uses the system’s proxy settings.

In my case, under Control Panel > Internet Options > Connections > LAN Settings…, I had enabled Use a proxy server for your LAN but had neglected to also tick the Bypass proxy server for local addresses checkbox. When Visual Studio gave the proxy a request for localhost, the proxy responded by closing the connection, and thus Visual Studio came back with the error message. Simple, logical, and entirely frustrating.