4 Kasım 2011 Cuma

MVC için SEO - 301 Redirect

Problem

Arama motorları web üzerinden ki aynı içeriğe birden fazla url ile ulaşılması durumunda tekrarlanan içerik olarak algılamaktadır. Arama motorları sitenizi tararken www'li e www'siz iki ayrı içeriği algılarlar. Yani http://www.siteniz.com/sayfa1.html ve http://siteniz.com/sayfa1.html  arama motorları için iki farklı tekrar edilen içeriktir. Bu durumda arama motorları tüm sitenizi tekrar eden içerik olarak algılar ve sitenizin bulunabilirliği azalır.

301 Redirect

Yapmanız gereken sitenizin www'li veya www'siz versiyonundan birini seçip gelen istekleri  diğer versiyona yönlendirmektir. Genellikle www'li versiyon kullanılmaktadır. Bende örnek olarak www'siz versiyondan gelen istekleri www'li versiyona yönlendireceğim. Yapmamız gereken gelen tüm istekleri kontrol edecek bir MvcHandler yazmak. MvcHandler'ımız gelen istekleri kontrol edecek ve istek adresinde www yoksa isteği www'li versiyona yönlendirecek.

Mvc 301 Redirect

   MvcHandler'lar Global.asax içinde yaptığımız Route tanımlarına eklenen nesnelerdir. MvcHandler bir Rooute işlemi çalışırken hangi Controller'ın seçilmesi gerektiğine karar verirler. Bu işlemi yapabilmek için tüm isteklerin başında çalışırlar. Mvc gelen isteğe göre önce hangi Route tanımının çalışması gerektiğine karar verir daha sonra Route içinde tanımlı Handler'larda hangi Controller sınıfının çalışması gerektiğine karar verir. Böyle her hangi bir Router için tüm istekler "Handle" edebileçeğiniz yer MvcHandler'dır.

Bizim problemimizde www'siz gelen tüm istekleri www'li versiyona çevirecek bir MvcHandler yazmamız gerekmektedir.

[cs]

public class RedirectHandler : MvcHandler
    {
        public RedirectHandler(RequestContext requestContext)
            : base(requestContext) { }

        protected override IAsyncResult BeginProcessRequest(
            HttpContext httpContext,
            AsyncCallback callback, object state)
        {

            if (!httpContext.Request.Url.AbsoluteUri.Contains("://www."))
            {

                httpContext.Response.Status = "301 Moved Permanently";
                httpContext.Response.StatusCode = 301;
                httpContext.Response.AppendHeader(
                    "Location",
                    httpContext.Request.Url.AbsoluteUri
                        .Replace("://", "://www.")
                    );
            }

            return base.BeginProcessRequest(
                httpContext, callback, state);
        }
    }

Mvc 301 Redirect

Yukarıda RedirectHandler içinde istek olarak gelen adresde www yoksa www'li versiyonuna yönlendirme yaptık. Fakat yönlendirmeden MVC sitemizde ki Route tanımlarının haberi bulunmamaktadır. Tüm Route tanımlarımıza RedirectHandler'ı kullanmaları gerektiğini söylememiz gerekmektedir. Yani tüm Route tanımlarımıza yazdığımız RedirectHandler ile çalışarak gerekli Controller'ı seçmesini söylemeliyiz.

[cs]

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.IgnoreRoute("Content/{*pathInfo}");

            routes.MapRoute(
                "DefaultController",
                "{controller}/{action}/{id}",
                new {controller = "Home", action = "Index",id=UrlParameter.Optional});

            RouteHandler<RedirectHandler>.Assign(RouteTable.Routes);

Global.asax içinde sitemizde bulunan tüm Route tanımlarına RedirectHandler ekledik. Burada kullandığım RouteHandler sınıfı verilen MvcHandler tipini tüm Route tanımlarına eklemekte sorumludur. Yani RouteHandler sınıfı MvcHandler sınıflarını Route listelerine ekleyebilecek yardımcı bir sınıfdır.

[cs]

public class RouteHandler<THttpHandler> : MvcRouteHandler
    where THttpHandler : MvcHandler
    {
        public RouteHandler(IControllerFactory controllerFactory)
            : base(controllerFactory) { }
        public static void Assign(RouteCollection routes)
        {
            using (routes.GetReadLock())
            {

                var routeHandler
                    = new RouteHandler<THttpHandler>(
                        ControllerBuilder.Current.GetControllerFactory());

                foreach (var route in routes
                    .OfType<Route>()
                    .Where(r => (r.RouteHandler is MvcRouteHandler)))
                {

                    route.RouteHandler = routeHandler;
                }
            }
        }
    }

