다음을 통해 공유


Demokod från MS Live - HTTP Modul för IIS7

Den demo som jag körde i inledningspasset under MS Live visade på möjligheten att skapa egna HTTP-moduler för Internet Information Server (IIS) 7 med hjälp av Visual Studio och hanterad kod. I tidigare versioner av IIS var man tvungen att använda sig av ISAPI-filter skrivna i native-kod för att kunna "jacka in" sig i IIS process-pipeline, men i och med IIS7 - som är helt modulärt uppbyggd - går det att skriva egna moduler i .NET-kod som hanterar de olika events som triggas när ett anrop processas av servern.

För att underlätta skapandet av nya moduler har vårt IIS-team tagit fram ett Starter-kit med en ny mall till Visual Studio som du kan utgå ifrån när du skapar ett projekt för en HTTP-modul. Den kan laddas hem här

I mitt scenario ville jag lägga till en banner till varje sida som processades av IIS7 i en befintlig ASP.NET-applikation. Därför ändrade jag exemplet på eventhandler som finns med i projektmallen från PreRequestHandlerExecute (som sker innan övrig ASP.NET-kod processat anropet)  till en eventhandler för ReleaseRequestState, som alltså triggas efter applikationens ASP.NET-processande. Så här ser den ut:

/// <summary>

/// Initializes the module, and registers for application events.

/// </summary>

/// <param name="application">

/// The System.Web.HttpApplication instance exposing application events.

/// </param>

public void Init(HttpApplication application)

{

application.ReleaseRequestState += new EventHandler(this.AddBanner);

// TODO: add additional application event handlers here

}

 

Min eventhanterare AddBanner ser ut så här:

        public void AddBanner(object source, EventArgs e)

        {

            HttpApplication application1 = (HttpApplication)source;

            HttpContext context1 = application1.Context;

            string content_type = context1.Response.ContentType.ToLower();

            if (content_type == "text/html")

                context1.Response.Filter = new BannerFilter(context1.Response.Filter);

        }

I AddBanner utgår jag från source-objektet för eventet av typen HttpApplication och kan med hjälp av dess HttpContext avgöra om Response.ContentType är "text/html" - d.v.s. är detta något som jag vill processa (GIF eller JPG-bilder ska ju t.ex. inte processas). Om det är "text/html" så använder jag en egen klass, BannerFilter, för att skapa ett "wrapper" filter som sedan används för att modifiera HTTP-body:n innan den skickas till klienten.

Min BannerFilter-klass, som ärver från basklassen System.IO.Stream, tar stömmen med ursprungsdata i sin konstruktor, kopierar denna ström till en intern variabel och använder den sedan för att modifiera innehållet om det är så att den innehåller ett body-element (direkt efter body-elementet vill jag stoppa in en banner i sidan). Allt detta sker i en override av basklassens Write-metod:

            public override void Write(byte[] buffer, int offset, int count)

            {

                // läs html från stream

                string pageHTML = System.Text.UTF8Encoding.UTF8.GetString(buffer, 0, buffer.Length);

                string banner = DAL.Banner.GetRandomBanner();

                // hitta start-body i html

                Regex bodyRegex = new Regex("<body ", RegexOptions.IgnoreCase);

                Match bodyMatch = bodyRegex.Match(pageHTML);

                // om jag får träff på regexp, lägg till banner i response

            if ((bodyMatch != null) && bodyMatch.Success)

                {

                    StringBuilder newBuffer = new StringBuilder(pageHTML.Length + banner.Length);

newBuffer.Append(pageHTML.Substring(0, bodyMatch.Index + pageHTML.Substring(bodyMatch.Index).IndexOf(">") + 1));

                    newBuffer.Append(banner);

                    newBuffer.Append(pageHTML.Substring(bodyMatch.Index + pageHTML.Substring(bodyMatch.Index).IndexOf(">") + 1));

                    UTF8Encoding encoding = new UTF8Encoding();

                    byte[] finalBuffer = new byte[encoding.GetByteCount(newBuffer.ToString())];

                    finalBuffer = encoding.GetBytes(newBuffer.ToString());

                    responseStream.Write(finalBuffer, 0, finalBuffer.Length);

                }

                // annars kan vi skriva original response till stream

                else

                {

                 responseStream.Write(buffer, 0, buffer.Length);

                }

            }

Hela Visual Studio-lösningen går att ladda hem här. Lösningen innehåller förutom HTTP-moduls-projektet även ett Data Access Layer-projekt som jag använder för att hämta en slumpvis vald WPF/E-banner från databasen. Detta projekt har i sin tur har referenser till Microsoft Data Access Applikation Block från Enterprise Library 3.0 (CTP). DAL-projektet och WPF/E-bannern kan naturligtvis ersättas med vilket material som helst, den lagrade procedur som jag använder är extremt simpel - den ser ut så här:

set ANSI_NULLS ON

set QUOTED_IDENTIFIER ON

GO

ALTER PROCEDURE [dbo].[randombanner_get]

            AS

BEGIN

            SET NOCOUNT ON;

            SELECT TOP 1 wpfebanner FROM banner

            ORDER BY NEWID()

END

Det enda speciella är att jag använder ORDER BY NEWID() för att få ett slumpvist resultat. För att Data Access Application Block ska fungera krävs att web.config för den aktuella ASP.NET-applikationen konfigureras med bl.a. Connection string till databasen. Vårt Enterprise Library och Application Block's kan du läsa mer om på Patterns & Practices-sidan: https://msdn2.microsoft.com/en-us/practices/default.aspx.

 

Du kan läsa mer om IIS7 och HTTP-moduler på https://www.iis.net/ - den nya portalen för utveckling och administration av IIS. Där finns även en hel del information om de äldre versionerna av IIS.  

  

IIS7 Managed Module1.zip