Replacing Windows Services with Web Apps using App Init in IIS 8+ (and 7.5 sort of)

We’ve always moved towards a SOA, so most of our business logic is in the web service layer, primarily WCF traditionally, but moving towards REST these days. So a lot of our windows services end up as simply a timer that polls a web method, or a timer that pools a web method to then tell it what methods to poll.

Disclaimer: if anyone is saying right now “why are you polling, and not using a Service Bus?”, its mostly to do with 3rd party integration or legacy systems when we need to poll.

In the past we have never contemplated putting something like this into a web app due to IIS limitations around “keep alive” of the app domain.

With Application Initialization as of IIS 8 (and supported in 7.5 with a plugin) we have started to do this. So instead of having a dependence on a windows service we are just dropping timers into the start up of the web app. This reduces the amount of projects we have to deploy/maintain/monitor.

There is a few dependencies on this though beyond just the tag in the web config that need to be set to make an App Domain “immortal”, as I’ve been putting it.

First the tag in your web config


 <applicationInitialization doAppInitAfterRestart="true">
 <add initializationPage="/myService.svc" />
</applicationInitialization>

I usually just throw it at the based svc file, but anything that hits something that executes .NET code is fine, then you need to make sure you have the doAppInitAfterRestart set “true”, in case someone kills the worker process or something odd like that.

Note though that relative paths (using ~) are not supported, so it you have something in a sub app you are going to have to code this path in.

Next is your App Pool Settings, you need to set the following values:

  • idleTimeout to 0
  • recyclingPeriodicrestart to 0
  • startMode to AlwaysRunning

Then you also need to set the following values in your Web Site settings as well

  • preloadEnabled to “true”
  • serviceAutoStartEnabled to “true”

I use Step Templates in Octopus deploy to check for these values and Update them if note set:

Below is an example of the powershell i use in the AppPool update script


Import-Module WebAdministration
$enable32BitAppOnWin64Val=[System.Convert]::ToBoolean($enable32BitAppOnWin64)
$idleTimeoutVal= [TimeSpan]::FromMinutes($idleTimeout)
$recyclingPeriodicrestartVal= [TimeSpan]::FromMinutes($recyclingPeriodicrestart)

Write-Host "Checking $AppPoolName processModel.idleTimeout is $idleTimeoutVal"
if((Get-ItemProperty "IIS:\AppPools\$AppPoolName").processModel.idleTimeout -ne $idleTimeoutVal)
{
$delPool = Get-Item "IIS:\AppPools\$AppPoolName";
$delPool.processModel.idleTimeout=$idleTimeoutVal;
$delPool | Set-Item
Write-Host "Setting $AppPoolName processModel.idleTimeout to $idleTimeoutVal"
}
else
{
Write-Host "$AppPoolName processModel.idleTimeout is $idleTimeoutVal already"
}

Write-Host "Checking $AppPoolName recycling.periodicrestart.time is $recyclingPeriodicrestartVal"
if((Get-ItemProperty "IIS:\AppPools\$AppPoolName").recycling.periodicrestart.time -ne $recyclingPeriodicrestartVal)
{
$delPool = Get-Item "IIS:\AppPools\$AppPoolName";
$delPool.recycling.periodicrestart.time=$recyclingPeriodicrestartVal;
$delPool | Set-Item
Write-Host "Setting $AppPoolName recycling.periodicrestart.time to $recyclingPeriodicrestartVal"
}
else
{
Write-Host "$AppPoolName recycling.periodicrestart.time is $recyclingPeriodicrestartVal already"
}

Write-Host "Checking $AppPoolName enable32BitAppOnWin64 is $enable32BitAppOnWin64Val"
if((Get-ItemProperty "IIS:\AppPools\$AppPoolName").enable32BitAppOnWin64 -ne $enable32BitAppOnWin64Val)
{
$delPool = Get-Item "IIS:\AppPools\$AppPoolName";
$delPool.enable32BitAppOnWin64=$enable32BitAppOnWin64Val;
$delPool | Set-Item
Write-Host "Setting $AppPoolName enable32BitAppOnWin64 to $enable32BitAppOnWin64Val"
}
else
{
Write-Host "$AppPoolName enable32BitAppOnWin64 is $enable32BitAppOnWin64Val already"
}

Write-Host "Checking $AppPoolName startMode is $startMode"
if((Get-ItemProperty "IIS:\AppPools\$AppPoolName").startMode -ne $startMode)
{
$delPool = Get-Item "IIS:\AppPools\$AppPoolName";
$delPool.startMode=$startMode;
$delPool | Set-Item
Write-Host "Setting $AppPoolName startMode to $startMode"
}
else
{
Write-Host "$AppPoolName startMode is $startMode already"
}

And i use a similar script for the Web Site update, pretty basic stuff.

What about the App Domain? the above will secure the App Pool but not the App Domain, a common cause of app domain restart is a deploy via octopus, when it changes the IIS path it will restart the App Domain of the site. to handle this you need to add a snippet like the below to you Application end event in Global asax


protected void Application_End()
{
var client = new WebClient();
var hostName = Dns.GetHostName();
var url = "http://" + hostName + "/service.svc";
client.DownloadString(url);
}

Once these are all setup you couldn’t kill that App Domain with a crowbar. Even end tasking it from Task Manager it will restart.

So you are safe to run timers and any other services you like in it, just like you would in a windows service.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s