Sonuç

SEO için aynı içeriğin tek bir adresi olması gerekmektedir. Sitemizin www'siz adreslerini www'li adrese yönlendirmemiz gerekiyor. Bir MvcHandler yazarak bu işlemi gerçekleştirdik. Mvc sitemizde bulunan tüm Router tanımlarına bu handler'ı ekledik. Bu örnekte tüm isteklerde çalışacak bir MvcHandler yazmayı ve sitemizin Router tanımlarına eklemeyi gördük. Ayrıca SEO için önemli bir problemide çözmüş olduk. Ayrıca 301 Redirect mekanizması sadece adrese www eklemek için kullanılmaz. Eski adresin yeni adrese yönlendirilmesi işlemindede kullanılır.

2 Kasım 2011 Çarşamba

MVC için SEO - RSS

Kullanıcılar site içinde ilgilendikleri içerikler güncellendikce haberdar olmak isterler. Sitede yayınlanan beslemelere (RSS  - feed) abone olarak site içeriğini takip ederler. Arama motorlarıda benzer şekilde site değişikliklerini takip etmek için site haritaları ve site beslemelerini kullanırlar. Peki MVC ile geliştirdiğimiz sitemize web içerik besleme özelliğini nasıl entegre edebiliriz?

RSS Nedir ?

RSS (Really Simple Syndication) web içeriği abonelik formatıdır. RSS içeriği sabit bir xml yapısıdır. <rss> tagları arasında sitede yer tüm içeriği sunabilmektesiniz. Site haritası'na (sitemap.xml) göre çok daha gelişmiş ve detaylı bir veri yapısına sahiptir. Yorumlar, resimler, yayın tarihi web master bilgisine kadar geniş bir veri yapısı ile besleme oluşturabilirsiniz.

RSS ve .NET

Bu kadar gelişmiş bir veri yapısını .NET ortamında nasıl yöneteceksiniz. Üstelik veri yapısı benzerde olsa atom gibi farklı bir  abonelik formatı daha  vardır. Tüm bu farklı besleme formatlarını destekleyen bir besleme kaynağı oluşturma araçına ihtiyaçımız var. .NET yer alan SyndicationFeed sınıfı bu ihtiyacımızı karşılamaktadır.  SyndicationFeed nesnesi oluşturup site içeriğinizi feed.items altına doldurarak kendi besleme kaynağınızı oluşturabilirsiniz.

[cs]

public SyndicationFeed CreateFeed()
        {
            var pages = _pageRepository.Query();
            
            var feed = new SyndicationFeed( 
                    _settingRepository[SiteSettingKey.SiteTitle],
                    _settingRepository[SiteSettingKey.SiteSlogan],
                    new Uri(_settingRepository[SiteSettingKey.SiteUrl]));
            feed.Categories.Add(new SyndicationCategory("Blog"));
            feed.Language = "tr-TR";
            feed.Copyright = new TextSyndicationContent(
                string.Format("{0} {1}", DateTime.Now.Year, _settingRepository[SiteSettingKey.SiteTitle]));
            feed.LastUpdatedTime = DateTime.Now;
            feed.Authors.Add(
                    new SyndicationPerson
                        {
                            Name = _settingRepository[SiteSettingKey.SiteTitle], 
                            Email = _settingRepository[SiteSettingKey.Email]
                        });
            var feedItems = new List<SyndicationItem>();
            foreach (var item in pages)
            {
                var sItem = 
                    new SyndicationItem(
                        item.Title,
                        null,  /*content*/
                        new Uri(string.Format("{0}/{1}/{2}/{3}", 
                                _settingRepository[SiteSettingKey.SiteUrl], 
                                item.Published.Year, 
                                item.Published.Month, 
                                item.Slug)))
                        {
                            Summary = new TextSyndicationContent(item.Summary),
                            Id = item.Slug,
                            PublishDate = item.Created
                        };
                sItem.Links.Add(
                    new SyndicationLink
                        {
                            Title = item.Title,
                            Uri =
                                new Uri(string.Format("{0}/{1}/{2}/{3}", 
                                    _settingRepository[SiteSettingKey.SiteUrl], 
                                    item.Published.Year, 
                                    item.Published.Month, 
                                    item.Slug)),
                            Length = item.ContentHtml.Length,
                            MediaType = "html"
                        });
                feedItems.Add(sItem);
            }
            feed.Items = feedItems;
            return feed;
        }

