This blog is a continuation of my previous blog on the topic of Challenges in Installer Implementation and how team resolved them. Above is a futuristic picture of the Port Mann Bridge when finished. It’s copied from Bridge's page. Check my previous blog for why I chose this picture
Once settled on the appearance and actions of the Progressbar we had to look for the backend calls.
Backend Calls
Services Framework
I was counting heavily on our shiny new Service Framework (SF). Turned out that wasn’t easly accessible to the Installer. Why? Initialization of the service routes dependent on something that wasn’t available during Installation. We have a flag in the DotNetNuke framework where we check for installation status and skip many actions if the framework has not been fully installed. We could have tinkered with bits here and there to make SF available during Installation, but that would have resulted in unnecessary overhead with a corresponding increase in installation time – the opposite of our goals for the new Installer
So much for a Progressbar, eh?
What now?
WebMethod
Turns out that Asp.Net has a little known Ajax method called as WebMethod. Webmethod provides easily callable APIs from the web client without the need for web services, wcf or mvc. That turned out to be actually pretty simple to use.
Code Behind
[System.Web.Services.WebMethod]
public static void RunInstall()
{
LaunchAutoInstall();
}
Client Side
//Call PageMethod which triggers long running operation
PageMethods.RunInstall(function () {
}, function (err) {
$.stopProgressbarOnError();
});
If you are interested in finding sample code around use of WebMethod, you can find in /Install/InstallWizard.aspx and InstallWizard.aspx.cs here on our codeplex source repository.
Url Checks
We have a number of places in the framework where we check for special URLs and skip processing if they are Installwizard.aspx, UpgradeWizard.aspx, etc.
Old Code
//First check if we are upgrading/installing
if (app.Request.Url.LocalPath.ToLower().EndsWith("install.aspx")
|| app.Request.Url.LocalPath.ToLower().EndsWith("upgradewizard.aspx")
|| app.Request.Url.LocalPath.ToLower().EndsWith("installwizard.aspx")
|| app.Request.Url.LocalPath.ToLower().EndsWith("captcha.aspx")
One problem with using WebMethods was that it appends the method name in the URL. Most of the above checks failed as we were using "EndsWith" in a string comparison. WebMethod actually changed the URL from installwizard.aspx to installwizard.aspx?RunInstall. (Note the extra parameter.) The fix was pretty simple, but we were thrown for a loop as this check was being done in a number of places and it took a while to narrow them all. Once located, we had to change each EndsWith to Contains.
Changed Code
//First check if we are upgrading/installing
if (app.Request.Url.LocalPath.ToLower().EndsWith("install.aspx")
|| app.Request.Url.LocalPath.ToLower().Contains("upgradewizard.aspx")
|| app.Request.Url.LocalPath.ToLower().Contains("installwizard.aspx")
|| app.Request.Url.LocalPath.ToLower().EndsWith("captcha.aspx")
Client Polling
In order to show progress we needed a mechanism to poll status from server frequently. The main installation thread could not be used as it takes a while to finish. We therefore decided to create other WebMethod calls. Not a big deal; just more work
A new WebMethod was quickly createdto get the Progress percentage and Milestone status. This call gets executed from the client side every second. Here comes the tricky part yet again, the code-behind initiallt used static variables to store current status of the Install. Everything was fine up until Installer restarted. As mentioned above, Installer makes many changes as it runs, and one of the changes is the update of web.config. This causes App Restarts.
App Restarts
App Restarts aren’t aren't evil as they sound :-) The good news was that the main thread kept running even after the app start. It actually did everything that it was supposed to do before terminating gracefully. One good thing to cheer about IIS is that it keeps the older w3wp.exe process running as long as old threads are still running. However, should a new request come during this time, IIS would launch a new instance of w3wp to serve new requests.
As soon as the App gets into restart situation, the very next client polling request causes a new w3wp.exe to fire up. No problem, everything was as expected. After all, the firing of a new App is pretty standard in IIS. However, all of our static variables to store Installer status in the original thread were no longer available in the process. We were hoping that the counters from original thread would be available to the polling thread throughout the Installation. That wasn't the case though. It maintained the counters till only about 80% of Instllation. What now? It’s like we were all ready for lift-off and then we ran into another snag
Inter Process Communication
Coming from a C++ background I am extremely familiar with the Inter Process Communication (IPC) tools available in Win32 APIs; mutex, events, named pipes, memory mapped files are a few that comes to mind. I must also say that none of them are actually suitable for a web application. Database was one option, but it wasn’t guaranteed to be available at the time of installation.
Alas, the old-school text file came to the rescue. The main installation thread stores status as a serialized JSON object in a known text file under \Portals\_Default\Logs folder and the Polling callbacks reads that. Since installation is a pretty fast moving vehicle ( writing to this intermediate log file very frequently), we added try-catch logic around reads and writes to make sure nothing gets locked-up or that we don't throw unnecessary errors.
Hope the above blog and one
prior provides great insight on some of the challenges our Engineering team encountered during creation of Installer in 7.0, and best how we tackled them.