Restart ASP.NET apps Programmatically, Log Application_End Events, and use Web Management logging

Shows how to instrument your ASP.NET application for custom restarting of the app, logging exceptions and using the built-in ASP.NET 2.0 Management Events and Auditing providers.

Recently I put a new web site online at a new hosting company, and one of the first things I noticed was that the application was restarting way too often, and taking a very long time to restart as well. This is not a large application, and the initialization that I do when the app starts should only take about 1/2 of a second, so I knew there was either something wrong with my code somewhere that was being affected only when the app ran in production on the shared hosting server, or that there was something wrong with the hosting company's setup and maybe I should find a better hosting company. The app ran fine on my local development machine, and I had done some load testing already that indicated the app was fast and could handle a pretty significant load. It was one of those "It runs great on my machine!" cases.

But -- and I emphasize the "But" - in order to really get a good determination of exactly what was happening, how, and when, I would need some pretty good metrics. So I put on my detective hat and started working on some code.

First, I'd need a good way to programmatically restart my app "on demand" so that I would be able to observe whatever happened with my logging metrics and know that it was I that caused this particular restart.

Second, I'd need a good way to get as much information as possible when the app stopped, as well as when it restarted.

Finally, I wanted to be able to wire in the new System.Web.Management eventing system and be able to audit various events in my app, and I'd need a basic report page that would allow me to view either my own logging or the Management audits events, and to be able to clear out the SQL Server tables after some tests.

 

The first situation, being able to restart your ASP.NET app on-demand, I solved in two different ways. You've probably seen code that uses the Process class to kill the ASP.NET worker process. That's a real brute - force approach; sure you'll get a new worker process spun up, but you've sidestepped all the normal shutdown cleanup and there can be some ugly side effects from doing this.

There are only two "elegant" ways that I know to tell an ASP.NET application to restart:

1. Touch the web.config file (add some space, or change the LastModified datetime of the file). The normal ASP.NET FileSystemWatcher mechanism will pick this up and restart the app. This can actually be done via a classic ASP page, and a side benefit of that would be that if the ASP.NET runtime is unresponsive, there is still a good chance that an ASP page will respond since it does not get processed through the ASP.NET ISAPI dll, rather through the ASP.DLL which does not run under ASP.NET at all. So here's some sample code for this:

<%
Sub Touch(FolderPath, FileName, NewDate)
Set app = Server.CreateObject("Shell.Application")
Set folder = app.NameSpace(FolderPath)
Set file = folder.ParseName(FileName)
file.ModifyDate = NewDate
set file = nothing
set folder = nothing
set app = nothing
End Sub
Call Touch(Server.MapPath("/"), "web.config", now)
Response.Write "Restarted..."
%>
<html>
<body>
Put this page in your robots.txt file with "Disallow", or you will get bots restarting your app on you!
</body>
</html>

2. Tell the AppDomain to unload, which it will do quite gracefully, and then request a page:

private void Restart()
{

System.Web.HttpRuntime.UnloadAppDomain();
WebClient myWebClient=null;
try
{
string url= "http://localhost/AppRestart/Default.aspx";
myWebClient = new WebClient();
byte[] stuff = myWebClient.DownloadData(url);
}
catch
{
}
finally
{
myWebClient.Dispose();
}
}

The above code can be easily hooked up to a button-click event, and you will find that once you get the proper logging code in place, all your normal shutdown events and cleanup are preserved. That's the "nice way".

Now let's move on to how to log the shutdown of the app. This uses some really cool reflection code from one of Scott Guthrie's team, that Scott posted on his blog. In order to show this, I'll provide the entire code for my Global.asax codebehind class, as I'll be referring to this more in a bit:

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.SessionState;

using System.Reflection;

using System.Diagnostics;

using PAB.ExceptionHandler;

using System.Net;

 

namespace AppRestart

{

    public class Global : System.Web.HttpApplication

    {

        public static string siteUrl = ConfigurationManager.AppSettings["siteUrl"];

        protected void Application_Start(object sender, EventArgs e)

        {

       Exception ex = new Exception("App started at " + DateTime.Now.ToString());           

            ExceptionLogger.HandleException(ex);

 

        }

 

        protected void Application_Error(object sender, EventArgs e)

        {

    PAB.ExceptionHandler.ExceptionLogger.HandleException(Server.GetLastError().GetBaseException());

            Server.ClearError();

        }

 

        protected void Application_End(object sender, EventArgs e)

