Canonical Voices

Posts tagged with '.net'

mandel

I’m currently working on a “small” pet project (that I want to take advantage of at some point) in which I’m json serializing (Json.Net) a Tuple in which the Item type can be of any kind. Of course when deserializing the object, I’d like to have my object type as close as possible to its original type, that is, if I serialized a Tuple<string> I want to get the same object type and not a Tuple<object>.

To solve this I have written the following small extension method to do the job using Reflection:

/// <summary>
/// Downcast the more generic tuple with an object type 
/// to a more specific one.
/// </summary>
/// <param name="tuple">The tuple to down cast.</param>
/// <returns>The downcasted instance of the object.</returns>
public static object DownCast(this Tuple<object> tuple)
{
    var tupleType = typeof (Tuple<>);
    var downcast = tupleType.MakeGenericType(tuple.Item1.GetType());
    return Activator.CreateInstance(downcast, tuple.Item1);
}

The same idea can be used for any type of tuple (Tuple<,>, Tuple<,,> etc…). I hope this small code helps someone else :)

Read more
mandel

Sometimes on Linux we take for granted DBus. On the Ubntu One Windows port we have had to deal with the fact that DBus on Windows is not that great and therefore had to write our own IPC between the python code and the c# code. To solve the IPC we have done the following:

Listen to a named pipe from C#

The approach we have followed here is pretty simple, we create a thread pool that will create NamedPipe. The reason for using a threadpool is to avoid the situation in which we only have a single thread dealing with the messages from python and we have a very chatty python developer. The code in c# is very straight forward:

/*
 * Copyright 2010 Canonical Ltd.
 * 
 * This file is part of UbuntuOne on Windows.
 * 
 * UbuntuOne on Windows is free software: you can redistribute it and/or modify		
 * it under the terms of the GNU Lesser General Public License version 		
 * as published by the Free Software Foundation.		
 *		
 * Ubuntu One on Windows is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the	
 * GNU Lesser General Public License for more details.	
 *
 * You should have received a copy of the GNU Lesser General Public License	
 * along with UbuntuOne for Windows.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Authors: Manuel de la Peña <manuel.delapena@canonical.com>
 */
using System;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using log4net;
 
namespace Canonical.UbuntuOne.ProcessDispatcher
{
    /// <summary>
    /// This oject represents a listener that will be waiting for messages
    /// from the python code and will perform an operation for each messages
    /// that has been recived. 
    /// </summary>
    internal class PipeListener : IPipeListener
    {
        #region Helper strcut
 
        /// <summary>
        /// Private structure used to pass the start of the listener to the 
        /// different listening threads.
        /// </summary>
        private struct PipeListenerState
        {
            #region Variables
 
            private readonly string _namedPipe;
            private readonly Action<object> _callback;
 
            #endregion
 
            #region Properties
 
            /// <summary>
            /// Gets the named pipe to which the thread should listen.
            /// </summary>
            public string NamedPipe { get { return _namedPipe; } }
 
            /// <summary>
            /// Gets the callback that the listening pipe should execute.
            /// </summary>
            public Action<object> Callback { get { return _callback; } }
 
            #endregion
 
            public PipeListenerState(string namedPipe, Action<object> callback)
            {
                _namedPipe = namedPipe;
                _callback = callback;
            }
        }
 
        #endregion
 
        #region Variables
 
        private readonly object _loggerLock = new object();
        private ILog _logger;
        private bool _isListening;
        private readonly object _isListeningLock = new object();
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// Gets the logger to used with the object.
        /// </summary>
        internal ILog Logger
        {
            get
            {
                if (_logger == null)
                {
                    lock (_loggerLock)
                    {
                        _logger = LogManager.GetLogger(typeof(PipeListener));
                    }
                }
                return _logger;
            }
            set
            {
                _logger = value;
            }
        }
 
        /// <summary>
        /// Gets if the pipe listener is indeed listening to the pipe.
        /// </summary>
        public bool IsListening
        {
            get { return _isListening; }
            private set
            {
                // we have to lock to ensure that the threads do not screw each
                // other up, this makes a small step of the processing to be sync :(
                lock (_isListeningLock)
                {
                    _isListening = value;
                }
            }
        }
 
