31 Ekim 2011 Pazartesi

MVC için Elmah ile hata loglama

Problem

MVC uygulamanızda oluşan tüm hataları loglamak ve gerektiğinde listelemek istiyorsunuz. Bunun için tüm muhtemel yerlere try{}catch() blokları yerleştirmeli ve hatayı bir yerlere yazmalısınız. ActionFilter yazarak action'lar içindeki hatayı filter seviyesinde loglayabilirsiniz. Fakat action'lar dışındaki alanlarda oluşan beklenmedik hataları nasıl elde edeceksiniz.

ELMAH Nedir?

ELMAH ( Error Logging Modules And Handlers) çok fazla bilinmeyen açık kaynak kodlu bir ASP.NET projesidir. Çalışma anında beklenmeyen bir durum oluştuğunda loglama için tek satır kodu değiştirmeden tamemen web.config ayaları ile ELMAH sizin yerinize aşağıdaki işlemleri yapacaktır.

  • Bütün beklenmeyen hataları loglama
  • Uzaktan erişilebilir bir web sayfası ile tüm hataları listeleme
  • Uzaktan erişilebilir bir web sayfası ile hata detaylarını gösterme
  • Asp.Net'in sarı hata sayfası yerine özelleştirilmiş hata sayfalarına yönlendirebilme
  • Hata oluştuğunda mail ile bilgilendirme
  • Hata listeleme için RSS bağlantısı
  • Hataları sql server gibi farklı veri kaynaklarında saklayabilme

Nasıl Kullanılır ?

Öncelikle ELMAH'ı kendi projenize kurmanız gerekmektedir. Nuget ile aşağıdaki komutu çalıştırarak kendi projenize ekleyebilirsiniz.

PM> Install-Package elmah

Kurulumdan sonra Nuget gerekli tüm web.config ayarlarını yapacaktır. MVC uygulamanızda elmah.axd sayfasına giderek hata listeleme sayfasına ulaşabilirsiniz.

elmah.axd sayfasının görünümü

ELMAH bu işleri nasıl yapmaktadır. web.config dosyası üzerinde takip edelim.

[xml]

<httpHandlers>
      <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
    </httpHandlers>
    <httpModules>
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
    </httpModules>

Elmah.ErrorLogModule ile Asp.Net işlemi içinde oluşan her beklenmeyen hata kayıt etmektedir. Loglama, mail gönderme ve filtreleme özelliklerini gene web.config üzerinden değiştirebilirsiniz. Oluşan beklenmedik hatalar varsayılan olarak xml dosyalarda tutulmaktadır. Ben örnek olması açısından hataların bir SQL CE veri tabanında tutulmasını göstericeğim. Yapmamız gereken web.config üzerinde ELMAH'a veri saklama yerini değiştirmesini söylemek.

[xml]

<connectionStrings>
    <add name="elmah-sqlservercompact" connectionString="Data Source=|DataDirectory|\Elmah.sdf" />
  </connectionStrings>
  <elmah> 
    <errorLog type="Elmah.SqlServerCompactErrorLog, Elmah" connectionStringName="elmah-sqlservercompact" />
  </elmah>

MVC Entegrasyonu

Hiç bir ekstra geliştirme yapmadan ELMAH çalışacaktır. Fakat MVC ile gelen beklenmeyen hataları yönetem bir HandleErrorAttribute ActionFilter nesmemiz var. Bu attribute MVC uygulamamızda GlobalFilter olarak kayıt edilmektedir. Bunun anlamı MVC ile gelen HandleErrorAttribute tüm Action metotlarımızın için bir merkezi hata yönetim noktasıdır.(Daha fazlası için Aspect Programing, Policy Injection) Action içinde oluşan hatalar HandleErrorAttribute içinde yakalanacağı için ELMAH tarafından listelenemeyecektir. Yapmamız gereken HandleErrorAttribute ile ELMAH'ı konuşturmaktır. Kendi HandleErrorAttribute sınıfmızı yazıp oluşan hatayı ELMAH'a bildirmemiz gerekmektedir.

[cs]

[Export]
    public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
    {
        public override void OnException(ExceptionContext context)
        {
            base.OnException(context);
            if (context.ExceptionHandled)
                ErrorSignal.FromCurrentContext().Raise(context.Exception);
        }
    }

Son olaral Global.asax içinde kendi HandleErrorAttibute sınıfımızı kayıt etmeliyiz.


[cs]

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new Filters.HandleErrorAttribute()); 
        }

Sonuç

ELMAH beklenmeyen hataları loglamak ve yönetmek için açık kaynak kodlu güçlü bir araçtır. Muhtemelen sonraki Asp.Net versiyonlarında framework içinde geleçek bir uygulamadır. MVC ile entegrasyonu oldukça kolaydır. Buradaki örnekte olduğu gibi hata loglarını ayrı bir SQL CE veri tabanında tutmanızı öneriyorum. Zira ELMAH hataların stack bilgisinide sakladığı için hata kayıtları çok fazla yer işgal etmektedir. Ayrı bir SQL CE veri tabanı kullanarak gerektiğinde hata kayıtlarını doğrudann silebilirsiniz.

30 Ekim 2011 Pazar

MVC için Dependency Injection

Problem

MVC mimarisi gereği çok parçalı bir yapıdır. View, controller, filter, model gibi bir çok parçanın bulunduğu ortamda nesneler arasında ki ilişkiyi takip etmek ve performansı korumak bir sorundur. Yani bir DbContext nesnenize her hangi bir view, controller veya filter üzerinden erişebilmek isteriz.

