Click here to Skip to main content
Click here to Skip to main content
Go to top

A general purpose Visual Studio 2008 Development Server (WebDev.WebServer.exe) test fixture

, 18 Apr 2010
Rate this:
Please Sign up or sign in to vote.
Use Visual Studio 2008 Development Server (WebDev.WebServer.exe) in automated testing frameworks.

UPDATE: This post is valid as reference material but the code presented has been redesigned and is available here


While I generally use CassiniDev for smoke testing, sometimes a quick and simple way to run some tests against a site using the built in server comes in handy.

Spinning up an instance of WebDev.WebServer.exe is fairly simple.

The executable can be found at C:\Program Files\Common Files\Microsoft Shared\DevServer\9.0\WebDev.WebServer.exe (Program Files (x86) for x64 Windows).

If you invoke the .exe with no arguments, you will see the usage document listed below.

Listing 1: WebDev.WebServer Usage

---------------------------
ASP.NET Development Server
---------------------------
ASP.NET Development Server Usage:
 WebDev.WebServer /port:<port number> /path:<physical path> [/vpath:<virtual path>]

	port number:
		[Optional] An unused port number between 1 and 65535.
		The default is 80 (usable if you do not also have 
				IIS listening on the same port).

	physical path:
		A valid directory name where the Web application is rooted.

	virtual path:
		[Optional] The virtual path or application root 
				in the form of '/<app name>'.
		The default is simply '/'.

Example:
WebDev.WebServer /port:8080 /path:"c:\inetpub\wwwroot\MyApp" /vpath:"/MyApp"

You can then access the Web application using a URL of the form:
	http://localhost:8080/MyApp

With this information in hand, you can spin up an instance of webdev from within a test fixture.

The problem you will find is that if an instance of webdev is already running on the specified port, a program crash dialog will be presented. The existing instance is still available to serve requests to your tests but the crash dialog is a serious usability issue that should be dealt with.

A suitable strategy for ensuring an existing instance is not already running on the desired port is to query the Windows Management API (WMI) and return the command line that started each instance, comparing it to the command line arguments that you would like to use in spinning up a new instance.  If an instance is already running, there is no need to start another.

So, with these concerns managed, the final requirements seem to be:

  • Determine location of WebDev.WebServer.exe.
  • Provide the port on which to serve the site.
  • Provide the virtual directory in which to root the site.
  • Provide a means of determining if an instance of WebDev.WebServer.exe is already running on the desired port.
  • Provide a means of starting, if necessary, an instance of WebDev.WebServer.exe at the physical path of the site to serve..

With these requirements in mind, I have constructed an abstract class, WebDevFixture.cs, that is suitable for use with any testing framework.

This class encapsulates all requirements listed.

The minimum usage requirement is to call StartServer with an absolute or relative path pointing to the directory of the site to be served.

The default Port and VDir can be changed by either overriding the readonly properties in the derived class or adding an appSettings entry in an optional app.config.

As a convenience, the NormalizeUri method will accept a relative URL fragment and return an absolute URI.

Listing 2 provides an example of an NUnit TestFixture derived from WebDevFixture using default values.

Listing 3 provides an example using overriden properties to supply the Port and VDir.

Listing 2: Usage with NUnit - Default Port and VDir:

using System;
using System.Net;
using NUnit.Framework;

namespace Salient.Web.HttpLib.Tests
{
    [TestFixture]
    public class NUnitWebDevFixture : WebDevFixture
    {
        [TestFixtureSetUp]
        public void TestFixtureSetUp()
        {
            // default WebDevFixture port = 9999
            // default WebDevFixture vdir = '/'
            // thus the site under test will be found at http://localhost:9999/

            // the path to the website under test can be absolute or relative

            StartServer(@"..\..\..\Salient.Web.HttpLib.TestSite");

        }

        [Test, Description("Simple Get with WebClient")]
        public void Get()
        {
            WebClient client = new WebClient();
            Uri uri = NormalizeUri("default.aspx"); // http://localhost:9999/default.aspx
            
            string actual = client.DownloadString(uri);
            Assert.IsTrue(actual.IndexOf("This is Default.aspx") > -1);
        }
    }
}

Listing 3: Usage with NUnit - Specific Port and VDir:

using System;
using System.Net;
using NUnit.Framework;

namespace Salient.Web.HttpLib.Tests
{
    [TestFixture]
    public class NUnitWebDevFixture2 : WebDevFixture
    {

        protected override int Port
        {
            get { return 12345; }
        }
        protected override string VDir
        {
            get { return "/myapp"; }
        }

        [TestFixtureSetUp]
        public void TestFixtureSetUp()
        {

            // the site under test will be found at http://localhost:12345/myapp
            StartServer(@"..\..\..\Salient.Web.HttpLib.TestSite");

        }

        [Test, Description("Simple Get with WebClient")]
        public void Get()
        {
            WebClient client = new WebClient();

            // http://localhost:12345/myapp/default.aspx
            Uri uri = NormalizeUri("default.aspx"); 		

            string actual = client.DownloadString(uri);
            Assert.IsTrue(actual.IndexOf("This is Default.aspx") > -1);
        }
    }
}

 

Listing 4: WebDevFixture.cs

// /*!
//  * Project: Salient.Web.HttpLib
//  * http://salient.codeplex.com
//  * Date: April 11 2010 
//  */

#region

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Management;

#endregion

