URL Rewriter ile akıllı urller oluşturma (Tam çözüm)

Çoğu web sitesinde kullanılan akıllı URL kullanımını bu yazıda tüm detayları ile bulabilirsiniz.

Önce gelin birlikte bu sayfanın adresini inceleyelim:

http://www.daltinkurt.com/Icerik/234/URL-Rewriter-ile-akilli-urller-olusturma-Tam-cozum.aspx

Evet, itiraf edeyim. FTP de /Icerik adında bir klasör yok. :) Onun içinde 234 adında bir klasör onun içinde de URL-Rewriter-ile-akilli-urller-olusturma-Tam-cozum.aspx adında bir dosya da yok.

Yapılan iş, adresteki 234 bilgisini alıp bir /CMS-Icerik.aspx?Id=234 adresine yönlendirmek. Site ziyaretçileri, Google ve diğer arama motorları bunu bilmiyor tabii :)

Akıllı url ler dediğimiz bu yöntemi kullanmanın birkaç avantajı var.

  1. http://www.hurriyet.com.tr/spor/futbol/22746948.asp adresini bir arkadaşınıza maille gönderdiğinizde, arkadaşınız muhtemelen ilk bakışta bu adreste ne olduğunu anlayamayacaktır.
  2. SEO açısından düşündüğümüzde de anahtar kelimelerin url de geçmesi çok önemli bir unsurdur.
  3. KISS (Keep it simple stupid) ilkesine uygun url ler, gereksiz site trafiğinin önüne geçmiş olur ve SEO açısından önemli hususlardan biri olan siteden hemen çıkma süresini azaltır. xxxxxx.com/ayakkabi/spor.aspx adresine kimin ne için gireceği bellidir. Ama aynı url xxxxxx.com/urunler.aspx?Id=90 şeklinde olsaydı ilgili ilgisiz herkes girecek ve sayfayla ilgisi olmayan ziyaretçiler belki de sayfadan hemen çıkacaktır. Bu da kesinlikle istenmeyen bir durumdur.

Avantajlar daha da arttırılabilir. Biz konumuza geri dönelim.

Yazının geriye kalan kısmında, URL Rewriter ı kullanarak akıllı URL kullanmanın tam ve doğru çözümünü göreceksiniz. -Tecrübeyle sabittir- 

İlk iş olarak http://urlrewriter.net/ adresine giderek Download sayfasından veya direkt  https://github.com/sethyates/urlrewriter adresinden,  URL Rewriter DLL ini oluşturmak için gerekli dosyaları indirelim.

Projeyi indirip Visiual Studio'da derleyelim ve proje klasörü/Bin/Debug klasörü içerisinde Intelligencia.UrlRewriter.dll nin oluştuğunu görelim.

Şimdi gelelim adım adım ayarların nasıl yapılacağına.

1) Oluşturduğumuz Intelligencia.UrlRewriter.dll dosyasını projemize refere edelim.

DLL i refere ettikten sonra ki Solution Explorer görünümü:

2) Web.config dosyasını açıp aşağıdaki değişiklikleri gerçekleştirelim. (Koyu kırmızı satırlar)

<?xml version="1.0"?>
<configuration>
  <configSections>
     <section name="rewriter" requirePermission="false" type="Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler, Intelligencia.UrlRewriter"/>
       <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
    <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
        <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
        <sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
          <section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="Everywhere" />
          <section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
          <section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
          <section name="roleService" type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
        </sectionGroup>
      </sectionGroup>
    </sectionGroup>
  </configSections>
  <appSettings/>
  <connectionStrings/>
  <system.web>
    <compilation debug="false">
      <assemblies>
        <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
      </assemblies>
    </compilation>
    <authentication mode="Windows" />
    <pages>
      <controls>
        <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      </controls>
    </pages>
    <httpHandlers>
      <remove verb="*" path="*.asmx"/>
      <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false"/>
    </httpHandlers>
    <httpModules>
      <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter"/>
    </httpModules>
  </system.web>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" warningLevel="4"
                type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <providerOption name="CompilerVersion" value="v3.5"/>
        <providerOption name="WarnAsError" value="false"/>
      </compiler>
      <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" warningLevel="4"
                type="Microsoft.VisualBasic.VBCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <providerOption name="CompilerVersion" value="v3.5"/>
        <providerOption name="OptionInfer" value="true"/>
        <providerOption name="WarnAsError" value="false"/>
      </compiler>
    </compilers>
  </system.codedom>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules>
      <remove name="ScriptModule" />
      <add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule"/>
    </modules>
    <handlers>
      <remove name="WebServiceHandlerFactory-Integrated"/>
      <remove name="ScriptHandlerFactory" />
      <remove name="ScriptHandlerFactoryAppServices" />
      <remove name="ScriptResource" />
      <add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode"
           type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode"
           type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    </handlers>
  </system.webServer>
  <runtime>
    <assemblyBinding appliesTo="v2.0.50727" xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Extensions" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Extensions.Design" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <rewriter>
    <!-- Buraya yönlendirme satırları gelecek -->
  </rewriter>
