ASP.NET Maintenance Scheduling Made Easy

Any time I have code I need to release for an ASP.NET website that I know is going to cause a recompile (like anything in app_code, any new binaries, master pages, user controls, etc.) I try to avoid doing it when I know there are a lot of users on the site. The best way to ensure that nobody will be using your website is to add scheduled maintenance support to your site. It’s very easy to do and it can save you and your users a lot of headache while you make updates.

Deriving from System.Web.UI.Page 
A quick and dirty way to get a maintenance window up for your site is to write a base page (derived from System.Web.UI.Page) that all of your pages derive from. That way you can specify things to happen on multiple pages from one central location. So let’s say I have BasePage.cs in my App_Code folder. I can override the OnPreInit() method to check to see whether I’m supposed to be in maintenance mode or not. If the site is supposed to be “down” I will redirect the user to a page that will say the site is down for maintenance. For now I will just be checking a flag I’ve set in app settings.

protected override void OnPreInit(EventArgs e) {
    base.OnPreInit(e);
    if (ConfigurationManager.AppSettings["MaintenanceMode"].ToUpper() == "TRUE") {
        if (Request.Url.AbsolutePath.ToLower() != "/maintenance.aspx") {
            Response.Redirect("Maintenance.aspx");
        }
    }
}

Using an HttpModule

An alternate way to add code that checks for maintenance (and is less work if you have medium-to-large sized site OR multiple sites that need to use the same maintenance code) is to place it in an HttpModule and add the module to web.config. I personally think the HttpModule is perfectly suited to solving this problem. Checking for maintenance is typically something that is separate from most other things, so it makes sense to modularize it as much as possible. It’s just good form! Here’s my module code:

public class MaintenanceModule : IHttpModule { #region IHttpModule Members public void Dispose() { } public void Init(HttpApplication context) { context.BeginRequest +=new EventHandler(context_BeginRequest); } public void context_BeginRequest(object sender, EventArgs e) {
        if (ConfigurationManager.AppSettings["MaintenanceMode"] == "TRUE") {
            if (Request.Url.AbsolutePath.ToLower() != "/maintenance.aspx") {
                Response.Redirect("Maintenance.aspx");
}
} } #endregion }

And here is my web.config entry (added to the httpModules section in system.web for local use; put it in the modules section of system.webserver for IIS to pick it up):

<add name="MaintenanceModule" type="MaintenanceModule" />

Scheduling Maintenance Ahead of Time

I like to give my users several days’ notice before I bring a site down for maintenance, so I do two things to accomplish that. First, I keep a table called MaintenanceSchedule that I can put a start time, end time, public message, and private message into. Then, on pages where I want to notify users of the upcoming maintenance, I check the table for maintenance periods that haven’t yet begun.

string connectionString =
    ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;

using (SqlConnection con = new SqlConnection(connectionString)) {
    con.Open();
    using (SqlCommand com = con.CreateCommand()) {
        com.CommandText = "GetNextMaintenancePeriod";
        com.CommandType = CommandType.StoredProcedure;

        using (SqlDataReader sdr = com.ExecuteReader()) {
            if (sdr.Read()) {
                DateTime start = sdr.GetDateTime(1);
                DateTime end = sdr.GetDateTime(2);

                litUpcomingMaintenance.Text =
                    "<strong>Alert!</strong> "
                    + "This site will be going down for maintenance from "
                    + start.ToString() + " until " + end.ToString() + ".";
            }
            else {
                // throw an exception to show the default message.
                throw new Exception("No maintenance period found...");
            }
        }
    }
}

This method also allows me to put a more specific message out when the site is actually being worked on as well as a way to keep a record of when past maintenance periods occurred.

string connectionString = 
    ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;

try {
    using (SqlConnection con = new SqlConnection(connectionString)) {
        con.Open();
        using (SqlCommand com = con.CreateCommand()) {
            com.CommandText = "GetCurrentMaintenancePeriod";
            com.CommandType = CommandType.StoredProcedure;

            using (SqlDataReader sdr = com.ExecuteReader()) {
                if (sdr.Read()) {
                    DateTime start = sdr.GetDateTime(1);
                    DateTime end = sdr.GetDateTime(2);
                    string message = sdr.GetString(3);

                    litMaintenance.Text = 
                        "This site is currently undergoing maintenance. "
                        + "Maintenance is expected to start at " 
                        + start.ToString() + " and end at " + end.ToString() 
                        + ".<br/><br/>" + message;
                }
                else {
                    // throw an exception to show the default message.
                    throw new Exception("No maintenance period found...");
                }
            }
        }
    }
}
catch (Exception ex) {
    // This is in case my database goes down during the maintenance period.
    litMaintenance.Text = "This site is currently undergoing maintenance." 
        + " Please try again later.";
}

I’ll also alter my base page to automatically take the site down when maintenance is scheduled to begin.

string connectionString = 
    ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;

if (Request.Url.AbsolutePath.ToLower() != "/maintenance.aspx") {
    using (SqlConnection con = new SqlConnection(connectionString)) {
        con.Open();
        using (SqlCommand com = con.CreateCommand()) {
            com.CommandText = "GetCurrentMaintenancePeriod";
            com.CommandType = CommandType.StoredProcedure;

            using (SqlDataReader sdr = com.ExecuteReader()) {
                if (sdr.Read()) {
                    Response.Redirect("Maintenance.aspx");
                }
            }
        }
    }
}

Code Sample

I’ve included a sample website with a SQL script to show how all the pieces fit together. Just download the files, run the SQL against your database, open the website (I used Microsoft Visual Web Developer 2008 Express Edition), and put your connection string into the web.config. Then, add some rows into the MaintenanceSchedule table and run the site to see how it all works.

Download Sample Code (.zip)

Leave a Reply