namespace Salient.Web.HttpLib
{
    /// <span class="code-SummaryComment"><summary>
</span>    /// A general purpose Visual Studio 2008 Development Server 
    /// (WebDev.WebServer.exe) test fixture
    /// <span class="code-SummaryComment"></summary>
</span>    public abstract class WebDevFixture
    {
        private const string WebDevPathx64 =
            @"C:\Program Files (x86)\Common Files\
		Microsoft Shared\DevServer\9.0\WebDev.WebServer.exe";

        private const string WebDevPathx86 =
            @"C:\Program Files\Common Files\
		Microsoft Shared\DevServer\9.0\WebDev.WebServer.exe";


        private readonly int _port = 9999;
        private readonly string _vdir = "/";

        protected WebDevFixture()
        {
            // set testing port - default 9999 - 
            // can be changed by adding an appSettings 'port'
            // note: appSetting is optional, will just default to 9999
            int tmpPort;
            _port = int.TryParse(ConfigurationManager.AppSettings["port"], 
			out tmpPort) ? tmpPort : _port;

            // set vdir - default '/' - can be changed by adding an appSettings 'vdir'
            // note: appSetting is optional, will just default to '/'

            _vdir = ConfigurationManager.AppSettings["vdir"] ?? "/";

            // do some munging to ensure that the vdir is going to work out for us
            _vdir = string.Format(CultureInfo.InvariantCulture, 
			"/{0}/", _vdir.Trim('/')).Replace("//", "/");
        }

        // these can optionally be overriden in app.config 
        // using similarly named appSettings. e.g. 'port' and 'vdir'
        // or overridden by derived class
        protected virtual int Port
        {
            get { return _port; }
        }

        protected virtual string VDir
        {
            get { return _vdir; }
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Returns an absolute Uri rooted on the running server.
        /// e.g. NormalizeUrl("default.aspx") will return 
        /// 'http://localhost:9999/default.aspx'
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="pathAndQuery">path and query relative to app vdir</param>
</span>        /// <span class="code-SummaryComment"><returns></returns>
</span>        protected Uri NormalizeUri(string pathAndQuery)
        {
            // do some munging to ensure that the vdir is going to work out for us
            string vdir = string.Format(CultureInfo.InvariantCulture, 
		"/{0}/", VDir.Trim('/')).Replace("//", "/");
            return
                new Uri(string.Format(CultureInfo.InvariantCulture, 
		"http://localhost:{0}{1}{2}", Port, vdir, pathAndQuery));
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Ensure that an instance of WebDev.WebServer.exe is 
        /// running and serving the target site. 
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="sitePath">path of the website to serve, 
</span>        /// can be absolute or relative<span class="code-SummaryComment"></param>
</span>        protected void StartServer(string sitePath)
        {
            sitePath = Path.GetFullPath(sitePath);

            // try x86 first
            string webDevPath = WebDevPathx86;
            if (!File.Exists(webDevPath))
            {
                // then x64
                webDevPath = WebDevPathx64;
                if (!File.Exists(webDevPath))
                {
                    throw new FileNotFoundException("Cannot find WebDev.WebServer.exe");
                }
            }

            string devServerCmdLine = String.Format(CultureInfo.InvariantCulture,
                          "/port:{0} /path:\"{1}\" /vpath:\"{2}\"", Port, sitePath,
                          VDir);

            // check to see if we already have a server running
            bool running = false;
            foreach (string cmdLine in GetCommandLines("WebDev.WebServer.exe"))
            {
                if (cmdLine.EndsWith
		(devServerCmdLine, StringComparison.OrdinalIgnoreCase))
                {
                    running = true;
                    break;
                }
            }

            if (!running)
            {
                Process.Start(webDevPath, devServerCmdLine);
            }
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Using WMI to fetch the command line that started all instances of a process
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="processName">Image name, e.g. WebDev.WebServer.exe</param>
</span>        /// <span class="code-SummaryComment"><returns></returns>
</span>        /// adapted from http://stackoverflow.com/questions/504208/
        /// how-to-read-command-line-arguments-of-another-process-in-c/504378%23504378
        /// original code by http://stackoverflow.com/users/61396/xcud
        private static IEnumerable<string> GetCommandLines(string processName)
        {
            List<string> results = new List<string>();

            string wmiQuery = string.Format(CultureInfo.InvariantCulture,
                 "select CommandLine from Win32_Process where Name='{0}'", processName);

            using (ManagementObjectSearcher searcher = 
			new ManagementObjectSearcher(wmiQuery))
            {
                using (ManagementObjectCollection retObjectCollection = searcher.Get())
                {
                    foreach (ManagementObject retObject in retObjectCollection)
                    {
                        results.Add((string) retObject["CommandLine"]);
                    }
                }
            }
            return results;
        }
    }
}

 

A complete solution utilizing this fixture can be found @ http://www.codeproject.com/KB/aspnet/Salient-Web-HttpLib-Intro.aspx 

As always, you can get the latest source and tests @ http://salient.codeplex.com


Technorati tags: ,

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sky Sanders
Software Developer (Senior) Salient Solutions
United States United States
My name is Sky Sanders and I am an end-to-end, front-to-back software solutions architect with more than 20 years experience in IT infrastructure and software development, the last 10 years being focused primarily on the Microsoft .NET platform.
 
My motto is 'I solve problems.' and I am currently available for hire.
 
I can be contacted at sky.sanders@gmail.com

Comments and Discussions

 
GeneralLooks good! PinmentorSandeep Mewara13-Apr-10 20:01 
Nice... Thumbs Up | :thumbsup:

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140905.1 | Last Updated 18 Apr 2010
Article Copyright 2010 by Sky Sanders
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid