代码之家  ›  专栏  ›  技术社区  ›  Rasmus Faber

卡西尼号/Web服务器.WebDev,NUnit和AppDomainUnloadedException

  •  9
  • Rasmus Faber  · 技术社区  · 16 年前

    我用的是卡西尼号/Web服务器.WebDev使用NUnit运行web服务的一些自动化测试。

    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
        }
      }
    }
    

    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.
    

    如果在调试器下运行nunit 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).
    

    有人知道这是什么原因吗?

    1 回复  |  直到 16 年前
        1
  •  7
  •   GBegen    16 年前

    我也有同样的问题,但没有使用卡西尼号。相反,我有自己的基于 System.Net.HttpListener System.Web.HttpRuntime 在通过创建的其他应用程序域中运行 System.Web.Hosting.ApplicationHost.CreateApplicationHost() . 这基本上就是Cassini的工作方式,只是Cassini在套接字层工作,并实现了 它自己。

    不管怎样,为了解决我的问题,我需要打电话 System.Web.HttpRuntime.Close() Close() 方法,该方法由[SetupFixture]类的[TearDown]方法调用,并且该方法调用 System.Web.HttpRuntime.Close()

    我通过.Net Reflector查看了Cassini的实现,尽管它使用 System.Web.HttpRuntime.ProcessRequest() ,它似乎没有呼叫 任何地方。

    我不确定你怎么能继续使用预先构建的Cassini实现( Microsoft.VisualStudio.WebHost.Server ),因为你需要 System.Web.HttpRuntime.Close() 在Cassini创建的应用程序域中调用主机ASP.Net.

    以下是我使用嵌入式web托管进行的工作单元测试的一些片段,以供参考。

    WebServerHost 类是一个非常小的类,它允许将请求封送到由创建的应用程序域中 系统.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));
        }
    }
    

    WebServerContext 类只是 System.Net.HttpListenerContext

    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;
    }
    

    这个 WebServerRequest System.Web.HttpWorkerRequest 类从ASP.Net通过托管域 Web服务器上下文

    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;
    }
    

    这个 WebServer 类是用于启动和停止web服务器的控制器。启动时ASP.Net托管域是用我的 System.Net.HttpListener 实例也会启动,并启动一个单独的线程来接受连接。当建立连接时,线程池中会启动一个工作线程,再次通过我的 班级。最后,当web服务器停止时,侦听器停止,控制器等待接受连接的线程退出,然后侦听器关闭。 最后,HTTP运行时也通过调用 WebServerHost.Close()

    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));
        }
    }
    

    最后,这个 Initialization 类,用NUnit[SetupFixture]属性标记,用于在单元测试启动时启动web服务器,并在单元测试完成时关闭它。

    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();
        }
    }
    

    我知道这不完全是回答问题,但我希望你觉得信息有用。

    推荐文章