Makale Özeti

Bu makaleden ASP.NET MVC framework'un önemli yapı taşlarından biri olan Routing kavramını başlangıç seviyesinde inceleyerek, ileri seviye routing makaleleri için bir giriş yapacağız.

Makale

Giriş

Bu makalemizde ASP.NET MVC’nin temel taşlarından biri olan Routing mekanizmasını inceleyeceğiz. ASP.NET özelinde konuyu incelemeden önce genel anlamda Routing ve URL Rewriting arasındaki farka bakmakta yarar var.

Routing ve URL Rewriting kavramlarında temel amaç aynıdır. İki kavramı da uygulamalarımızda adreslerimizi kullanılabilirlik adına kaliteli hale getirmek için kullanırız. Bir adresin kaliteli olmasından kasıt; kısa, kolay yazılabilir ve son kullanıcı için çözümlenebilir olmasıdır. Kaliteli adresler kullanan bir web uygulamasında son kullanıcılar adresleri çözümleyerek, menüyü kullanmadan istedikleri sayfaların adreslerini oluşturabilirler.   

URL rewriting yapısında istek web sunucusuna ulaştığında, web sunucu istek adresini inceleyerek onu sunucu üzerinde bulunan farklı bir adrese iletir. Üretilen cevap yine web sunucu tarafından alınarak istemciye dönülür. Bu yapıda herhangi bir yönlendirme kullanılmadığı için istemci arka tarafta kullanılan adresi görmez. URL rewriting kavramında isteği cevaplayan sayfanın gerçek bir adresi olduğu ve istek sunucu tarafında bu adrese yönlendirildiği için sayfasının bu yapıdan haberi olmaz.

Routing kavramı ise iki yönlü bir yapıya sahiptir. İstemci ulaşmak istediği adresi belirtken, hizmet verecek sayfa da routing kavramından haberdardır ve ne tür adreslere hizmet vereceğini routing sistemine belirtir. Ayrıca bu yapıda hizmet verecek sayfanın gerçek bir adresi olmadığı için routing yapısı haricinde sayfaya direk olarak ulaşma imkanı yoktur.

ASP.NET MVC mimarisinde (tüm mvc mimarilerinde olduğu gibi) istekler web sayfaları tarafından karşılanmaz. Bunun yerine istekler direk olarak bir fonksiyona iletilir. Bu fonksiyona sahip olan sınıf Controller olarak adlandırılır. İsteğin iletildiği fonksiyon ise Action olarak adlandırılır. ASP.NET mimarisinde Routing iki noktada yer alır:

-        Gelen isteğin ilgili Controller sınıfının ilgili Action fonksiyonuna iletilmesini sağlar.

-        Fonksiyon sonrasında istemciye iletilecek adresin oluşmasını sağlar.

Rota Tanımlama

Zorunlu olmamakla birlikte (ASP.NET MVC şablonunda bu şekilde olmasından dolayı) genel olarak URL rotaları Global.asax.cs dosyası içerisinde RegisterRoutes fonksiyonu içerisinde tanımlanır. Yeni bir URL rotası eklemek için bu fonksiyon içerisinde yer alan routes değişkeni üzerinde bulunan MapRoute fonksiyonu çağrılır. Bu metodun en basit çağırımı aşağıdaki şekildedir.

 

routes.MapRoute("Basit", "{controller}/{action}");

 

Bu çağırımda fonksiyon bir isim ve bir URL deseni alır. Bu desen içerisinde küme parantezi ile belirlenmiş bölümlerin her birine URL parametresi denir.

Routing mekanizması gelen bir istek adresi ile var olan URL rotalarının desenlerini eşleştirip, URL parametrelerine karşılık gelen adres bölümlerini parametrelere değer olarak atar.

 

Örnek olarak “/Urun/Getir” şeklindeki bir adres yukarıdaki rota ile eşleştiğinde Routing mekanizması {controller} = “Urun” ve {action} = “Getir” atamalarını otomatik olarak gerçekleştirir.

 

Routing sisteminin çalışabilmesi için ilgili rotaya uyan istekleri hangi Controller sınıfının hangi Action fonksiyonuna ileteceğini çözebilmesi gerekir. Bu noktada ASP.NET MVC’nin temel ilkelerinden biri olan “Convention over configuration” (tanımlama yerine kabul) devreye girer. Routing sistemi çağrılacak Controller sınıfının isminin {controller} URL parametresi ile Action fonksiyonunun isminin ise {action} URL parametresi ile belirtileceğini kabul eder.

Örnek olarak “/Urun/Getir” adresi “{controller}/{action}” deseni ile kullandığında UrunController sınıfının Getir fonksiyonu ile eşleşir.

URL Parametreleri

ASP.NET MVC web uygulamalarımızda yukarıda bahsettiğimiz controller ve action URL parametreleri haricinde de parametreler kullanabiliriz. Bu parametreler Routing sistemi tarafından otomatik olarak Action fonksiyonumuza iletilir. Bir örnek ile inceleyelim.