        /// <summary>
        /// Gets and sets the number of threads that will be used to listen to the 
        /// pipe. Each thread will listeng to connections and will dispatch the 
        /// messages when ever they are done.
        /// </summary>
        public int NumberOfThreads { get; set; }
 
        /// <summary>
        /// Gets and sets the pipe stream factory that know how to generate the streamers used for the communication.
        /// </summary>
        public IPipeStreamerFactory PipeStreamerFactory { get; set; }
 
        /// <summary>
        /// Gets and sets the action that will be performed with the message of that 
        /// is received by the pipe listener.
        /// </summary>
        public IMessageProcessor MessageProcessor { get; set; }
 
        #endregion
 
        #region Helpers
 
        /// <summary>
        /// Helper method that is used in another thread that will be listening to the possible events from 
        /// the pipe.
        /// </summary>
        private void Listen(object state)
        {
            var namedPipeState = (PipeListenerState)state;
 
            try
            {
                var threadNumber = Thread.CurrentThread.ManagedThreadId;
                // starts the named pipe since in theory it should not be present, if there is 
                // a pipe already present we have an issue.
                using (var pipeServer = new NamedPipeServerStream(namedPipeState.NamedPipe, PipeDirection.InOut, NumberOfThreads,PipeTransmissionMode.Message,PipeOptions.Asynchronous))
                {
                    Logger.DebugFormat("Thread {0} listenitng to pipe {1}", threadNumber, namedPipeState.NamedPipe);
                    // we wait until the python code connects to the pipe, we do not block the 
                    // rest of the app because we are in another thread.
                    pipeServer.WaitForConnection();
 
                    Logger.DebugFormat("Got clien connection in tread {0}", threadNumber);
                    try
                    {
                        // create a streamer that know the protocol
                        var streamer = PipeStreamerFactory.Create();
                        // Read the request from the client. 
                        var message = streamer.Read(pipeServer);
                        Logger.DebugFormat("Message received to thread {0} is {1}", threadNumber, message);
 
                        // execute the action that has to occur with the message
                        namedPipeState.Callback(message);
                    }
                    // Catch the IOException that is raised if the pipe is broken
                    // or disconnected.
                    catch (IOException e)
                    {
                        Logger.DebugFormat("Error in thread {0} when reading pipe {1}", threadNumber, e.Message);
                    }
 
                }
                // if we are still listening, we will create a new thread to be used for listening,
                // otherwhise we will not and not lnger threads will be added. Ofcourse if the rest of the
                // threads do no add more than one work, we will have no issues with the pipe server since it
                // has been disposed
                if (IsListening)
                {
                    ThreadPool.QueueUserWorkItem(Listen, namedPipeState);
                }
            }
            catch (PlatformNotSupportedException e)
            {
                // are we running on an OS that does not have pipes (Mono on some os)
                Logger.InfoFormat("Cannot listen to pipe {0}", namedPipeState.NamedPipe);
            }
            catch (IOException e)
            {
                // there are too many servers listening to this pipe.
                Logger.InfoFormat("There are too many servers listening to {0}", namedPipeState.NamedPipe);
            }
        }
 
        #endregion
 
        /// <summary>
        /// Starts listening to the different pipe messages and will perform the appropiate
        /// action when a message is received.
        /// </summary>
        /// <param name="namedPipe">The name fof the pipe to listen.</param>
        public void StartListening(string namedPipe)
        {
            if (NumberOfThreads < 0)
            {
                throw new PipeListenerException(
                    "The number of threads to use to listen to the pipe must be at least one.");
            }
            IsListening = true;
            // we will be using a thread pool that will allow to have the different threads listening to 
            // the messages of the pipes. There could be issues if the devel provided far to many threads
            // to listen to the pipe since the number of pipe servers is limited.
            for (var currentThreaCount = 0; currentThreaCount < NumberOfThreads; currentThreaCount++)
            {
                // we add an new thread to listen
                ThreadPool.QueueUserWorkItem(Listen, new PipeListenerState(namedPipe, MessageProcessor.ProcessMessage));
            }
 
        }
 
        /// <summary>
        /// Stops listening to the different pipe messages. All the thread that are listening already will 
        /// be forced to stop.
        /// </summary>
        public void StopListening()
        {
            IsListening = false;
        }
 
    }
}

Sending messages from python

