I am using Cassini/WebServer.WebDev to run some automated tests of a WebService using NUnit.
I am not doing anything fancy, just
public class WebService{
Microsoft.VisualStudio.WebHost.Server _server;
public void Start(){
_server = new Microsoft.VisualStudio.WebHost.Server(_port, "/", _physicalPath);
}
public void Dispose()
{
if (_server != null)
{
_server.Stop();
_server = null;
}
}
}
[TestFixture]
public void TestFixture{
[Test]
public void Test(){
using(WebService webService = new WebService()){
webService.Start();
// actual test invoking the webservice
}
}
}
, but when I run it using nunit-console.exe, I get the following output:
NUnit version 2.5.0.9015 (Beta-2)
Copyright (C) 2002-2008 Charlie Poole.\r\nCopyright (C) 2002-2004 James W. Newki
rk, Michael C. Two, Alexei A. Vorontsov.\r\nCopyright (C) 2000-2002 Philip Craig
.\r\nAll Rights Reserved.
Runtime Environment -
OS Version: Microsoft Windows NT 6.0.6001 Service Pack 1
CLR Version: 2.0.50727.1434 ( Net 2.0.50727.1434 )
ProcessModel: Default DomainUsage: Default
Execution Runtime: net-2.0.50727.1434
.....
Tests run: 5, Errors: 0, Failures: 0, Inconclusive: 0 Time: 28,4538451 seconds
Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0
Unhandled exceptions:
1) TestCase1 : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain.
2) TestCase2 : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain.
3) TestCase3 : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain.
4) TestCase4 : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain.
If I run nunit-console under the debugger, I get the following output in the debug console:
[...]
The thread 0x1974 has exited with code 0 (0x0).
############################################################################
############## S U C C E S S #################
############################################################################
Executed tests : 5
Ignored tests : 0
Failed tests : 0
Unhandled exceptions : 4
Total time : 25,7092944 seconds
############################################################################
The thread 0x1bd4 has exited with code 0 (0x0).
The thread 0x10f8 has exited with code 0 (0x0).
The thread '<No Name>' (0x1a80) has exited with code 0 (0x0).
A first chance exception of type 'System.AppDomainUnloadedException' occurred in System.Web.dll
##### Unhandled Exception while running
System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain.
at System.Web.Hosting.ApplicationManager.HostingEnvironmentShutdownComplete(String appId, IApplicationHost appHost)
at System.Web.Hosting.HostingEnvironment.OnAppDomainUnload(Object unusedObject, EventArgs unusedEventArgs)
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in System.Web.dll
The thread 0x111c has exited with code 0 (0x0).
The program '[0x1A64] nunit-console.exe: Managed' has exited with code -100 (0xffffff9c).
Do anyone have any ideas what could be causing this?
I had the same problem, but was not using Cassini. Instead, I had my own web server hosting based on System.Net.HttpListener
with ASP.Net support through System.Web.HttpRuntime
running in a different application domain created via System.Web.Hosting.ApplicationHost.CreateApplicationHost()
. This is essentially the way Cassini works, except that Cassini works at the socket layer and implements a lot of the functionality provided by System.Net.HttpListener
itself.
Anyway, to solve my problem, I needed to call System.Web.HttpRuntime.Close()
before letting NUnit unload my application domain. I did this by exposing a new Close()
method in my host proxy class that is invoked by the [TearDown] method of my [SetupFixture] class and that method calls System.Web.HttpRuntime.Close()
.
I looked at the Cassini implementation through .Net Reflector and, although it uses System.Web.HttpRuntime.ProcessRequest()
, it doesn't seem to call System.Web.HttpRuntime.Close()
anywhere.
I'm not exactly sure how you can keep using the pre-built Cassini implementation (Microsoft.VisualStudio.WebHost.Server
), as you need to get the System.Web.HttpRuntime.Close()
call to occur within the application domain created by Cassini to host ASP.Net.
For reference, here are some pieces of my working unit test with embedded web hosting.
My WebServerHost
class is a very small class that allows marshaling requests into the application domain created by System.Web.Hosting.ApplicationHost.CreateApplicationHost()
.
using System;
using System.IO;
using System.Web;
using System.Web.Hosting;
public class WebServerHost :
MarshalByRefObject
{
public void
Close()
{
HttpRuntime.Close();
}
public void
ProcessRequest(WebServerContext context)
{
HttpRuntime.ProcessRequest(new WebServerRequest(context));
}
}
The WebServerContext
class is simply a wrapper around a System.Net.HttpListenerContext
instance that derives from System.MarshalByRefObject to allow calls from the new ASP.Net hosting domain to call back into my domain.
using System;
using System.Net;
public class WebServerContext :
MarshalByRefObject
{
public
WebServerContext(HttpListenerContext context)
{
this.context = context;
}
// public methods and properties that forward to HttpListenerContext omitted
private HttpListenerContext
context;
}
The WebServerRequest
class is just an implementation of the abstract System.Web.HttpWorkerRequest
class that calls back into my domain from the ASP.Net hosting domain via the WebServerContext
class.
using System;
using System.IO;
using System.Web;
class WebServerRequest :
HttpWorkerRequest
{
public
WebServerRequest(WebServerContext context)
{
this.context = context;
}
// implementation of HttpWorkerRequest methods omitted; they all just call
// methods and properties on context
private WebServerContext
context;
}
The WebServer
class is a controller for starting and stopping the web server. When started, the ASP.Net hosting domain is created with my WebServerHost
class as a proxy to allow interaction. A System.Net.HttpListener
instance is also started and a separate thread is started to accept connections. When connections are made, a worker thread is started in the thread pool to handle the request, again via my WebServerHost
class. Finally, when the web server is stopped, the listener is stopped, the controller waits for the thread accepting connections to exit, and then the listener is closed. Finally, the HTTP runtime is also closed via a call into the WebServerHost.Close()
method.
using System;
using System.IO;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Web.Hosting;
class WebServer
{
public static void
Start()
{
lock ( typeof(WebServer) )
{
// do not start more than once
if ( listener != null )
return;
// create web server host in new AppDomain
host =
(WebServerHost)ApplicationHost.CreateApplicationHost
(
typeof(WebServerHost),
"/",
Path.GetTempPath()
);
// start up the HTTP listener
listener = new HttpListener();
listener.Prefixes.Add("http://*:8182/");
listener.Start();
acceptConnectionsThread = new Thread(acceptConnections);
acceptConnectionsThread.Start();
}
}
public static void
Stop()
{
lock ( typeof(WebServer) )
{
if ( listener == null )
return;
// stop listening; will cause HttpListenerException in thread blocked on GetContext()
listener.Stop();
// wait connection acceptance thread to exit
acceptConnectionsThread.Join();
acceptConnectionsThread = null;
// close listener
listener.Close();
listener = null;
// close host
host.Close();
host = null;
}
}
private static WebServerHost
host = null;
private static HttpListener
listener = null;
private static Thread
acceptConnectionsThread;
private static void
acceptConnections(object state)
{
while ( listener.IsListening )
{
try
{
HttpListenerContext context = listener.GetContext();
ThreadPool.QueueUserWorkItem(handleConnection, context);
}
catch ( HttpListenerException e )
{
// this exception is ignored; it will be thrown when web server is stopped and at that time
// listening will be set to false which will end the loop and the thread
}
}
}
private static void
handleConnection(object state)
{
host.ProcessRequest(new WebServerContext((HttpListenerContext)state));
}
}
Finally, this Initialization
class, marked with the NUnit [SetupFixture] attribute, is used to start the web server when the unit tests are started, and shut it down when they are completed.
using System;
using NUnit.Framework;
[SetUpFixture]
public class Initialization
{
[SetUp]
public void
Setup()
{
// start the local web server
WebServer.Start();
}
[TearDown]
public void
TearDown()
{
// stop the local web server
WebServer.Stop();
}
}
I know that this is not exactly answering the question, but I hope you find the information useful.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With