</configuration>

3) Bu noktada http://www.daltinkurt.com/Icerik/150/Bir-Stringi-akilli-URL-ye-donusturme.aspx adresine göz atmanızda fayda olacaktır. Çünkü kod bazında URL oluştururken ToURL() benzeri bir genişletme metodu kullanmak bize avantaj sağlayacaktır.

Şimdi veritabanından çektiğimiz bilgileri kullanarak repeater imizi dolduralım. Linkleri oluştururken de linklerimizi akıllı url ler olarak oluşturalım.

Örnek tablomuz şu şekilde olsun:

Şimdi repeaterimizi dolduralım. Veritabanı işlemleri için http://www.daltinkurt.com/Icerik/158/HelperDB-Sinifim.aspx adresindeki HelperDB sınıfını kullanabilir veya kendiniz istediğiniz yöntemi kullanabilirsiniz.

Kullandığım genişletme metotları için http://www.daltinkurt.com/Icerik/165/Kullandigim-genisletme-metotlarim.aspx adresine göz atabilirsiniz.

Önce bir Repeater ekleyelim:

<asp:Repeater ID="rptHaberler" runat="server">
    <ItemTemplate>
        <img src="images/point.gif" alt="" style="vertical-align: middle;" />
        <asp:HyperLink ID="hl" runat="server" Text='<%#Eval("Baslik") %>' 
        NavigateUrl='<%#string.Format("/Haber/{0}/{1}.aspx", Eval("Id"), Eval("Baslik").ToString().ToURL()) %>' />
    </ItemTemplate>
    <SeparatorTemplate>
        <hr />
    </SeparatorTemplate>
</asp:Repeater>

HyperLink in NavigateUrl property sini nasıl oluşturduğumuza dikkat edin.

Şimdi de tablomuzdaki verileri getirelim:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        HelperDB helperDB = new HelperDB();
        string sql = "select * from cmsicerikler order by Id desc limit 10";
        rptHaberler.DataSource = helperDB.ExecuteDataSet(sql).Tables[0];
        rptHaberler.DataBind();
    }
}

Sonuç:

Alt taraftaki linke dikkat ettiniz mi?

http://localhost:52437/Haber/174/Infopath-ile-ilgili-2-webiner.aspx

4) Şimdi web.config içerisinden yönlendirme satırımızı ekleyelim:

<rewriter>
  <rewrite url="~/Haber/(\d+)/(.+)\.aspx" to="~/Haber.aspx?Id=$1"/>
</rewriter>

Amaç anlaşılmıştır herhalde. /Haber/174/Infopath-ile-ilgili-2-webiner.aspx formatındaki url i /Haber.aspx?Id=174 e yönlendiriyoruz.

Siz de buna benzer şekilde istediğiniz kadar yönlendirme satırını <rewriter>...</rewriter> tagları arasına yazabilirsiniz.

5) Peki Haber.aspx dosyası içerisinden gelen Id parametresini nasıl okuyacağız? 

Hiçbir ekstra durum yok. Önceden nasıl okuyorsanız şimdi de aynı şekilde okuyabilirsiniz:

protected void Page_Load(object sender, EventArgs e)
{
    int id = 0;
    if (Request.QueryString["Id"] != null)
        if (Request.QueryString["Id"].IsInteger())
            id = Request.QueryString["Id"].ToInt32();

    Response.Write("Id den gelen değer: " + id);
}

Id değerini kontrol edip ekrana yazdırıyoruz:

İsterseniz URL bilgisinin detaylarını göstermeye çalışalım:

protected void Page_Load(object sender, EventArgs e)
{
    int id = 0;
    if (Request.QueryString["Id"] != null)
        if (Request.QueryString["Id"].IsInteger())
            id = Request.QueryString["Id"].ToInt32();

    Response.Write("Id den gelen değer: " + id);

    Response.Write("<br/>");
    Response.Write("Request.RawUrl : " + Request.RawUrl);
    Response.Write("<br/>");
    Response.Write("Request.Url : " + Request.Url);
}

