IIS7 UrlRewrite és ASP.NET postback

Az IIS 7-hez kiadott UrlRewrite modul számos előnnyel rendelkezik: az alkalmazástól független, XML-ben konfigurálható, regex alapú, van hozzá GUI, nem csak rewrite-ot, hanem redirectet is tud és még sorolhatnám. Mivel a webkiszolgálóba épül be, tetszőleges platformon íródott alkalmazással használható, így kiválóan működik ASP.NET-tel is. Mindössze csak a postback-kel van gond.

Az UrlRewrite modul leginkább azt tudja, hogy a bejövő URL-ekre reguláris kifejezéssel megállapítja, hogy melyik minta illeszkedik, majd kiveszi az URL egy részét és átteszi query string paraméterbe. Például:

Bejövő URL: http://localhost/RewriteSample/Users/Tas
Átírt URL: http://localhost/RewriteSample/User.aspx?name=Tas

Ehhez mindössze az alábbi szabályt kell felvennünk a web.configban, amiben az IIS Managerbe beépülő varázsló sokat segít:

 <rewrite>
     <rules>
        <rule name="UserRewrite" stopProcessing="true">
            <match url="^Users/([^/]+)/?$"/>
            <conditions>
                <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
                <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
            </conditions>
            <action type="Rewrite" url="User.aspx?name={R:1}" />
        </rule>
     </rules>
  </rewrite>

A böngészőben ezek után a barátságos cím fog megjelenni, valójában azonban a User.aspx fog futni, ami mindebből semmit nem vesz észre: az oldalon lévő DataSource vezérlő QueryStringParametere tökéletesen fog működni.

A gondok akkor keletkeznek, amikor nem csak GET-es oldalunk van, hanem postbackelünk is.

Postbacknél duplázódnak a query stringek

Ha feldobunk egy Button vezérlőt az oldalra, aminek hatására történik egy postback, majd újratöltődik az oldal, azt fogjuk észrevenni, hogy a lekérdezéseink elromlanak. Jobban utánajárva kideríthetjük, hogy a problémát a hibás query stringek okozzák. A fenti User.aspx oldalnak postback után két name query string paramétere lesz, ráadásul az URL is elromlik:

    http://localhost/RewriteSample/Users/Huba?name=Huba

Újabb postback után:

    http://localhost/RewriteSample/Users/Huba?name=Huba&name=Huba

Ekkor a name query string paraméter értéke “Huba,Huba,Huba” lesz, amire biztosan nem vágytunk. A megoldás az appendQueryString=”false” attribútum felvétele a web.configba:

  <action type="Rewrite" url="User.aspx?name={R:1}" appendQueryString="false" />

Postback után megjelenik a query string

A fenti attribútum felvétele után a sokszorozódási probléma megszűnik, viszont postback után egy példányban megjelenik a query string paraméter, így:

    http://localhost/RewriteSample/Users/Kond?name=Kond

vagy rosszabb esetben így (nesze neked barátságos URL):

    http://localhost/RewriteSample/Users/Töhötöm?name=T%u00f6h%u00f6t%u00f6m

Mindennek az oka a lekért oldal form tagjében keresendő:

  <form name="aspnetForm" method="post" action="Kond?name=Kond" id="aspnetForm">

Szerencsére ASP.NET 3.5 SP1-től kezdve a HtmlForm Action tulajdonsága írható, így nem kell JavaScripttel bűvészkednünk, mint korábban. A bejövő URL-t a HTTP_X_ORIGINAL_URL szerver változóban kapjuk meg, ha volt újraírás. (Érdekes módon ez megegyezik Request.RawUrl tulajdonsággal, de azt hivatalosan nem találtam meg sehol, hogy mindig megegyezik.) Akár beállíthatjuk az egészet, vagy ha egységesek akarunk lenni a nem újraírt oldalakkal, akkor csak az utolsó “/” utáni részt, sőt felturbózhatjuk a tegnapi ReturnUrl-es trükkel is:

  private void CorrectPagesWithRewrittenUrls()
  {
    string originalUrl = this.Request.ServerVariables[ "HTTP_X_ORIGINAL_URL" ];

    if( !String.IsNullOrEmpty( originalUrl ) )
    {
        int lastSlashPosition = originalUrl.LastIndexOf( '/' );
        this.Form.Action = originalUrl.Substring( lastSlashPosition + 1 );

        HtmlGenericControl c = new HtmlGenericControl();
        c.TagName = "input";
        c.Attributes[ "name" ] = "ReturnUrl";
        c.Attributes[ "type" ] = "hidden";
        c.Attributes[ "value" ] = originalUrl;            
        this.Form.Controls.Add( c );
    }
  }