Once the pipe server is listening in the .Net side we simple have to use the CallNamedPipe method to be able to send messages to .Net. In my case I have used Json as a stupid protocol, ideally you should do something smart like protobuffers.

 call the pipe with the message
    try:
        data = win32pipe.CallNamedPipe(pipe_name, 
            data_json, len(data_json), 0 )
    except Exception, e:
        print "Error: C# client is not listening!! %s" % e.message

Read more
mandel

It is not a secret that I love Spring.Net, it just makes the development of big application a pleasure. During the port of Ubuntu One to Windows I have been using the framework to initialise the WCF service that we use to provide other .Net applications the ability of communicating with Ubuntu One. Yes, this is our DBus alternative!

The idea behind using WCF is to allow other applications to use the different features that Ubuntu One provides, the very first application that we would like to use this would be Banshee on Windows (I have to start looking into that, but I have too much to do right now). In order to provide this functionality we use named pipes to allow the communication, there are two reasons for this:

  • For an application to host a WCF service that uses a binding besides the named pipe binding requires special permissions. This is clearly a no no for a user application like Ubuntu One.
  • Named pipes are dammed efficient!!! Named pipes on Windows are at the kernel level, cool :)

Initially I though of hosting the WCF services as a Windows services, why not?!?! Once I had this feature implemented, I realized the following. It turns out that while impersonation does get spawn within different threads, this is not the case for processes. This is a major pain in the ass. The main reason for this being a problem is the fact that if an application is executed in a different user space, the different env variables that are used are those of the user executing the code. This means that things like your user roming app dir will not be able to use, plus other security issues.

