views:

572

answers:

9

I work at a college and have been developing an ASP.NET site with many, many reports about students, attendance stats... The basis for the data is a ms sql server db which is the back end to our student management system. This has a regular maintenance period on thursday mornings for an unknow length of time (dependent on what has to be done).

Most of the staff are aware of this but the less regular users seem to be forever ringing me up. What is the easiest way to disable the site during maintenance obviously I can just try a db query to test if it is up but am unsure of the best way to for instance redirect all users to a "The website is down for maintenance" message, bearing in mind they could have started a session prior to the website going down.

Hopefully something can be implemented globally rather than per page.

+11  A: 

Drop an html file called "app_offline.htm" into the root of your virtual directory. Simple as that.

Scott Guthrie on the subject and friendly errors.

Will
+1  A: 

You could display a message to people who have logged in saying "the site will be down for maintenance in xxx minutes" then run a service to log everyone out after xxx minutes. Then set a flag somewhere that every page can access, and at the top of every page(or just the template page) you test if that flag is set, if it is, send a redirect header to a site is down for maintenance page.

Charles Ma
+1  A: 

The "offline.html" page won't work if the user was already navigating within the site, or if he's accessing the site from a bookmark/external link to a specific page.

The solution I use is to create a second web site with the same address (ip or host header(s)), but have it disabled by default. When the website is down, a script deactivates the "real" web site and enables the "maintenance" website instead. When it comes back online, another scripts switches back to the "real" web site.

The "maintenance" web site is located in a different root directory, with a single page with the message (and any required images/css files)

To have the same message shown on any page, the "maintenance" web site is setup with a 404 error handler that will redirect any request to the same "website is down for maintenance" page.

ckarras
Actually the app_offline page will get evaluated upon every page request. So if they are already in the site, next page request will send them to app_offline page. If they deep link in from an external site they'll also see the app_offline page.
Sean Gough
Actually, when I posted that, I said "offline.html", so at the time ckarras was absolutely right. My bad.
Will
+1  A: 

What happens now when the site is down and someone tries to hit it? Does ADO.NET throw a specific exception you could catch and then redirect to the "website down" page?

You could add a "Global.asax" file to the project, and in its code-behind add an "Application_Error" event handler. It would fire whenever an exception is thrown and goes uncaught, from anywhere in your web app. For example, in C#:

protected void Application_Error(object sender, EventArgs e)
{
    Exception e = Server.GetLastError().GetBaseException();
    if(e is SqlException)
    {    
     Server.ClearError();
     Server.Transfer("~/offline.aspx");
    }
}

You could also check the Number property on the exception, though I'm not sure which number(s) would indicate it was unable to connect to the database server. You could test this while it's down, find the SQL error number and look it up online to see if it's specifically what you really want to be checking for.

EDIT: I see what you're saying, petebob.

Matt Blaine
A: 

Thanks for the replies so far I should point out i'm not the one that does the maintenance nor do I have access all the time to IIS. Also I prefer options where I do nothing as like all programmers I am a bit lazy.

I know one way is to check a flag on every page but i'm hoping to avoid it. Could I not do something with the global.asax page infact I think posting has engaged my brain:

Think I could put in Application_BeginRequest a bit of code to check the sql state then redirect:

HttpContext context = HttpContext.Current; if (!isOnline()) { context.Response.ClearContent(); context.Response.Write("" + "top.location='" + Request.ApplicationPath + "/public/Offline.aspx';"); }

Or something like that may not be perfect not tested yet as i'm not at work. Comments appreciated.

PeteT
A: 

Above posted before I saw Matt Blaine's reply I am thinking along the same lines as you. See my post I think for my particular site Application_BeginRequest is the only place it would work this is because it uses crystal reports on much of it for report generating meaning the sql errors are actually within it's error handling not mine.

PeteT
A: 

I would suggest doing it in Application_PreRequestHandlerExecute instead of after an error occurs. Generally, it'd be best not to enter normal processing if you know your database isn't available. I typically use something like below

void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
 string sPage = Request.ServerVariables["SCRIPT_NAME"];
 if (!sPage.EndsWith("Maintenance.aspx", StringComparison.OrdinalIgnoreCase))
 {
  //test the database connection
  //if it fails then redirect the user to Maintenance.aspx
  string connStr = ConfigurationManager.ConnectionString["ConnectionString"].ConnectionString;
  SqlConnection conn = new SqlConnection(connStr);
  try
  {
   conn.Open();
  }
  catch(Exception ex)
  {
   Session["DBException"] = ex;
   Response.Redirect("Maintenance.aspx");
  }
  finally
  {
   conn.Close();
  }
 }
}
James
A: 

A slightly more elegant version of the DB check on every page would be to do the check in the Global.asax file or to create a master page that all the other pages inherit from.

The suggestion of having an online site and an offline site is really good, but only really applicable if you have a limited number of sites to manage on the server.

EDIT: Damn, the other answers with these suggestions came up after I loaded the page. I need to remember to refresh before replying :)

Dr8k
A: 

James code forgets to close the connection, should probably be:

try
{
   conn.Open();
}
catch(Exception ex)
{
   Session["DBException"] = ex;
   Response.Redirect("Maintenance.aspx");
}
finally
{
   conn.Close();
}
Dr8k