Yukarıdaki örnekte bir besleme kaynağı (feed) oluşturulmuştur. Besleme kaynağı içerisine sitenin başlığı, url bilgisi, dil bilgisi, yazar bilgisi gibi bir çok bilgi eklenmiştir. Daha sonra sitede bulunan sayfalar feed içerisine eklenmiştir. Bu örnekte sayfa içeriğini olduğu gibi besleme kaynağına (feed) gömmek yerine sadece özet bilgisi göndermeyi tercih ettim. Beslemem içerisine hem içeriğin özetini hemde içeriğin linkini ekledim. Böylece sitemize ait içeriği webde paylaşabilecek duruma geldik.

RSS ve MVC

Rss içeriğimizi oluşturduk. Şimdi bu içeriği MVC sitemizde yayınlamamız gerekiyor. Oluşturduğumuz Rss içeriği kendi formatına sahip.  Web browser uygulamasına geri dönen değerin Rss formatına sahip olduğunu söylememiz gerekiyor. Böylece kullanıcı Rss içeriğini düzgün şekilde görüntüleyebilsin.

[cs]

public class SyndicationFeedResult : ContentResult
    {
        public SyndicationFeedResult(SyndicationFeed feed)
        {
            using (var memstream = new MemoryStream())
            using (var writer = new XmlTextWriter(memstream, System.Text.Encoding.UTF8))
            {
                feed.SaveAsRss20(writer);
                writer.Flush();
                memstream.Position = 0;
                Content = new StreamReader(memstream).ReadToEnd();
                ContentType = "application/rss+xml";
            }
        }
    }

Yukarıdaki kod bloğunda MVC ContentResult sınıfından türettiğimiz SyndicationFeedResult sınıfı  vardır. SyndicationFeedResult sınıfı içerik olarak .NET'e ait SyndicationFeed nesnesini Rss  formatında serialize etmektedir. Veri içeriğinin Rss formatında olduğunu söylemektedir.  Tüm Rss içeriğini CreadFeed() ile oluşturduk ve Rss içeriğini istemciye uygun formatta ulaştıracak SyndicationFeedResult sınıfnı yazdık. Bir action metot içinde site beslememizi paylaşabiliriz.

[cs]

public SyndicationFeedResult Feed()
        {
            var feed = CreateFeed(); 
            return new SyndicationFeedResult(feed);
        }

mvc feed görüntüsü

Sonuç

Rss ve Atom gibi web içerik besleme yapıları gelişmiş veri yapıları ile çok detaylı bir şekilde içerik paylaşmamıza olanak sağlamaktadır. Bu gelişmiş veri yapısı ve farklı veri formatları ile uğraşmadan .NET SyndicationFeed sınıfını kullanarak içerik paylaşımı yapabilirsiniz. MVC ile SyndicationFeed sınıfını entegre etmek için yeni bir ActionResult sınıfnı yazmalısınız. Özelleşmiş ActionResult sınıfınız içerik tipini rss olarak ayarlayıp SyndicationFeed içeriğini döndürmelidir.

1 Kasım 2011 Salı

MVC için SEO - Sitemap

MVC ile site geliştrdiniz ve dünyanın bundan haberdar olması istiyorsunuz. Yapmanız gerek sitenizi arama motorlarına kaydetmek. Arama motorlarının sitenizde dolaşabileceği ve sitenizin listelerde üst
sıralarda çıkarabilecek bir yol sunmalısınız. MVC ile geliştirilen bir sitede SEO nasıl yapılır? Burada her yerde bulunabilecek (saylarda özel başlıklar kullanın, meta etiketleri ile sayfa içeri betimleyin, okunabilir linkler verin gibi) SEO önerileri yerine kodlama yapılarak sitenize kazandırmanız gereken özellikleri anlatacağım.
İlk olarak insanların ve arama motorlarının sitenizde rahatça dolaşabilmesi için site haritaları hazırlamanız gerekmektedir. Sitenizde ziyaretcileriniz için mutlaka bir site haritası sayfası veya arşiv sayfası oluşturunuz.

SiteMap.xml

Site Haritaları, web yöneticilerinin arama motorlarını sitelerindeki taranabilir

sayfalar hakkında bilgilendirmeleri için kolay bir yoldur.En basit biçimiyle, bir

Site Haritası, arama motorlarının siteyi daha akıllıca tarayabilmeleri için, her

bir URL'yle ilgili ek meta verilerle (son güncellenme zamanı, genellikle ne sıklıkta

değiştiği ve sitedeki diğer URL'lere göre ne kadar önemli olduğu) birlikte, bir