After realizing that the WCF services could not be hosted on a Windows service, I moved to write a work a round that would do the following:

  1. Configure the WCF services to use named pipes only for the current user.
  2. Start a console application that will host the WCF services.
  3. Start the different WCF clients for Ubuntu One (currently is our clietn app, but should it could be your own!

Although the definition of the solution is simple, we have to work around the issue that up ’til now all our WCF services were defined through configuration and were injected by the spring.net IoC. Usually you can change the location of you app domain configuration by using the following code:

AppDomain.CurrentDomain.SetData(“APP_CONFIG_FILE”,”c:\\ohad.config);

In theory wth the above code you can redirect the configuration to a new file, and if you use for example:

System.Configuration.ConfigurationSettings.AppSettings["my_setting"]

you will be able to get the value of your new configuration. Unfortunatly, the Spring.Net IoC uses the ConfigurationManager class which ignores that setting… Now what?

Well, re-writting all the code to not use Spring.Net IoC was not an option because it means changing a lot of work and does mean to move from an application where dependencies are injected to one were we have to manually init all the different objects. After some careful though, I move to use a small CLR detail that I knew to make the AppDomain that executed our code to use the users configuration. The trick is the following, use one AppDomain to start the application. This would be a dummy AppDomain that does not execute any code at all but launches a second AppDomain whose configuration is the correct one and which will execute the actual code.

In case I did not make any sense, here is an example code:

using System;
using Canonical.UbuntuOne.Common.Container;
using Canonical.UbuntuOne.Common.Utils;
using log4net;
 
namespace Canonical.UbuntuOne.ProcessDispatcher
{
 
    static class Program
    {
        private static readonly ILog _logger = LogManager.GetLogger(typeof(Program));
        private static readonly ConfigurationLocator _configLocator = new ConfigurationLocator();
 
        /// <summary>
        /// This method starts the service.
        /// </summary>
        static void Main()
        {
 
            _logger.Debug("Redirecting configuration");
 
            // Setup information for the new appdomain.
            var setup = new AppDomainSetup
            {
                ConfigurationFile = _configLocator.GetCurrentUserDaemonConfiguration()
            };
 
            // Create the new appdomain with the new config.
            var executionAppDomain = AppDomain.CreateDomain("ServicesAppDomain",
                AppDomain.CurrentDomain.Evidence, setup);
 
            // Call the write config method in that appdomain.
            executionAppDomain.DoCallBack(() =>
            {
                _logger.Debug("Starting services.");
                // use the IoC to get the implementation of the SyncDaemon service, the IoC will take care of 
                // setting the object correctly.
                ObjectsContainer.Initialize(new SpringContainer());
                var syncDaemonWindowsService = 
                     ObjectsContainer.GetImplementationOf<SyncDaemonWindowsService>();
                // To run more than one service you have to add them here
                syncDaemonWindowsService.Start();
                while (true) ;
            });
 
        }
    }
}

Well I hope this helps someone else :D

Read more
mandel

On the process on porting Ubuntu One to windows we took a number of decisions that we expect to make peoples live better when installing it.

Python

One of the most important decisions that had to be taken was what to do with python. Most of the code (probably all) of the sync daemon has been written in python and reusing that code is a plus.

In this situation we could have chosen to different paths:

  • Dependency: Add the presence of a python runtime as a dependency. That is either add a bootstrap that installs python, install it in its normal location through a python installer or ask the user to do it for us.
  • Embedded:Use py2exe or pyInstaller to distribute the python binaries so that we do not “require” python to be there.

Both options have their pros an cons but we decide for second one for the following reasons:

  • A user could change the python runtime and brake Ubuntu One
  • More than one runtime could be there.
  • Is a normal user really interested about having python in their machine?

Unfortunately so far I have not managed to use pyInstaller which I’ve been told is smarter than py2exe in terms of creating smaller binary packages (anyone with experience with that is more than welcome to help me).

But Pyhon is not THAT heavy

Indeed, Python is not that heavy and should not make the msi huge. But of course Ubuntu One has a number of dependencies that are not Python alone:

  • Ubuntu SSO: In the last iteration of Ubuntu One the service started using the Ubuntu SSO. This code has been re-writen in C# and in the near future will be exposed by a WCF service so that things such as the Ubuntu One music store could be used on Windows (banshee sounds like a great app to offer on windows)
  • Gnome keyring: This is actually a dependency from Ubuntu SSO, but it has to be taken into account. We needed a place where we could store secrets. I have implemented a small lib that allows to store secrets in the Current User registry key that uses the DPAPI so that the secrets are safe. Later in the cycle I’ll add a WCF service that will allow other apps to store secrets there and might even add an abstraction layer so that it uses the GnomeKeyring code done by NDesk when on Linux.
  • Dependency Injection: I have intially started using WPF but I do not deny the option of using GTK# in the future, or at least have the option. For that I have sued the DI of Spring.Net so that contributors can add their own views. You could even customize your Ubuntu One by modifying the DI and point to a dll that provides a new UI.
  • PDBs are present: Currently PDB files are present and the code is compiled in DEBUG mode, this does make a diff in the size of it.

At the end of the day, all this adds up making the msi quite big. Currently I’m focused on porting all the required code, in the next cycle I’ll make sure you have a nice and small package :D

Read more
mandel

I have been working for about 2 moths now and after releasing our internal alpha release I have found a very interesting bug. In our port to windows we have decided to try and make your live as nice as possible in an environment as rough as Windows and to do so we allow our port to auto-update to always deliver the latests bug fixes.

Ofcourse to ensure that we are updating you system with the correct data we always perform a check sum of the msi. While our msi is updating to S3 using python, the code that downloads it is C#. Here are the different codes to calcualte the checksum:

    fd = open(filename,'r')
    md5_hash = hashlib.md5(fd.read()).hexdigest()
private static string Checksum(AlgoirhtmsEnum algorithm, string filePath)
        {
            HashAlgorithm hash;
            switch (algorithm)
            {
                case AlgoirhtmsEnum.SHA1:
                    hash = new SHA1Managed();
                    break;
                case AlgoirhtmsEnum.SHA256:
                    hash = new SHA256Managed();
                    break;
                case AlgoirhtmsEnum.SHA384:
                    hash = new SHA384Managed();
                    break;
                case AlgoirhtmsEnum.SHA512:
                    hash = new SHA512Managed();
                    break;
                default:
                    hash = new MD5CryptoServiceProvider();
                    break;
 
            }
            var checksum = "";
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                var md5 = hash.ComputeHash(stream);
                for (var i = 0; i < md5.Length; i++)
                {
                    checksum += md5[i].ToString("x2").ToLower();
                }
            }
            return checksum;
        }

Believe it or not the hash returned by each piece of code was different. WTF!!!! After a ridiculous time looking at it I managed to spot the issue. If you are using python on windows, unless you use the b option when opening a file, python will convert all the CRLF to LF making the hash to be different, how to fixed this? simply open the file this way:

fd = open(filename,'rb')

Go an figure….

Read more
mandel

