Znáte to: nadšeni z nové verze nainstalujete Windows 2008 s IIS 7.0, překopírujete dosud bez problémů fungující aplikaci a bác ho: nic nefunguje. Změny architektury mezi verzemi 6.0 a 7.0 způsobily, že ASP.NET aplikace nestačí jenom tupě zkopírovat, ale je třeba upravit i konfigurační soubory.

"Classic" versus "Integrated"

Ve verzi 6.0 existovala jenom jedna možnost, jak monolitické IIS rozšířit o další funkčnost: ISAPI. Pokud měl web server umět cokoliv víc, než jenom vzít soubor ležící na disku a procpat ho sítí k příjemci, musely se o to postarat ISAPI filtry a extensions (pro jednoduchost pomíjím pro nás irelevantní CGI). Active Server Pages (ASP), ASP.NET a cokoliv podobného bylo tedy implementováno jako ISAPI extension.

Celý .NET tedy žil uvnitř jednoho DLLka, jménem aspnet_isapi.dll. Dozvěděl se jenom o těch požadavcích, o kterých mu IIS výslovně řekl. Které požadavky to budou určovala typicky přípona. Pokud byl požadavek na soubory s .NETovými příponami typu .aspx, .ashx, .axd a další, vaše aplikace ho viděla a mohla obsloužit. Pokud se jednalo třeba o obrázek s příponou .jpg nebo jiný statický soubor, aplikace se o něm vůbec nedozvěděla. Z tohoto důvodu například pro statické soubory nefungovala URL authorization.

IIS 7.0 nabízí dva režimy práce (managed pipeline mode). Jeden z nich je označen jako "Classic". V něm IIS postupuje výše uvedeným způsobem. Jedná se v podstatě o režim kompatibility s předchozí verzí. Výchozí režim je ale "Integrated". V tomto režimu má .NET výsadní postavení: HTTP handlery a HTTP moduly, známé z ASP.NET umí IIS spouštět přímo. Jsou tak co do schopností postaveny na roveň ISAPI filtrům a vestavěné funkcionalitě IIS.

HTTP handlery a moduly samy se nezměnily. Není tedy nutné je přepisovat nebo překompilovat. Co se ale změnilo, byla konfigurace. Způsob, jakým se IIS má dozvědět, které handlery a moduly má použít.

20090213-AppPools

Pokud tedy máte aplikaci, která mám na IIS 6.0 chodí na na IIS 7.0 se odmítá spustit, problém leží velmi pravděpodobně zde. Protože se nejedná ani tak o rozdíl mezi verzemi IIS, jak mezi integrovaným a klasickým režimem, máte na výběr dvě možnosti:

  1. Umístit danou aplikaci do application poolu, který má nastaven režim "Classic". Po instalaci IIS vytvoří dva application pooly: DefaultAppPool s integrovaným režimem a Classic .NET AppPool s režimem zpětně kompatibilním.
  2. Změnit registrace modulů a handlerů v souboru web.config tak, aby odpovídaly novému konfiguračnímu modelu.

Rozdíly v konfiguraci

Handlery a moduly se v IIS 6.0 a v Classic Mode registrují v sekci system.web. Konfigurační soubor aplikace může vypadat nějak takto (pro přehlednost pomíjím ostatní nastavení):

<?xml version="1.0"?>

<configuration>

    <system.web>

        <httpHandlers>

            <add path="*.my.axd" verb="*" type="MyHttpHandler" />

        </httpHandlers>

        <httpModules>

            <add name="MujModul" type="MyHttpModule" />

        </httpModules>

    </system.web>

</configuration>

V případě Integrated mode se už nejedná o konfiguraci ASP.NET, ale IIS samotného. Pročež se přesunula do sekce system.webServer a i když vypadá na první pohled stejně, mírně se liší:

<?xml version="1.0"?>

<configuration>

    <system.webServer>

        <handlers>

            <add name="MujHandler" path="*.my.axd" verb="*" type="MyHttpHandler" />

        </handlers>

        <modules>

            <add name="MujModul" type="MyHttpModule" preCondition="managedHandler" />

        </modules>

    </system.webServer>

</configuration>

V zásadě musíte provést tři změny:

  1. Je nutno přejmenovat elementy httpHandlers a httpModules na pouhé handlers a modules.
  2. Nově mají i handlery atribut name a ten je povinný – je tedy nutno je pojmenovat.
  3. Pro jistotu můžete přidat k modulům atribut preCondition s hodnotou managedHandler.