Gördüğünüz üzere gerçek url ile sahte url li bu şekilde elde edebiliyoruz.

6) Şimdi gelelim işin püf noktalarına. Bu şekilde sitemizi oluşturduğumuzda birkaç sıkıntı ile karşılaşacağız.

Bunlardan birincisi; sayfamıza bir adet button ekleyelim ve postback oluşması için butona tıklayalım. Tıkladıktan sonra adres satırına dikkat edin.

<form id="form1" runat="server">
<div>
    <asp:Button runat="server" Text="Postback yap" />
</div>
</form>

Dikkat ettiyseniz, adres satırımız değişti. Tabii ki kimse postbackten sonra böyle bir durum oluşmasını istemez.

Önce nedenini açıklayalım, sonra da çözüm yolunu:

/Haber/174/Infopath-ile-ilgili-2-webiner.aspx adresindeyken sayfamızın kaynağını açıp inceleyelim:

....
<form name="form1" method="post" action="../../Haber.aspx?Id=174" id="form1">
...
</form>


Sorun tam da buradan kaynaklanıyor. Sayfamızdaki form elementinin action adresi bizim sayfa adresimizle aynı değil ve de postbackten sonra formun action ında yazan adres çağrıldığı için bu sorunu yaşıyoruz.

Çözüm için, formun action bilgisini sayfa adresimiz le değiştirmemiz gerekiyor.

Öncelikle Solution Explorer dan web sitemize sağ tıklayıp Add ASP.NET Folder -> App_Browser klasörünü ekleyelim. Bu klasöre form.browser adında bir dosya oluşturarak içerisine şu satırları ekleyelim.

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

Şimdi de App_Code klasörüne System.Web.UI.Adapters.ControlAdapter sınıfından türettiğimiz FormRewriterControlAdapter adında bir class oluşturalım ve içerisine şu satırları yazalım:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public class FormRewriterControlAdapter : System.Web.UI.Adapters.ControlAdapter
{
    protected override void Render(System.Web.UI.HtmlTextWriter writer)
    {
        base.Render(new RewriteFormHtmlTextWriter(writer));
    }
}
public class RewriteFormHtmlTextWriter : HtmlTextWriter
{
    public RewriteFormHtmlTextWriter(HtmlTextWriter writer)
        : base(writer)
    {
        this.InnerWriter = writer.InnerWriter;
    }

    public RewriteFormHtmlTextWriter(System.IO.TextWriter writer)
        : base(writer)
    {
        base.InnerWriter = writer;
    }

    public override void WriteAttribute(string name, string value, bool fEncode)
    {
        if (name == "action")
        {
            HttpContext Context;
            Context = HttpContext.Current;
            if (Context.Items["ActionAlreadyWritten"] == null)
            {
                // Bu değişiklik önemli !!!
                value = Context.Request.RawUrl;
               
                Context.Items["ActionAlreadyWritten"] = true;
            }
        }
        base.WriteAttribute(name, value, fEncode);
    }
}

Koyu kırmızı olan satıra dikkat edin. Bu satır formumuzun action bilgisini sayfamızın adresi ile değiştiriyor.

Şimdi bir daha sayfamızı yenileyelim ve kaynağa tekrar göz atalım:

http://localhost:52437/Haber/174/Infopath-ile-ilgili-2-webiner.aspx

...
<form name="form1" method="post" action="/Haber/174/Infopath-ile-ilgili-2-webiner.aspx" id="form1">
...
</form>

İstediğimiz değişiklik gerçekleşti.

Zaman içerisinde tekrar yaşama ihtimaliniz olan bu sıkıntıyı hepten yok edecek bir satırı daha sayfanıza eklemenizi tavsiye ediyorum.

Eğer MasterPage kullanmıyorsanız, sayfanızın Page_Load'una, kullanıyorsanız da MasterPage in Page_Load una alttaki satırı yazmanız yeterli:

protected void Page_Load(object sender, EventArgs e)
{
    form1.Action = Request.RawUrl;
}

Özellikle projelerinizin ilerleyen safhalarında yani projeniz büyüdüğünde yukarıdaki satırı kullanmamanız sizin için sıkıntı çıkarabilecektir.