I’m currently heavily working on Windows (oh irony) and I was confronted with a situation in which I wanted to be able to write a unit test that will access a web-page perform certain operations and check that everything went ok. This is not the first time I have been confronted with this situation (yes, really) and last time I resorted to use the great trick from Phil Haack in which the Microsoft.VisualStudio.WebHost namespace (yes, cassini) is used. I’ve copy paste his code here so that you can easily find it (you cannot longer access it through his page).

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
using Microsoft.VisualStudio.WebHost;
 
namespace Gazintas.Test.Common
{
        /// <summary>
        /// <para>A Web Server useful for unit tests.  Uses the same code used by the
        /// built in WebServer (formerly known as Cassini) in VisualStudio.NET 2005.
        /// Specifically, this needs a reference to WebServer.WebHost.dll located in
        /// the GAC.
        /// </para>
        /// </summary>
        /// <remarks>
        /// <para>If you unseal this class, make sure to make Dispose(bool disposing) a protected
        /// virtual method instead of private.
        /// </para>
        /// <para>
        /// For more information, check out: http://haacked.com/archive/2006/12/12/Using_WebServer.WebDev_For_Unit_Tests.aspx
        /// </para>
        /// </remarks>
        public sealed class TestWebServer : IDisposable
        {
                private bool started = false;
                private Server webServer;
                private int webServerPort;
                private string webServerVDir;
                private string sourceBinDir = AppDomain.CurrentDomain.BaseDirectory;
                private string webRoot = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WebRoot");
                private string webBinDir = Path.Combine(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WebRoot"), "bin");
                private string webServerUrl; //built in Start
 
                /// <summary>
                /// Initializes a new instance of the <see cref="TestWebServer"/> class on port 8085.
                /// </summary>
                public TestWebServer() : this(8085, "/")
                {
                }
 
                /// <summary>
                /// Initializes a new instance of the <see cref="TestWebServer"/> class
                /// using the specified port and virtual dir.
                /// </summary>
                /// <param name="port">The port.</param>
                /// <param name="virtualDir">The virtual dir.</param>
                public TestWebServer(int port, string virtualDir)
                {
                        this.webServerPort = port;
                        this.webServerVDir = virtualDir;
                }
 
                /// <summary>
                /// Starts the webserver and returns the URL.
                /// </summary>
                public Uri Start()
                {
                        //NOTE: WebServer.WebHost is going to load itself AGAIN into another AppDomain,
                        // and will be getting it's Assemblies from the BIN, including another copy of itself!
                        // Therefore we need to do this step FIRST because I've removed WebServer.WebHost from the GAC
                        if (!Directory.Exists(webRoot))
                                Directory.CreateDirectory(webRoot);
 
                        if (!Directory.Exists(webBinDir))
                                Directory.CreateDirectory(webBinDir);
 
                        CopyAssembliesToWebServerBinDirectory();
 
                        //Start the internal Web Server pointing to our test webroot
                        webServer = new Server(webServerPort, webServerVDir, this.webRoot);
                        webServerUrl = String.Format("http://localhost:{0}{1}", webServerPort, webServerVDir);
 
                        webServer.Start();
                        started = true;
                        Debug.WriteLine(String.Format("Web Server started on port {0} with VDir {1} in physical directory {2}", webServerPort, webServerVDir, this.webRoot));
                        return new Uri(webServerUrl);
                }
 
                private void CopyAssembliesToWebServerBinDirectory()
                {
                        foreach (string file in Directory.GetFiles(this.sourceBinDir, "*.dll"))
                        {
                                string newFile = Path.Combine(this.webBinDir, Path.GetFileName(file));
                                if (File.Exists(newFile))
                                {
                                        File.Delete(newFile);
                                }
                                File.Copy(file, newFile);
                        }
                }
 
                /// <summary>
                /// Makes a  simple GET request to the web server and returns
                /// the result as a string.
                /// </summary>
                /// <param name="page">The page.</param>
                /// <returns></returns>
                public string RequestPage(string page)
                {
                        WebClient client = new WebClient();
                        string url = new Uri(new Uri(this.webServerUrl), page).ToString();
                        using (StreamReader reader = new StreamReader(client.OpenRead(url)))
                        {
                                string result = reader.ReadToEnd();
                                return result;
                        }
                }
 
                /// <summary>
                /// Makes a  simple POST request to the web server and returns
                /// the result as a string.
                /// </summary>
                /// <param name="page">The page.</param>
                /// <param name="formParameters">
                /// The form paramater to post. Should be in the format "Name=Value&Name2=Value2&...&NameN=ValueN"
                /// For extra credit, build a version of this method that uses a NameValue collection. I use a
                /// string because it's possible you may want to post flat text.
                /// </param>
                /// <returns></returns>
                public string RequestPage(string page, string formParameters)
                {
                        HttpWebRequest request = WebRequest.Create(new Uri(new Uri(this.webServerUrl), page)) as HttpWebRequest;
 
                        if (request == null)
                                return null; //can't imagine this happening.
 
                        request.UserAgent = "Sutext UnitTest Webserver";
                        request.Timeout = 10000; //10 secs is reasonable, no?
                        request.Method = "POST";
                        request.ContentLength = formParameters.Length;
                        request.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
                        request.KeepAlive = true;
 
                        using (StreamWriter myWriter = new StreamWriter(request.GetRequestStream()))
                        {
                                myWriter.Write(formParameters);
                        }
 
                        HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                        if (response.StatusCode < HttpStatusCode.OK && response.StatusCode >= HttpStatusCode.Ambiguous)
                                return "Http Status" + response.StatusCode;
 
                        string responseText;
                        using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
                        {
                                responseText = reader.ReadToEnd();
                        }
 
                        return responseText;
                }
 
                /// <summary>
                /// Extracts a resources such as an html file or aspx page to the webroot directory
                /// and returns the filepath.
                /// </summary>
                /// <param name="resourceName">Name of the resource.</param>
                /// <param name="destinationFileName">Name of the destination file.</param>
                /// <returns></returns>
                public string ExtractResource(string resourceName, string destinationFileName)
                {
                        if (!started)
                                throw new InvalidOperationException("Please start the webserver before extracting resources.");
 
                        //NOTE: if you decide to drop this class into a separate assembly (for example,
                        //into a unit test helper assembly to make it more reusable),
                        //call Assembly.GetCallingAssembly() instead.
                        Assembly a = Assembly.GetExecutingAssembly();
                        string filePath;
                        using (Stream stream = a.GetManifestResourceStream(resourceName))
                        {
                                filePath = Path.Combine(this.webRoot, destinationFileName);
                                using (StreamWriter outfile = File.CreateText(filePath))
                                {
                                        using (StreamReader infile = new StreamReader(stream))
                                        {
                                                outfile.Write(infile.ReadToEnd());
                                        }
                                }
                        }
                        return filePath;
                }
 
                /// <summary>
                /// Stops this instance.
                /// </summary>
                public void Stop()
                {
                        Dispose();
                }
 
                ~TestWebServer()
                {
                        Dispose(false);
                }
 
                public void Dispose()
                {
                        Dispose(true);
                        GC.SuppressFinalize(this);
                }
 
                ///<summary>
                ///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
                ///</summary>
                /// <remarks>
                /// If we unseal this class, make sure this is protected virtual.
                /// </remarks>
                ///<filterpriority>2</filterpriority>
                private void Dispose(bool disposing)
                {
                        if (disposing)
                        {
                                ReleaseManagedResources();
                        }
                }
 
                // Cleans up the directories we created.
                private void ReleaseManagedResources()
                {
                        if(this.webServer != null)
                        {
                                this.webServer.Stop();
                                this.webServer = null;
                                this.started = false;
                        }
 
                        if (Directory.Exists(this.webBinDir))
                                Directory.Delete(this.webBinDir, true);
 
                        if (Directory.Exists(this.webRoot))
                                Directory.Delete(this.webRoot, true);
                }
        }
}

This is a great solution if either you expect all your developers to have visual studio since I’m not sure that you are allows to distribute WebDev.WebHost.dll, in order cases, this solution is no good. This is where Mono becomes extremely useful:

/**
 * Copyright 2010 Canonical Ltd.
 * 
 * This file is part of UbuntuOne on Windows.
 * 
 * UbuntuOne on Windows is free software: you can redistribute it and/or modify		
 * it under the terms of the GNU Lesser General Public License version 		
 * as published by the Free Software Foundation.		
 *		
 * Ubuntu One on Windows is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the	
 * GNU Lesser General Public License for more details.	
 *
 * You should have received a copy of the GNU Lesser General Public License	
 * along with UbuntuOne for Windows.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Authors: Manuel de la Peña <manuel.delapena@canonical.com>
 */
using System;
using System.IO;
using System.Net;
using System.Threading;
using Mono.WebServer;
 
namespace Canonical.UbuntuOne.Common.Tests
{
    /// <summary>
    /// This class allows to start a XSP server for testing purposes. Before the server is ran
    /// all the different properties must be set up so that the server knows where to run from an
    /// what to run.
    /// </summary>
    [Serializable]
    public class TestWebserver
    {
 