siteye ilişkin URL'leri listeleyen bir XML dosyasıdır. (sitemaps.org adresinden alıntıdır)
SiteMap.xml dosyası bir çok arama motoru tarafından desteklenen bir endüstri standartıdır. Sitenizin SiteMap.xml protokolüne uygun site haritası sağlaması gerekmektedir.

MVC sitemap.xml

Eğer siteniz için dinamik bir sitemap.xml oluşturmak istiyorsanız ne yapmalısınız ? İlk olarak yapmamız gereken sitemize gelen sitemap.xml dosya isteğini yakalamamız ve bir action'a yönlendirmemiz gerekmektedir. Uygulamanın başlangıçında (Global.asax Application_Start()) içinde sitemap.xml yönlendirmeyi yapalım:
[cs]
public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("robots.txt");
            routes.MapRoute("sitemap", "sitemap.xml", new { controller = "Rss", action = "SiteMap" });
Sitemize gelen sitemap.xml dosyası isteği RssController.SiteMap() action'a yönlendirdik. RssController.SiteMap() action metotdu stansart bir html yerine sitemap.xml protokolüne uygun çıktı üretmelidir. O halde ilk olarak sitemap.xml protokolüne uygun özelleşmiş bir ActionResult oluşturamamız gerekmektedir. Böylece arama motorlarının anlayabileceği bir içerik oluşturmuş olacağız. Sonuç olarak bir xml döndüreceğimiz için XmlWriter sınıfnı çıktı üretmek için kullanabiliriz.
[cs]
public class SitemapActionResult : ActionResult
    {
        private readonly string _siteUrl;
        private readonly List<Page> _pages;

        public SitemapActionResult(string siteUrl, List<Page> pages)
        {
            _siteUrl = siteUrl;
            _pages = pages;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            context.HttpContext.Response.ContentType = "text/xsl";
            using (XmlWriter writer = XmlWriter.Create(context.HttpContext.Response.Output))
            {
                writer.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
                writer.WriteStartElement("url");
                writer.WriteElementString("loc", _siteUrl);
                writer.WriteElementString("lastmod", DateTime.Now.ToString("yyyy-MM-dd"));
                writer.WriteElementString("changefreq", "daily");
                writer.WriteElementString("priority", "1.0");
                writer.WriteEndElement();

                foreach (var page in _pages)
                {
                    writer.WriteStartElement("url");
                    writer.WriteElementString("loc",
                                              string.Format("{0}/{1}/{2}/{3}", _siteUrl, page.Published.Year,
                                                            page.Published.Month, page.Slug)); 
                    writer.WriteElementString("lastmod", page.LastChanged.ToString("yyyy-MM-dd")); 
                    writer.WriteElementString("changefreq", "daily");
                    writer.WriteElementString("priority", "0.5");
                    writer.WriteEndElement();
                } 
                writer.WriteEndElement(); 
                writer.Flush();
                writer.Close();
            }
        }
    }
Böylece protokole uygun çıktı üretip xml forrmatında HttpContext.Response üzerine yazmış olduk. Benim örneğimde sayfaların değişim sıklığı ve önceliklerinin aynı olduğunu kabul edilmiştir. Her bir sayfaya ulaşabilmek için okunabilir bir link verilmiştir. Kendiniz için yukarıdaki ActionResult sınıfnı özelleştirebilirsiniz. Son olarak satimap.xml isteklerini yönlendirdiğimiz RssController.SiteMap() action metodunun SitemapActionResult ile isteklere cevap vermesi gerekmektedir.
[cs]
public ActionResult SiteMap()
        {
            return new SitemapActionResult(
                _settingRepository[SiteSettingKey.SiteUrl],
                _pageRepository.Query()
                    .Where(c => c.IsPublish && c.Published < DateTime.Now)
                    .OrderByDescending(c => c.Published).ToList());
        }
Böylelikle yayında olan sayfaları listeleyip sitemap.xml protokolüne uygun olarak cevap dönmüş olduk.
sitemap.xml görüntüsü

Sonuç

Arama motorlarına sitede bulunan sayfaları listemek ve sayfalar hakkında bilgi vermek için sitemap.xml dosyaları kullanılmaktadır. Buradaki çalışma ile MVC sitenize ait sitemap.xml dosyanızı dinamik olarak üretip kullanabileceğiniz bir yol önerilmiştir. SEO için yapılması gerekenler listesinde önemli bir madde olan sitemap.xml dosyası oluşturmayı çözümlemiş bulunmaktayız.