Thursday 25 February 2010

Migration of ASP.NET app from IIS6 to IIS7 (7.5)

For many of us familiar problem. You developing applications under IIS6 and you're about to move them to IIS7. In previous version of IIS was enough to copy over your files, create app pool and site. IIS7 (7.5) is different in this point.

In IIS6 there was only one way hot to extend server with other features – using ISAPI filters. Whole .NET is lived within one dll aspnet_isapi.dll. If the request was for files with .NET type extensions (such .aspx, .ashx, .axd and so on) your application know them and was able to serve them. If request was for file with extension for example .jpg or other static file, the application was not aware about them. For example this is the reason why URL authorization does not work for static files.

IIS7 offers two modes of work:

  • Classic – In IIS requests are processed by above description. Basically the system is compatible with previous versions of IIS
  • Integrated – the default one. In this mode has IIS privileged position, the HTTP handlers and modules (known from ASP.NET) can be executed directly. They are in line with ISAPI filters and built-in IIS functionality.

HTTP handlers, modules were not changed at all, so you don't need to rewrite or recompile them. But was has been changed is the way how to know IIS about which handler, module to use. Basically this is often problem when app is running properly under IIS6 but won't under IIS7. This is not such difference between IISs but it's big difference between Classic and Integrated mode on application pool. You have two options:

  1. Put your application under Classic managed pool (this is not recommended, use it only in case when other solutions fails)
  2. Change the registration of modules and handlers in web.config file to reflect newest configuration schema.

There is no difference whether you register handler or module under IIS6 or IIS7 Classic Mode. Illustration of its registration in web.config:

<?xml version="1.0"?>
<
configuration>
<
system.web>
<
httpHandlers>
<
add verb="*" path="*My.axd" type="MyHttpHandler"/>
</
httpHandlers>
<
httpModules>
<
add name="MyModule" type="MyHttpModule"/>
</
httpModules>
</
system.web>
</
configuration>
In case of web.config in II7 Integration mode, registration will looks like:

<?xml version="1.0"?>
<
configuration>
<
system.webServer>
<
handlers>
<
add name="NameOfMyHandler" verb="*" path="*My.axd" type="MyHttpHandler"/>
</
handlers>
<
modules>
<
add name="MyModule" type="MyHttpModule" preCondition="managedHandler"/>
</
modules>
</
system.webServer>
</
configuration>
Generally you have to perform these changes:
  1. you need rename httpHandlers to handlers and httpModules to modules.


  2. Handlers has required attribute name, so you have to name them


  3. Modules should have attribute preCondition with value managedHandler. This is optional and depends on behavior of particular module (module will called only in case when its execution will be driven by handler written in .NET).

Changes can be done manually or by command line tool (see bellow).

HTTP modules are called for each request. In case of II6 or Classic mode it means for each request mapped in aspnet_isapi.dll configuration. For integrated mode it means for all request including for static files.

Well sometimes you run into problem when you your app needs to work IIS6 as well IIS7. Problem is once you register handlers and module in system.webServer section you need to remove their registration from system.web section. If the IIS would ignore old registration in system.web section I could be security risk caused by not executed some modules, handlers. Mostly those for authentication and authorization. But there is am option how to avoid this checking and allow to have registrations in both sections. All you need to do is to turn off validation by attribute validateIntegratedModeConfiguration. Using this attribute is not really recommended.


So the universal web.config for both scenarios would looks like:

<?xml version="1.0"?>
<
configuration>
<
system.web>
<
httpHandlers>
<
add verb="*" path="*My.axd" type="MyHttpHandler"/>
</
httpHandlers>
<
httpModules>
<
add name="MyModule" type="MyHttpModule" />
</
httpModules>
</
system.web>
<
system.webServer>
<
validation validateIntegratedModeConfiguration="false"/>
<
handlers>
<
add name="NameOfMyHandler" verb="*" path="*My.axd" type="MyHttpHandler"/>
</
handlers>
<
modules>
<
add name="MyModule" type="MyHttpModule" preCondition="managedHandler"/>
</
modules>
</
system.webServer>
</
configuration>
Instead of modifying web.config manually, you can perform required changes from command line by

%windir%\system32\inetsrv\Appcmd migrate config "<ApplicationPath>"

ApplicationPath is site map name in IIS, for example Default Web Site.

The tool will modify web.config in order to move registration of handlers, modules from system.web to system.webServer section.

Even you've did web.config changes you app can still failing under Integration mode. The most common is "Request is not available in this context" exception. This happens when your implementation is about to access Request object in Application_Start method of global.asax file. Error is due to design change in the II7 Integrated pipeline that makes Request object unavailable in Application_Start. The Classic mode has no problems with it. What you can do about it is to change your app to avoid reference to Request context inside Application_Start or running app under Classic mode (not recommended). More about avoiding reference to Request object you can find on Mike Volodarsky's blog.

2 comments:

Unknown said...

Very Nice article and explaination.
But if we want to use Request Context on Application Start. What should we do?

Macrosoft said...

Excellent post about migrating asp.net from iis6.

ASP.Net Migration