        private ApplicationServer _server;
 
        #region Property settings
 
        /// <summary>
        /// a comma separated list of virtual directory and
	/// real directory for all the applications we want to manage
	/// with this server. The virtual and real dirs. are separated
	/// by a colon. Optionally you may specify virtual host name
	/// and a port.
        /// </summary>
        public string Apps { get; set; }
 
        /// <summary>
        /// adds application definitions from all XML files
        /// found in the specified directory DIR. Files must have
	/// .webapp' extension
        /// </summary>
        public string AppConfigDir { get; set; }
 
        /// <summary>
        /// adds application definitions from the XML
	/// configuration file. See sample configuration file that");
        /// comes with the server.
        /// </summary>
        public string AppConfigFile { get; set; }
 
        /// <summary>
        /// the server changes to this directory before anything else.
        /// </summary>
        public string RootDir { get; set; }
 
        /// <summary>
        /// Gets and sets the port number in which the server will be ran.
        /// </summary>
        public int Port { get; set; }
 
        /// <summary>
        /// Gets and sets the ip of the server.
        /// </summary>
        public string Ip { get; set; }
 
        /// <summary>
        /// Gets and sets if the server should be stop when there is an exception.
        /// </summary>
        public bool NonStop { get; set; }
 
        /// <summary>
        /// Gets and sets if we want the server to be verbose.
        /// </summary>
        public bool Verbose { get; set; }
 