        {

            HttpRuntime runtime =

                (HttpRuntime)typeof(System.Web.HttpRuntime).InvokeMember("_theRuntime",

                                BindingFlags.NonPublic

                                | BindingFlags.Static

                                | BindingFlags.GetField,

                                null,

                                null,

                                null);

 

            if (runtime == null)

                return;

 

            string shutDownMessage =

                (string)runtime.GetType().InvokeMember("_shutDownMessage",

                                BindingFlags.NonPublic

                                | BindingFlags.Instance

                                | BindingFlags.GetField,

                                null,

                                runtime,

                                null);

 

            string shutDownStack =

                (string)runtime.GetType().InvokeMember("_shutDownStack",

                               BindingFlags.NonPublic

                               | BindingFlags.Instance

                               | BindingFlags.GetField,

                               null,

                               runtime,

                               null);

            Exception ex = new Exception(shutDownMessage + ": " + shutDownStack);

            ExceptionLogger.HandleException(ex);  

        }       

    }

}

You can see Scott's code in the Application_End EventHandler. Now for this, I already had an ExceptionLogger class that does a very nice job of logging exceptions to SQL Server, sending out optional Syslog messages, and optional emails as well, each containing a link with the EventId (a guid) to my Report page to view the exact entry. So I simply use an instance of the Exception class to "carry" the information, and send it into my ExceptionLogger with " ExceptionLogger.HandleException(ex);" . You can see also that I have the Application_Start and Application_Error wired up to use my logging class as well. There is no particular cost involved in using an exception to carry logging information; exceptions are only expensive when they are thrown, as in Application_Error. Since my logging class already expects exception instances, there was certainly no reason to add new code. In addition, .NET 2.0 has enhanced the Exception class with a new Data field where you can store as much additional name=value pairs of data that you want.

Incidentially, I needed to revise my original ExceptionLogger class since when an AppDomain shuts down, there is no HttpContext to extract data from. So my original code needed to have additional checks for null and try/catch blocks to prevent the exception log class itself from throwing an exception and therefore being unable to send the info into the database! Erm... having an exception logging class that throws exceptions isn't my idea of robust code! The revised code is included in the download for this article.

Now let's move on to the System.Web.Management auditing and events code.

ASP.NET 2.0 has a rich Provider model that includes a whole bunch of very cool Event and Audit logging classes built in, and you can use the base Provider classes just as you would with Membership, Roles, and Profile to roll your own custom event providers. One of the providers is the SqlWebEventProvider, which as one might guess, sends your events into a Sql Server Table. You install this feature into your database with code like this:

string connectionString = ConfigurationManager.ConnectionStrings["test"].ConnectionString;
System.Web.Management.SqlServices.Install("TEST",
            System.Web.Management.SqlFeatures.SqlWebEventProvider,connectionString);

You can then wire up the provider in your web.config like so:

<system.web>

  <healthMonitoring

  enabled="true"

    heartbeatInterval="0">

        <bufferModes>

          <remove name="Analysis"/>

          <add name="Analysis"

          maxBufferSize="10"

          maxFlushSize="2"

          urgentFlushThreshold="2"

          regularFlushInterval="00:00:02"

          urgentFlushInterval="00:00:01"

          maxBufferThreads="1"

    />

        </bufferModes>

        <providers>

          <remove name ="SqlWebEventProvider"/>

   <add name="SqlWebEventProvider"  type="System.Web.Management.SqlWebEventProvider,
       System.Web,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a
"

          connectionStringName="test"

          maxEventDetailsLength="1073741823"

          buffer="true"

          bufferMode="Analysis"

    />

        </providers>

        <eventMappings>

          <remove name ="All Audits"/>

          <add name="All Audits"

          type="System.Web.Management.WebAuditEvent, System.Web,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a"

    />

        </eventMappings>

        <profiles>

          <remove name="Default"/>

          <add name="Default"

          minInstances="1"

          maxLimit="Infinite"

          minInterval="00:10:00"

    />

        </profiles>

        <rules>

          <add name="All Events Default"

          eventName="All Events"

          provider="SqlWebEventProvider"

          profile="Default"

          minInterval="00:00:01" minInstances="1"

    />

        </rules>

 </healthMonitoring>

The above has some defaults and I've trimmed the timings down for testing purposes. There are a lot of options, so if you want to use this in a production app, I suggest you study the material on MSDN that covers it. This is built into the sample app.

At this point, with everything that I've described above, along with my sample app and included ExceptionLogger code, you have everything you need to do a really professional investigative job on your app and what's making it sick or well.

There is a SQL Script included in the sample app that will install my LogItems table and stored procs, and you'll need to set up your web.config connection strings to match your machine, as well as the System.Net.Mail elements to match your SMTP setup so mail can be sent out. Most everything in the web.config is self-describing, so most developers should have little trouble setting this up and testing it out in preparation for any customization that you want to do on it. The report page has form elements and buttons to search for exception log items, view Provider events, and also to truncate either table, as well as test buttons to restart your app and even generate a custom exeption on demand.

You can download the Visual Studio 2005 Solution here.

Oh, by the way - my tests showed that my tabstrip menu control, which uses a file-based cache dependency, was not playing well with the hosting company's rather unusual clustered-server arrangement. As soon as I removed the file dependency, everything was happy. Moral of the story? Instrument your apps -- you'll thank yourself later!

By Peter Bromberg   Popularity  (13522 Views)