7) Gelelim bir başka önemli noktaya daha. Şu ana kadarki haliyle sitenizi kendi alan adınızda yayınladığınızda, akıllı url lerin çalıştığını ama Google ve diğer arama motorları tarafından sayfalarınızın indekslenmediğini göreceksiniz.

Bu durumu aşmak için de az önce oluşturduğumuz App_Browsers klasörümüze genericmozilla5.browser adında bir dosya ekleyerek içerisine şu satırları yazıyoruz:

<browsers>
  <browser id="GenericMozilla5" parentID="Mozilla">
    <identification>
      <userAgent match="Mozilla/5\.(?'minor'\d+).*[C|c]ompatible; ?(?'browser'.+); ?\+?(http://.+)\)" />
    </identification>
    <capabilities>
      <capability name="majorversion" value="5" />
      <capability name="minorversion" value="${minor}" />
      <capability name="browser" value="${browser}" />
      <capability name="Version" value="5.${minor}" />
      <capability name="activexcontrols" value="true" />
      <capability name="backgroundsounds" value="true" />
      <capability name="cookies" value="true" />
      <capability name="css1" value="true" />
      <capability name="css2" value="true" />
      <capability name="ecmascriptversion" value="1.2" />
      <capability name="frames" value="true" />
      <capability name="javaapplets" value="true" />
      <capability name="javascript" value="true" />
      <capability name="jscriptversion" value="5.0" />
      <capability name="supportsCallback" value="true" />
      <capability name="supportsFileUpload" value="true" />
      <capability name="supportsMultilineTextBoxDisplay" value="true" />
      <capability name="supportsMaintainScrollPositionOnPostback" value="true" />
      <capability name="supportsVCard" value="true" />
      <capability name="supportsXmlHttp" value="true" />
      <capability name="tables" value="true" />
      <capability name="vbscript" value="true" />
      <capability name="w3cdomversion" value="1.0" />
      <capability name="xml" value="true" />
      <capability name="tagwriter" value="System.Web.UI.HtmlTextWriter" />
    </capabilities>
  </browser>
</browsers>

Bundan sonra URL Rewriter ı sıkıntı duymadan rahatlıkla kullanabilirsiniz.

Sizin için örnek birkaç örnek yönlendirme satırı göstermek istiyorum:

 <rewrite url="~/Urunler.aspx" to="~/E-Ticaret/Default.aspx"/>
 <rewrite url="~/Urun/Kategori/(\d+)/(.+)\.aspx" to="~/E-Ticaret/Kategori.aspx?Id=$1"/>
 <rewrite url="~/Urun/Detay/(\d+)/(.+)\.aspx" to="~/E-Ticaret/Urun.aspx?Id=$1"/>
 <rewrite url="~/Yerel/(\d+)/(.+).aspx" to="~/Yerel.aspx?Id=$1"/>
 <rewrite url="~/Uye/(.+)/(\d+)/Takip-Listesi.aspx" to="~/Uye/Takip-Listesi.aspx?Id=$2"/>
 <rewrite url="~/Uye/(.+)/Multimedya-Galeri/(\d+)/(.+).aspx" to="~/Uye/GaleriDetay.aspx?Id=$2"/>
 <rewrite url="~/CMS/Icerik/(.+)/(\d+)/(.+)\.aspx" to="~/CMS/CMSIcerik.aspx?Id=$2"/>
 <rewrite url="~/CMS/Icerik/Ayrinti/(.+)/(\d+)/(.+)\.aspx" to="~/CMS/CMSIcerik.aspx?Id=$2&amp;Ayrinti=1"/>
   

Bu örneklere göre çıkarmanız gereken sonuçlar:

  • sabit bir url i sabit bir sayfaya yönlendirebiliyoruz,
  • url leri istediğimiz formatta kullanabiliyoruz,
  • birden fazla parametre olduğunda bunları $1, $2, .. şeklinde kullanım sırasına göre, yönlendireceğimiz sayfaya parametre olarak aktarabiliyoruz.
  • int tipindeki parametreler için (\d+), string tipindeki parametreler için (.+) kullanıyoruz.
  • yönlendireceğimiz sayfaya birden fazla parametre göndereceksek, paremetreleri $ ile değil &amp; ile ayırıyoruz.

Tüm bu yaptığımız değişikliklerin sonucunda Solution Explorer görünümü şu şekilde olacaktır:

Tüm proje dosyalarını indirmek için buraya tıklayabilirsiniz.

Herkese bol akıllı url li çalışmalar diliyorum. :)