Poslední bod nemusí být bezpodmínečně nutný a záleží na povaze konkrétního modulu, zda jej aplikujete. HTTP moduly se volají při všech požadavcích. V případě IIS 6 a Classic Mode to znamená "při všech požadavcích, mapovaných v konfiguraci na aspnet_isapi.dll" a v případě integrated mode "při úplně všech požadavcích, včetně například těch na statické soubory". Nové chování vám sice dává víc možností, ale pokud aplikace s tak velkým rozletem nepočítá, může být výhodnější držet se hesla "dobrého pomálu". Nastavení preCondition na managedHandler způsobí, že se daný modul bude volat pouze v případě, že jej bude vyřizovat handler psaný v .NETu. Což efektivně emuluje předchozí chování.

Jak na univerzální konfiguraci

Pokud handlery a moduly určíte v sekci system.webServer, musíte odstranit jejich registraci v system.web. Pokud tak neučiníte, IIS vyhodí chybu a vaši aplikaci odmítne spustit. Proč?

Protože pokud by starou registraci v system.web jenom mlčky ignoroval, mohlo by to představovat bezpečnostní riziko. Pokud by uživatel, neznalý obsahu tohoto článku, zkopíroval aplikaci z předchozího režimu, mohlo by se stát, že některé moduly a handlery se nespustí. A jsou to právě typicky moduly, které se starají o autentizaci a autorizaci. Takový přístup by tedy mohl zpřístupnit části aplikace neoprávněným uživatelům. Proto IIS trvá na tom, že web.config musí obsahovat jenom jednu sadu definic v jedné sekci.

Jak ale postupovat v případě, že chcete napsat konfigurační soubor univerzálně, aby byl použitelný v obou verzích a režimech? Nezbývá vám, než shora uvedené hlídání vypnout pomocí atributu validateIntegratedModeConfiguration. Pokud toto učiníte, dáváte tím IIS na vědomí, že jste si popsaného problému vědomi, že nestojíte o jeho péči a že si sami nastavíte co je třeba a zařídíte, aby byl obsah obou sekcí správný. Univerzální konfigurační soubor v duchu dříve uvedených příkladů může vypadat takto:

<?xml version="1.0"?>

<configuration>

    <system.web>

        <httpHandlers>

            <add path="*.my.axd" verb="*" type="MyHttpHandler" validate="true"/>

        </httpHandlers>

        <httpModules>

            <add name="MujModul" type="MyHttpModule"/>

        </httpModules>

    </system.web>

    <system.webServer>

        <validation validateIntegratedModeConfiguration="false" />

        <handlers>

            <add name="MujHandler" path="*.my.axd" verb="*" type="MyHttpHandler" />

        </handlers>

        <modules>

            <add name="MujModul" type="MyHttpModule" preCondition="managedHandler" />

        </modules>

    </system.webServer>

</configuration>

Je smutnou, jakkoliv pochopitelnou, skutečností, že pokud používáte šablony pro AJAXové projekty (ASP.NET AJAX nebo verze 3.5), je v nich tento atribut dopředu uveden, protože obsahují registrace potřebných komponent. Pokud vám tedy aplikace po přechodu do integrovaného modu sice nehází konfigurační chybu, ale celkově funguje dost podivně, možná že problém je právě zde.

Automation for the people

Převod konfiguračních souborů můžete udělat samozřejmě udělat ručně, výše uvedeným postupem. Ten se ale dá snadno automatizovat:

  1. Nejprve aplikaci zprovozněte v klasickém režimu, což by mělo být (alespoň pokud jde o IIS a ASP.NET) poměrně snadné.
  2. Poté si spusťte příkazový řádek a přepněte se do adresáře C:\Windows\System32\inetsrv (potažmo tam, kde máte nainstalované Windows).
  3. Zde spusťte příkaz appcmd.exe migrate config "Název web site/". Onen parametr v uvozovkách je ve skutečnosti konfigurační cesta, ne název webu – proto to lomítko na konci. Pokud se tedy váš web jmenuje "MujWeb", bude příkaz appcmd.exe migrate config "MujWeb/".
  4. Aplikaci nyní můžete spustit v integrovaném režimu.

Program appcmd.exe provede změnu souboru web.config. Pomocí dalších parametrů můžete kontrolovat co a jak se migruje. Jejich seznam získáte po spuštění appcmd.exe migrate config /?.