URL Rotamız: “{controller}/{action}/{id}”

 

HomeController sınıfımızın içerisinde bulunan Content fonksiyonumuzun imzası:
public ActionResult Content(int? id)

 

URL İsteğimiz: /HomeController/Content/3

 

Bu örnekte belirtilen şekilde bir istek geldiğinde Routing sistemi controller ve action URL parametrelerini kullanarak Content fonksiyonunu çağırması gerektiğini bulacaktır. Sonrasında fonksiyonun beklediği parametre ismi ile URL’de tanımlı parametre ismini eşleştirip Controller fonksiyonuna id parametresi olarak 3 değerini iletecektir.

Fonksiyonumuzda neden nullable parametre kullandığımızı varsayılan değerler başlığında inceleteceğiz.

URL parametrelerini incelerken CatchAll URL parametresi yapısına da değinmek gerekiyor. CacthAll yapısı ile c#’da bulunan param keyword’u benzerlik göstermektedir. Bu yapı bir ya da daha fazla URL parametresi ile eşleşebilir. Bu tür parametreler {*parametreismi} şeklinde tanımlanır ve bize URL eşleştirmesinde kolaylık sağlar.

Örnek olarak “{controller}/{action}/{*extra}” şeklinde bir desen aşağıdaki şekilde eşleşmeleri sağlar

Adres

Eşleşme

/Home/Content/2010/12/12

extra = “2010/12/12”

/Home/Content/2010/12/12/

extra = “2010/12/12”

/Home/Content/

extra = “”

 

Geçerli URL Desenleri

Routing sistemimizde URL desenleri tasarlarken uymamız gereken tek bir kural vardır. İki URL parametresi arka arkaya tanımlanamaz, aralarında mutlaka bir ya da daha fazla karakter olması gerekir. Bunun nedeni bu şekilde tanımlanan bir URL deseninde Routing sisteminin hangi parametreye hangi değeri atayacağını çözememesidir.

URL Deseni

Durum

{controller}/{action}/{title}-{author}

Geçerli

{controller}/{action}/Book{title}By{author}

Geçerli

{controller}/{action}/{title}.{author}

Geçerli

{controller}/{action}/{title}{author}

Hatalı

 

Varsayılan Değerler

MapRoute fonksiyonunun bir overload’unu kullanarak URL rotalarımızda URL parametrelerimiz için varsayılan değerler vermemiz mümkün. Varsayılan değerler kullandığımızda gelen istek ile karşılanan ve varsayılan değer atanmış URL parametreleri, URL desenimizde bulunan parametreler ile karşılaştırılır. Bu sayede tam olarak URL desenine uygun olmayan istek adreslerinin de rotalar ile eşleşmesi sağlanır.

Örnek olarak ASP.NET MVC proje şablonunda yer alan URL rotasını inceleyelim.

routes.MapRoute(

"Default", // Route name

"{controller}/{action}/{id}", // URL with parameters

new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults

);

 

Bu rotada desende kullanılan 3 URL parametresi için varsayılan değer tanımlanmış. Bu nedenle aşağıdaki isteklerin hepsi URL rotamız ile eşleşecektir.

İstek adresi

Eşleşme

/

controller = “Home”, action=”Index”, id = “”

/Content

controller=”Content”, action=”Index”, id=””

/Content/Survey

controller=”Content”, action=”Survey”, id=””

/Content/Survey/3

controller=”Content”, action=”Survey”, id=”3”

 

Varsayılan değer tanımlarken dikkat edilmesi gereken kural en sağdan başlayarak URL parametreleri için varsayılan değer tanımlanmalıdır. Ayrıca “/” karakterleri arasında kalan bölümde birden fazla URL parametresi olduğu durumda, bu parametrelerin hepsi için varsayılan değer tanımlanmalıdır.

URL Parametreleri başlığı altındaki örneğimizde Action metodumuzda parametremizi nullable olarak tanımlamış ve açıklamasını buraya adreslemiştik. Yukarıdaki örneğimizde göreceğiniz gibi URL parametrelerimizi optional olarak tanımlayabiliyoruz, böyle bir durumda optional parametrelerimizde karşılık gelen Action parametrelerimizi de nullable olarak tanımlamamız gerekir ki bu tür eşleşmeler sistemimizde hataya neden olmasın.

Kısıtlar

ASP.NET MVC kullanarak bir blog sitesi geliştirdiğimizi düşünelim. Bu site için proje şablonunda hazır gelen rotayı kullanmamız yeterli olacaktır.

routes.MapRoute(

"Default", // Route name

"{controller}/{action}/{id}", // URL with parameters

new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults

);

 

Uygulamamızda /Blog ile var olan blogların listesine erişirken, /Blog/Display/{id} formatı ile belirtilen id'ye sahip kayıtları gösterebiliriz.

Ancak uygulamamızın ana işlevi blog yazılarımızı göstermek olduğu için direk olarak /{id} formatında isteklerde de belirtilen id’li kaydı göstermek istiyoruz. Bunu sağlamak için şu şekilde bir rota yazalım.