        /// <summary>
        /// The exception which stop the server.
        /// </summary>
        public Exception Exception { get; set; }
 
        #endregion
 
        /// <summary>
        /// Creates a new webserver that can be used for testing purposes.
        /// </summary>
        public TestWebserver()
        {
            // set the basic settings
            Port = 8080;
            Ip = "0.0.0.0";
        }
 
        public void Start()
        {
            var ipaddr = IPAddress.Parse(Ip);
 
            if (!String.IsNullOrEmpty(RootDir))
            {
                Environment.CurrentDirectory = RootDir;
            }
 
            RootDir = Directory.GetCurrentDirectory();
 
            WebSource webSource = new XSPWebSource(ipaddr, Port, false);
 
            _server = new ApplicationServer(webSource)
            {
                Verbose = Verbose,
                SingleApplication = false
            };
 
            if (Apps != null)
                _server.AddApplicationsFromCommandLine(Apps);
 
            if (AppConfigFile != null)
                // allows to load the config from a file
                _server.AddApplicationsFromConfigFile(AppConfigFile);
 
            if (AppConfigDir != null)
                // add config from a dir
                _server.AddApplicationsFromConfigDirectory(AppConfigDir);
 
            if (Apps == null && AppConfigDir == null && AppConfigFile == null)
                _server.AddApplicationsFromCommandLine("/:.");
 
            var vh = _server.GetSingleApp();
            if (vh != null)
            {
                // Redo in new domain
                vh.CreateHost(_server, webSource);
                var svr = (TestWebserver)vh.AppHost.Domain.CreateInstanceAndUnwrap(
                    GetType().Assembly.GetName().ToString(), GetType().FullName);
                webSource.Dispose();
                svr.Start();
            }
 
            if (_server.Start(!NonStop, Exception) == false)
                return;
        }
 
        /// <summary>
        /// Allows to stop the server if it is currently running.
        /// </summary>
        public void Stop()
        {
            _server.Stop();
        }
    }
}

The above class allows to start an XSP server that will host a webpage that you can use for your unit tests. This code was inspired by the XSP implementation from mono and adapted so that is will be a class that could be easily instantiated for unit testing and that can be distributes since it just depends on Mono.WebServer2.dll which has the MIT license, I hope it helps you :D

Read more