How to Take an Asp.Net MVC Web Site Down for Maintenance

Keeping a customer facing web site up and performing well is a challenge, especially when you are still adding new features. While providing an "always on" experience for users is preferred there are times when it is easier to take the site "down for maintenance" and fix those things that are just too difficult and costly to do with the site up. Jon Smith describes his solution to a controlled "down for maintenance" approach for ASP.NET MVC sites.

No one wants to take a web site offline so that users cannot access it, especially if it is generating revenue. If you are someone like Amazon.com then you have the incentive and resources to update your site without any down time. However for less revenue-sensitive web sites it can be easier and cheaper to take a site down, hopefully when no one wants to use it, fix something in a planned way and then bring it back up again.

I like to plan ahead, and so when I was put in charge of a small ASP.NET MVC5 e-commerce site then I started thinking of all the things that could go wrong and tried to work out how to mitigate those problems. Azure, our chosen cloud provider, has lots of great features to help with seamless software deployment but one issue that has been bugging me – how to apply the more ‘difficult’ database migrations to a live site.

Our e-commerce site will start quite small, and users are in Europe, so taking the site offline at 5am might be painful for me, but shouldn’t affect our customers too much. Rather than attempt changes to the live site, the plan is to take the site ‘down for maintenance’, apply the database migration and associated new software deployment, and then bring the site back up

My requirements for “down for maintenance”

To help me design the system I thought about the process I would go though. I needed a command that would allow me to temporarily prevent all users, other than myself as an admin user, from accessing the web site. This will make it much easier to apply database migrations as there are no changes happening to the database during the migrations. This is especially important for the more ‘difficult’ migrations where the changes require some of the existing data to be transformed/moved to new tables or columns, e.g. splitting one table into two tables, which means the old code won’t work anymore.

A database migration like this is always followed by an updating the software. I want the new software to obey the same ‘offline’ mode that the last software has. This allows me as admin user, or the automation system, to check the new site before bringing it back online.

From the customer point of view I want to have friendly messages so that they know what is happening. I especially don’t want someone to be half way through placing an order and then lose it because the site suddenly goes down. That is definitely a way to upset customers!

Bring all these points together then the process for taking the web site down, doing the migration, checking it and bring it back up had the following requirements:

  1. I want to warn my customers, so I put up a message announcing ‘the site will go down for maintenance in xx minutes’. This shows as a banner on all web pages.
  2. When the site is down it should:
  3. If I upload new software then that also should come up as “down for maintenance” until I, as the admin user, has checked it out and makes it back online.
  4. Finally my site is Microsoft’s Azure Cloud offering so I have the following technical requirements:
  5. I cannot log in locally, which affects how I know who is the admin user.
  6. I also want a solution that will handle scaling up on Azure, i.e. it must work if I have multiple instances of the web site running. This affects how I store the offline information.

Using an Action Filter to redirect users

Having researched how to at a site offline I found some very good examples (see Khalid Abuhakmeh‘s good post and this helpful StackOverflow question & answer by FaNIX). As Khalid Abuhakmeh and FaNIX suggested the best way to implement this is by adding a MVC Action Filter. This intercepts each action call and allows you to change what happens. Here is my Action Filter code, which is different to Abuhakmeh and FaNIX versions because my needs are slightly different:

The decision as to whether we should tell the user that the site is “down for maintenance” is done by the OfflineHelper class, which I will describe later, and its sets the ThisUserShouldBeOffline property to true. If true (see test on line 9) then we stop the normal page display and redirect them to the ” Offline.cshtml” view while also setting the StatusCode to ServiceUnavailable (503) so web crawlers won’t index the pages while offline.

This action filter needs to be run on all actions. To do this we add it to the GlobalFilters.Filters in the Global.asax.cs file, e.g.

The OfflineHelper class

I had to decide how the application would know it was in “down for maintenance” mode. In the end I decided to use the absence/presence of simple text file to control the mode. Using a file seems a bit archaic, but it fits the requirements:

  • I didn’t want to use the database, as I want the database quiescent during migrations.
  • A file would be read when new software is loaded, which would continue to be in “down for maintenance” mode until the admin user used a command to delete the file.
  • A simple file will work with Azure’s multiple instances when you scale up because all instances share the same local file system (see this helpful stackoverflow answer).
  • It allows an automated deployment script to take the site offline by writing an appropriately formatted file, and take it back online by deleting the file.
  • Plus if anything goes wrong I can use FTP to manually read, write or delete the file.

Here is the code from the OfflineHelper class

As you can see from the code the absence of the ‘ offline file.txt’ file is a simple test as to whether we should even consider being offline. If there is a file, and it hasn’t already been read in then we read it. Using this approach means we only take the performance hit of reading the file once, which is done via the OfflineFileData class (explained later).