routes.MapRoute("BlogPost", "{id}", new { controller = "Blog", action = "Display" });

 

Test ettiğimizde artık /{id} formatında isteklerin /Blog/Display/{id} gibi ilgili Action fonksiyonuna iletildiğini göreceğiz. Ancak artık /Blog adresinin doğru çalışmadığını göreceğiz. Çünkü bu istek de yeni yazdığımız URL rotası ile eşleşecek.

Bunu önlemek için URL rotamıza kısıt ekleyebiliriz. {id} parametresinin her zaman sayısal bir değer olacağını varsayarsak, yeni rotamız için sadece sayısal değerler ile eşleşmesi için kısıtı şu şekilde koyabiliriz.

routes.MapRoute("BlogPost", "{id}", new { controller = "Home", action = "About" }, new { id = @"\d+" });

 

Yeni eklediğimiz parametre ile {id} değişkeni için Regular Expression kullanarak bir kısıt getirdik. Kısıtlar belirlerken RegularExpression haricinde kendi yazdığımız özel kısıt sınıflarını da kullanabiliriz ancak bu konunun detaylarını Routing ile ilgili ileri seviye makalemizde inceleyeceğiz.

İstekleri ASP.NET MVC dışında tutmak

ASP.NET MVC mimarisi var olan ASP.NET mimarisi üzerine kurulu olduğu için, MVC projelerimizde WebForms sayfaları ya da Handler sınıflarımızı kullanmamız mümkün. Ancak büyük ihtimal ile bu sayfaların adresleri var olan URL Rota desenlerimiz ile eşleşeceği için, istekler MVC mimarisi içerisinde karşılanmaya çalışacaktır. Bunun önüne geçebilmek için Routing mekanizmasına bu tür isteklerin MVC ile ilgili olmadığını söylememiz ve bu isteklerin ASP.NET tarafından cevaplanmasına izin vermemiz gerekir.

Routing yapısında gelen isteği MVC ile yönetmek istemediğimiz ifade etmenin iki yöntemi bulunuyor. Bunları birer örnek ile inceleyelim.

İlk olarak root klasör içerisinde bulunan handler sınıflarımızı (*.ashx) MVC mimarisi dışında tutalım.

routes.Add(new Route

    (

         “{resource}.ashx/{*pathInfo}”,

         new StopRoutingHandler()

    ));

Bu URL rotasında “.ashx” ile biten tüm dosyaları yakalayabiliriz. Dikkat ederseniz desenin sonuna bir CatchAll parametresi ekleyerek farklı get isteklerini de yakalıyoruz.

Rotamızda StopRoutingHandler sınıfının bir nesnesini kullanıyoruz. Bu handler sınıfı desene uygun istekleri Routing dışına çıkartmamızı sağlıyor.

İkinci yöntemimizi ise yine root klasöründe bulunan webforms sayfaları için uygulayalım.

routes.IgnoreRoute(“{page}.aspx/{*pathInfo}”);

 

Bu rotamızda da RouteCollection sınıfına bir extension method olarak eklenmiş olarak IgnoreRoute fonksiyonunu kullandık. Extension method’u kullanmanın Routing yapısının çalışması açısından StopRoutingHandler’dan bir farkı bulunmazken bize kolaylık sağladığı da açık.

Routing hatalarını incelemek

Routing yapısında adres çözümlemele işlemi, run time’da gerçekleştiği için uygulamamızda var olan yanlış yönlendirmelerin nedenini bulmakta zorlanabiliriz. Böyle bir durum ile karşılaştığınızda yaptığını isteğin hangi rota ile eşleştiğini ve URL parametrelerinin aldığı değerleri görmeniz işimizi çok kolaylaştıracaktır.

Phil Haack tarafından geliştirilen ASP.NET MVC route debugger kütüphanesi sayesinde bu işlemi kolaylıkla gerçekleştirebiliriz. Kütüphaneyi “NuGet” üzerinden “routedebugger”    paketi ile ya da http://code.haacked.com/mvc-1.0/RouteDebug-Binary.zip adresinden indirebilirsiniz.

Kütüphaneyi kullanmak için uygulamamızın bin klasörüne koymamız ve Global.asax.cs dosyasında bulunan Application_Start fonksiyonunun sonuna aşağıdaki satırı eklememiz yeterli olacaktır.

RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);

 

Bu değişikliği yaptıktan sonra web uygulamamıza gelen tüm istekler ilgili action fonksiyonuna yönlendirilmek yerine Route debugger tarafından karşılanacak ve cevap olarak eşleşme bilgilerini içeren bir sayfa gönderilecektir.

Son Söz

Bu makalemizde ASP.NET MVC mimarisinin önemli parçalarından biri olan Routing yapısına başlangıç seviyesinde bir giriş yaparak Routing hakkındaki ileri seviye makalelerimiz için zemin hazırladık.

Puan vererek makale hakkında geri bildirimlerinizi paylaşırsanız sevinirim.