MVC'nin bu karmaşasından arınmanın en iyi yöntemi MVC uygulamıza bir DI mekanizması eklemektir. Dependency Injection(DI) bir ilke veya bir mimari desendir. DI epeyce farklı tanımları vardır. Genel olarak DI uygulama nesneleri arasında nesne bağımlılılarını ve yaşan döngülerini yönetmektedir.

MVC'ye DI nasıl entegre edilir?

MVC çalışma anında ihtiyaç duyduğu tüm nesneleri elde etmek için formal bir yöntem ile geliştirmiştir. MVC IDepenedcyResolver interface uygulaması olan DependencyResolver nesnesi ile birlikte gelmektedir. MVC uygulamanızda oluşturulacak tüm nesneler IDependencyResolver interface ile oluşturulmaktadır. Yani aslında MVC doğrudan DI esteği ile gelmektedir. MVC Uygulamamıza DI entegre etmek için yapmamız gereken IDepenedcyResolver interface uygulayan bir sınıf oluşturmak ve bunu MVC uygulamıza söylemektir.

IDependecyResolver

IDependecyResolver interface'i iki fonksiyona sahiptir.

[cs]

public interface IDependencyResolver
    {
        object GetService(Type serviceType);
        IEnumerable<object> GetServices(Type serviceType);
    }
  • GetService(servicType): Bu fonksiyon bir servis tipini alır ve çözümlemeye çalışır. Eğer istenen servis tipine bağlı servis tanımları varsa uygun servisi üretir ve döndürür. Eğer uygun bir bağımlılık tanımı bulamazsa null döner bu durumda MVC varsayılan davranışına devam eder.
  • GetServices(servicType): Bu mehod servis tipine uygun tüm servis çözümlerini bir koleksiyon (IEnumerable<>) içinde döner. Benzer şekilde eğer servis tipi çözümlenemezse null döner ve MVC'nin varsayılan şekilde çalışmaya devam etmesini sağlar.

DependencyResolver, özel IDependencyResolver nesnemizi kaydetmek için kullandığınız statik bir sınıftır. DependencyResolver.SetResolver ( IDependecyResolver) yöntemini kullanarak uygulamanız için özelleştirdiğiniz IDependencyResolver nesnenizi kurabilirsiniz. Artık MVC uygulamızda üretilen tüm MVC nesneleri özelleştirdiğiniz IDependencyResolver nesnesi üzerinden sağlanacaktır.

CompositionDepencyResolver Oluşturmak

Şimdi basit bir IDependecyResolver örneği yapalım. Tüm MVC nesnelerinin üretileceği Composition kullanan özelleşmiş CompositionDependecyResolver sınıfmız. :

[cs]

[Export(typeof (IDependencyResolver))]
    public class CompositionDependencyResolver
        : IDependencyResolver
    {
        #region IDependencyResolver Members

        public object GetService(Type serviceType)
        {
            try
            {
                IEnumerable<Lazy<object, object>> exports = _container.GetExports(serviceType, null, null);
                if (exports.Any())
                    return exports.First().Value;

                object result = _defaultDependencyResolver.GetService(serviceType);
                if (result != null) _container.ComposeParts(result);
                return result;
            }
            catch (Exception)
            {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            try
            {
                IEnumerable<Lazy<object, object>> exports = _container.GetExports(serviceType, null, null);
                if (exports.Any())
                    return exports.Select(e => e.Value).AsEnumerable();
                IEnumerable<object> result = _defaultDependencyResolver.GetServices(serviceType);
                _container.ComposeParts(result);
                return result;
            }
            catch (Exception)
            {
                return null;
            }
        }

        #endregion
    }


Burada basitçe servis tipi önce katolog üzerinde aranmaktadır. Eğer katalog üzerinde servis tipine ait bir çözüm varsa bu döndürülmektedir. Eğer katalog üzerinde servis tipine ait bir çözüm yoksa MVC'nin varsayılan IDependencyResolver uygulamasına çözüm sorulmaktadır. Varsayılan IDependencyResolver  çözümü bulursa dönen nesne üzerinde tekrar DI işlemi yapılmaktadır.

Şimdi Global.asax içinde özelleştirdiğimiz IDependencyResolver sınıfımızı kayıt edelim:

[cs]

protected void Application_Start()
        {
            var catalog = new AggregateCatalog(new AssemblyCatalog(typeof(PageRepository).Assembly));
            var container = new CompositionDependencyResolver(catalog, DependencyResolver.Current);

            DependencyResolver.SetResolver(container);

Böylelikle tüm MVC nesnelerinizin bağımlılıklarını tek bir sınıf ile çözebilirsiniz.Artık istediğiniz MVC nesnesi ile DI kullanabilirsiniz.

[cs]

[Export, PartCreationPolicy(CreationPolicy.NonShared)]
    public class BlogController : Controller
    {
        private readonly IPageRepository _pageRepository;
        private readonly ILayoutRepository _layoutRepository;
        private readonly ITagRepository _tagRepository;
        private readonly ICommentRepository _commentRepository;
        private readonly ISettingRepository _settingRepository;

        [ImportingConstructor]
        public BlogController(IPageRepository pageRepository, ILayoutRepository layoutRepository, 
            ITagRepository tagRepository, ICommentRepository commentRepository, ISettingRepository settingRepository)
        {
            _pageRepository = pageRepository;
            _layoutRepository = layoutRepository;
            _tagRepository = tagRepository;
            _commentRepository = commentRepository;
            _settingRepository = settingRepository;
        }

Sonuç

MVC  uygulamaları daha fazla DI desteğine ihtiyaç duyduğu için framework IDependecyResolver interface sunmaktadır. Burada özelliştirilmiş bir IDependecyResolver nasıl yazılaçağını inceledik. DI ihtiyaçımızı gene .NET içinde sunulan Composition isim uzayı ile karşıladık.