If the offline file exists then there is a test to see if this user is allowed to access the site. If the time for the site to be offline hasn’t happened yet, or the user if coming on a specific IP (which we will see later is taken from the authorised person who set the site to go offline) then the user is let through.

As we will see later the static OfflineData property is useful for showing messages.

The OfflineFileData class

The OfflineFileData class is in charge of the offline file and showing/changing its content. The class is a bit long so I will show it as two parts: a) the reading of the file, which is done in the ctor, and b) the GoOffline and RemoveOffline commands

a) Reading the Offline File

The first part of the OfflineFileData class is shown below. I contains properties to hold what is read from the file when the ctor is called:

The code is fairly straightforward. It reads in three fields in an expected format from in the file and sets the three properties.

  1. TimeWhenSiteWillGoOfflineUtc: The DateTime in UTC format as to when the site should be offline.
  2. IpAddressToLetThrough: The IP of the admin person that put the site into offline mode, so we can let that particular person through.
  3. Message: An message that the admin person can give, like “Expect to be back by 9:30 GMT”

b) The GoOffline and RemoveOffline commands

The second part of the OflineFileData class contains the commands to put the site into, and take up out of, offline mode. I build these as static methods and are shown below:

I think the code is again fairly straightforward. Note that the use of the Func <string, string> mapPath is used to pass in the Server.MapPath method from the MVC Action. We need this to get the correct filepath to the file. This also allows the code to be easily Unit Tested.

The Offline.cshtml View

The View ‘ Offline.cshtml is places in the Views/Shared directory and looks like this (note: I am using bootstrap for my CSS). A fairly simple view that does the job, as shown below. The message input by the admin user is shown, which gives better feedback to customers.

The MVC Actions

The last piece of the puzzle is to deal with the MVC actions that the admin user calls to go offline or return to normal working. They are pretty simple, but I give them to complete the code. The actions go inside a Controller, which I haven’t shown and you need some sort of way to make them visible in the menu when a user with the Admin role is logged in.

Again these are very basic MVC actions. Note that the ‘ HttpContext.Request.UserHostAddress returns the IP address of the current user as a string. This is stored in the offline test file so that we can let that user through the offline check. Also OfflineModel model contains an int property called DelayTillOfflineMinutes and the string Message that the admin person can optionally add.

What I have not shown

I have not shown the simple banner that appear when the web site is set to go offline in the future. This is added to the default layout file, normally called _ Layout.cshtml in the Views/Shared folder. It accesses the static property OfflineData in the OfflineHelper class and if not null can calculate and show the time till the site goes offline as a warning to the user.

Also, in my system I give feedback to the admin user that the system after the offline/online calls as, from their point of view, nothing has obviously changed.

Down sides of this approach

I always like to look at the down sides of any approach I use. When architecting any system, there is nearly always a trade-off to be had. In this case we are putting an Action Filter that is called on every action call, which has a performance impact. The main performance costs are:

  1. Checking if the file exists.
  2. Reading the file content.

In my first version I read the file every time, which if there was a file then we had a 3ms with +- 3ms deviation overhead. In the newer version I only read the file on the first time we find it. This improved the performance for the case where the offline file exists.

I have instrumented the creation of the OfflineHelper in the OfflineActionFilter and 99% of the time is in the OfflineHelper, which you would expect. When running on an Azure B1 Basic single core , i.e. not very powerful, and the time that the OfflineHelper takes are:

  • When online: average 0.3ms, +-0.1ms deviation
  • When offline or going offline : average .4ms, +- 0.1ms deviation

Note: There is an approx 6ms cost when first read ing a new file.

Clearly there is a small performance cost to using this approach, as File.Exists ( ) takes some time. It would be possible to add some caching, i.e. you only look for the file if more than x seconds has passed since you did so. At the moment I am happy to live with these figures.

Other than that I cannot see any other major problems with this approach.

Conclusion

There are many ways to handle the process of taking an ASP.NET MVC web application ‘down for maintenance’, but this seems a good choice for my particular requirements when doing data migrations. One feature I particularly like about this solution is that if you upload new software it restarts still in offline mode, which is what I want as I can then check the migration + new code works before taking it back online. It will also work well with automation.

Maybe your need is different, but I hope my approach, plus the pointers to other implementations give you some ideas on how you might add a similar feature. You may not use it often, and I certainly hope I don’t need to get up at 5am to update the site. However my moto on things like this is the same as the Scouts – “be prepared”. The one time I need this feature I will be very pleased that I have as I then won’t have to explain to the client why the site is slow/broken and I have to kill the whole site to fix it.