Ezt meghívhatjuk Page_Loadból, ám ha valami miatt nem változtathatunk a kódon vagy több oldalt is érint a probléma, akkor lehet saját Control Adaptert írni. Ehhez először egy .browser fájl kell tennünk az App_Browsers mappába, ami megmondja, hogy a HtmlForm példányok rendereléséért a MyFormControlAdapter lesz a felelős.

  <browsers>
    <browser refID="Default">
        <controlAdapters>
            <adapter controlType="System.Web.UI.HtmlControls.HtmlForm" adapterType="MyFormControlAdapter" />
        </controlAdapters>
    </browser>
  </browsers>

Mivel nem akarjuk a teljes form renderelésének terhét magunkra vállalni, hanem csak egyetlen attribútumot akarunk átírni, ezért így valósítottam meg a control adaptert:

  public class MyFormControlAdapter : ControlAdapter
  {
    protected override void Render( HtmlTextWriter writer )
    {
        base.Render( new MyFormHtmlTextWriter( writer ) );
    }
  }

Ebben az a jó, hogy a MyFormHtmlTextWriternek nem kell minden metódusát implementálnunk, elég azzal foglalkozni, ami az attribútumok írásáért felelős:

  public class MyFormHtmlTextWriter : HtmlTextWriter
  {
    public MyFormHtmlTextWriter( HtmlTextWriter writer ) : base( writer )
    {
        this.InnerWriter = writer.InnerWriter;
    }

    public override void WriteAttribute( string name, string value, bool fEncode )
    {
        if( name.Equals( "action", StringComparison.OrdinalIgnoreCase ) )
        {
            HttpContext context = HttpContext.Current;
            string originalUrl = context.Request.ServerVariables[ "HTTP_X_ORIGINAL_URL" ];

            if( !String.IsNullOrEmpty( originalUrl ) && context.Items[ "FormActionModified" ] == null )
            {
                int lastSlashPosition = originalUrl.LastIndexOf( '/' );
                value = originalUrl.Substring( lastSlashPosition + 1 );

                context.Items[ "FormActionModified" ] = true;
            }
        }

        base.WriteAttribute( name, value, fEncode );
    }
  }

A fenti példa teljes forráskódja letölthető innen.


zip UrlRewriteFixSample.zip (376 kB)


Balássy György (MS RD, ASP.NET MVP, MCTS)

Balássy György (MS RD, ASP.NET MVP, MCTS) Villamosmérnök, a BME Automatizálási és Alkalmazott Informatikai Tanszékén webportálok fejlesztését oktatja. 2000 óta foglalkozik a Microsoft .NET platformjával, melynek meghonosításában jelentős szerepet vállalt előadóként, konzulensként és A .NET Framework és programozása című könyv társszerzőjeként. Az MSDN Kompetencia Központon belül a Portál Technológiák Csoport vezetője, szakterülete web alapú rendszerek fejlesztése és üzemeltetése. 2004-ben Magyarországon elsőként kapta meg a Most Valuable Professional címet, majd 2005 óta a Microsoft magyarországi regionális igazgatója. Publikációi a Technet Magazinban, az MSDN Kompetencia Központ honlapján és szakmai blogjában olvashatóak.

2009.07.28. 11:39:57 | Permalink | Hozzászólások: 0 | Tárgyszavak: ,


  • A C# 2008 és a .NET 3.5

    Dávid Zoltán Gincsai Gábor barátommal lektoráltuk a címben szereplő könyv magyar kiadásának első kötetét. A könyv a Szak Kiadó munkáját dícséri. Magyarországon nagyon kevés – gyakorlatilag nulla – naprakész, mély, mégis érthető külföldi szakkönyv jelenik meg még azelőtt, hogy elavulna. Ez egy nagy kivétel. A szerző Andrew Troelsen tokkal-vonóval bemutatja, amit a C#-ról és a .NET-ről jelenleg tudni érdemes. A benne foglaltak elolvasását nyugodt szívvel ajánlom mindenkinek. Tovább »
  • Gombok kiszürkítése Firefoxban

    Balássy György (MS RD, ASP.NET MVP, MCTS) Azt hiszem jogosan várjuk el egy szoftver felhasználói felületétől, hogy ha egy gombot nem lehet megnyomni, akkor ez messziről ordítson róla. Sajnos ez a Firefoxban megjelenített weboldalakon nem mindig van így. Tovább »


Írja meg Ön is véleményét!


Hozzászólásokat csak regisztrált, bejelentkezett felhasználóktól tudunk elfogadni!

Hozzászólások