Debug Edilebilir Windows Service Geliştirmek

blg232_GirisMerhaba Arkadaşlar,

Uzun süre önce dış kaynak(Outsource) olarak görev aldığım bir bankacılık uygulamasında Windows Service tabanlı entegrasyon işlemleri için görevlendirilmiştim. Herşeyden önce bu servislerin bankacılık uygulaması olması nedeniyle, farklı ve yabancı sistemleri de ilgilendiren iş adımları bulunmaktaydı. Bu sebepten söz konusu Windows Service uygulamalarının hem kod içerikleri hem de iş kuralları oldukça karışık olabiliyordu. İlgili Windows Service örneklerinin geliştirilmesi bir yana, bunların test ortamına atılması ve sonuçlarının takip edilmesi ise başlı başına bir dertti Confused smile 

Genellikle servisin çalışma durumunu izlemek adına özellikle Exception bloklarında veya metod başlangıç ile bitiş noktalarında(örneğin OnStart başında ve sonunda) işletim sisteminin uygulamaya özel Event Log’ larına bilgi atmaktaydım. Bu bilgileri atarken de durumun kritikliğine göre Warning, Exclamation, Error gibi hazır sistem ikonlarından yararlanıyor ve çalışma zamanındaki durumu analiz etmeye çalışıyordum.

Ancak bir developer için, kodun çalışma zamanındaki durumunu incelemenin sayısız yolu olduğu da bir gerçek. Öyleki, Debug etmek bence en güzel yollardan birisi. Lakin bir Windows Service uygulamasının Debug edilmesi de sanıldığı kadar kolay değil Thinking smile  İşte bu yazımızda internet üzerinden yaptığım araştırmalar sonucu öğrendiğim ve bir Windows Service uygulamasının nasıl debug edilebeceğine dair uygulanabilen yöntemlerden birisini ele alıyor olacağız. Olabildiğince basit bir şekilde anlatmaya gayret edeceğim bu vaka çalışmamızda, adım adım ilerliyor olacağızWinking smile Öyleyse gelin şu metal entegre üzerindeki böcekleri ayıklamaya çalışalım.

İlk olarak Windows Service Application tipinden bir uygulama oluşturmamız gerekiyor. Bu uygulama içerisinde yer alan ControllerService isimli Windows Service tipinin içeriği başlangıçta aşağıdaki gibidir. Söz konusu servis içerisindeki Timer örneğinin 10 saniyede bir çalıştırdığı olay metoduna göre, belirli bir klasördeki(ki path bilgisi App.config dosyasından çekilmektedir) dosyaların şifrelenmesi için bir akış çalıştırmaktadır ki aslında bunun konumuz için çok da büyük bir önemi yoktur Winking smile

using System.Configuration; 
using System.IO; 
using System.ServiceProcess; 
using System.Timers;

namespace FileControllerService 
{ 
    public partial class ControllerService 
        : ServiceBase 
    { 
        private string _path = null; 
        private Timer _timer = null;

        public ControllerService() 
        { 
            InitializeComponent(); 
            _path = ConfigurationManager.AppSettings["Path"]; 
            _timer = new Timer(10000); 
            _timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed); 
        }

        void _timer_Elapsed(object sender, ElapsedEventArgs e) 
        { 
            string[] files=Directory.GetFiles(_path); 
            foreach (string file in files) 
            { 
                File.Encrypt(file); 
            } 
        } 
        protected override void OnStart(string[] args) 
        { 
            _timer.Start(); 
        }

        protected override void OnStop() 
        { 
            _timer.Stop(); 
        } 
    } 
}

Bir Windows Service tipi temel olarak ServiceBase tipinden türemektedir. Windows Service Application proje şablonunda yer alan bu hazır uyarlamaya göre Program sınıfı içerisindeki Main metodunun kod yapısı da aşağıdaki gibi olacaktır.

using System; 
using System.Configuration; 
using System.ServiceProcess; 
using InvestigationLib;

namespace FileControllerService 
{ 
    static class Program 
    { 
        static void Main() 
        { 
            ServiceBase[] ServicesToRun; 
            ServicesToRun = new ServiceBase[] 
            { 
                new ControllerService() 
            }; 
            ServiceBase.Run(ServicesToRun); 
        } 
    } 
}

Dikkat edileceği üzere Servisin çalıştırılması işini ServiceBase tipinin static Run metodu üstlenmektedir. Ne varki bu tip bir servisin varsayılan olarak Debug edilmesi mümkün değildir. Bir başka deyişle servise ait OnStart ve OnStop gibi metodların içerisine girilememektedir. Bu, özellikle karmaşık iş kuralları ve modelleri içeren Windows Service örnekleri düşünüldüğünde, geliştirme sürecini zorlaştıran ve uzatan bir durumun oluşması anlamına gelmektedir. Elbette servisin pek çok noktasından örneğin işletim sistemi Event Log’ larına bilgi gönderilebilir veya bir Text dosyası içerisine servisin çalışması sırasındaki sürecin önemli adımlarına ait bilgiler(mesela Exceptipon mesajları) yazdırılabilir. Ancak değişken, parametre ve metod sayılarının iyiden iyiye fazlalaştığı, referans edilen kütüphanelerin çok olduğu bir modelde bu içerikleri izlemekte, üretmekte zor olmaktadır.

Aslında bir geliştirici için kodun yazılması ve test ortamına alınması esnasında debug edilebilyor olması son derece önemlidir. Şimdi dilerseniz yazımıza konu olan bu basit Windows Service örneğinin debug edilebilir bir versiyonunu nasıl geliştirebileceğimize bir bakalım Open-mouthed smile Durumu anlamanın en kolay yolu bitmiş olan örneğin sınıf diagramına bakmak olacaktır(Yani çözüme biraz tersten bakmamız gerekmektedir) İşte çözümsel yaklaşıma ait sınıf diagramı görüntüsü.

blg232_ClassDiagram

Şimdi bu sınıf diagramını adım adım üretmeye başlayalım. İlk olarak bir arayüz sözleşmesi(Interface Contract) oluşturarak işe başlayacağız. Buradaki en büyük amacımız birden fazla Windows Service tipinin Debug edilebilir versiyonları için ortak bir sözleşme sunmaktır.

namespace InvestigationLib 
{ 
    public interface IWindowsServiceContract 
    { 
        void OnStart(string[] args); 
        void OnStop(); 
    } 
}

IWindowsServiceContract arayüzünün içinde iki basit metod bildirimi olduğu görülmektedir. Örneğimizi çok basit tutmak istediğimizden OnPause, OnContinue, OnShutdown gibi metod bildirimlerini göz önüne almadık. Ancak siz kendi denemelerinizi yaparken veya bir ürün geliştirmesi sırasında bu tekniği uygularken mutlaka söz konusu diğer metodları da düşünmelisiniz. Aslında yaptığımız bir anlamda ServiceBase ile override edilen metodlara ait bir sözleşme bildiriminde bulunmaktan ibarettir.

Bu sözleşme bildiriminin ardından ServiceBase türevli olan ve IWindowsServiceContract arayüzünü implemente eden tiplerin fonksiyonelliklerini çağırabilen başka bir sınıf daha üretilir. WindowsServiceCaller

using System.ServiceProcess;

namespace InvestigationLib 
{ 
    public class WindowsServiceCaller 
        :ServiceBase 
    { 
        IWindowsServiceContract _winSrv;

        public WindowsServiceCaller(IWindowsServiceContract winSrv)            
        { 
            _winSrv = winSrv;   
        } 
        protected override void OnStart(string[] args) 
        { 
            _winSrv.OnStart(args); 
        } 
        protected override void OnStop() 
        { 
            _winSrv.OnStop(); 
        } 
    } 
}

WindowsServiceCallar tipi için söylenebilecek iki önemli nokta vardır. Bunlardan ilki ServiceBase tipinden türemiş olması ve senaryomuza göre OnStart ile OnStop metodlarını override etmesidir. Diğer yandan ezilen OnStart ve OnStop metodları içerisinden yapılan çağrılar, tipin yapıcı metodu(Constructor) içerisinden alınan IWindowsServiceCaller arayüzünü implemente eden herhangibir nesne örneğine aittir. Bir başka deyişle bu tip, OnStart ve OnStop metod bildirimlerini sözleşme olarak sunan IWindowsServiceContract arayüzünü implemente eden bir tipin, çalışma zamanındaki asıl OnStart ve OnStop fonksiyonelliklerini kullanmaktadır. (Oh oh ohhh!!! Including var, inheritance var, Polimorphysm var, overriding var Open-mouthed smileNesne Yönelimli Programlama-Object Oriented Programming temellerini hatırlamanın zamanı )

Dolayısıyla bu tanımlamanın ardınan debug edilmesi gereken kod içeriğini taşıyan asıl servis tipi geliştirilir ki tesadüf bu olsa gerek bu tipte IWindowsServiceContract arayüzünü implemente etmektedir Open-mouthed smile

using System; 
using System.IO; 
using System.Timers;

namespace InvestigationLib 
{ 
    public class DebugableControllerService 
        :IWindowsServiceContract 
    { 
        string path=String.Empty; 
        Timer timer = null;

        public DebugableControllerService() 
        { 
            timer = new Timer(10000); 
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed); 
        }

        void timer_Elapsed(object sender, ElapsedEventArgs e) 
        { 
            try 
            { 
                string[] files = Directory.GetFiles(path); 
                foreach (string file in files) 
                { 
                    File.Encrypt(file); 
                } 
            } 
            catch (Exception excp) 
            { 
                //TODO:Handle Exception 
                // Eğer Windows Service projesinin özelliklerinden Output Type -> Console Application olarak seçilirse buradan Console penceresine de bilgi yazdırılabilir 
            } 
        }

        #region IWindowsServiceContract Members

        public void OnStart(string[] args) 
        { 
            path = args[0]; 
            timer.Start(); 
        }

        public void OnStop() 
        { 
            timer.Stop(); 
        }

        #endregion 
    } 
}

DebugableControllerService sınıfı aslında ilk başta debug edemediğimiz ControllerService sınıfının asıl fonksiyonelliklerini içermektedir. Tabi ControllerService tipinin yaptığı gibi ServiceBase’ den türemek yerine IWindowsServiceContract arayüzünü uygulamaktadır. Böylece WindowsServiceCaller tipinin kullanabileceği bir sınıf oluşturulmuştur. Ancak hazırlıklar bu tipleri yazmakla bitmez. Birde söz konusu debug edilecek tip örneğini başlatacak/yürütüecek bir başka sınıfın olmasında yarar vardır Crying face İşte başlatıcı tipimiz.

using System.Threading;

namespace InvestigationLib 
{ 
    public static class ServiceStarter 
    { 
        public static void Run(string[] args,IWindowsServiceContract winSrv) 
        { 
            winSrv.OnStart(args); 
            Thread.Sleep(45000); 
            winSrv.OnStop();            
        } 
    } 
}

Bu static sınıf içerisindeki Run metodu IWindowsServiceContract arayüzünü implemente eden her hangibir tip üzerinden OnStart ve OnStop metodlarını çağırabilir ve böylece simülasyon işlemini başlatabilir. Geriye yapılması gereken tek bir işlem kalmaktadır. Windows Service projesindeki Main metodu içeriğini aşağıdaki kod parçasında görüldüğü gibi değiştirmek.

using System; 
using System.Configuration; 
using System.ServiceProcess; 
using InvestigationLib;

namespace FileControllerService 
{ 
    static class Program 
    { 
        static void Main() 
        { 
            #region Debug Edilebilir Versiyon 
           
            DebugableControllerService implementation = new DebugableControllerService(); 
            if (Environment.UserInteractive) 
                ServiceStarter.Run( 
                    new string[]{ 
                        ConfigurationManager.AppSettings["Path"]                        
                    } 
                    , implementation); 
            else 
                ServiceBase.Run(new WindowsServiceCaller(implementation));

            #endregion 
        } 
    } 
}

Debug edilmek istenen kod içeriğini taşıyan DebugableControllerService örneğinin oluşturulmasından sonra ServiceStarter tipinin Run metodundan yararlanarak sürecin başlatılması sağlanır. Şu aşamada kod içerisinde breakpoint’ ler yardımıyla ilerlenmesi mümkündür Laughing out loud Hatta örneğin olmayan bir klasör içeriğinin Directory tipinin GetFiles metodu yardımıyla okunmaya çalışılması sırasında oluşan Exception mesajları da, Visual Studio arabiriminin Output ekranına düşmektedir. Aynen aşağıdaki ekran çıktısında görüldüğü gibi.

blg231_DebugWindow

Ancak istenirse işlemlerin daha kolay takip edilmesi adına Console penceresine bilgi yazdırılması da mümkün olabilir. Bunun için Windows Service projesinin Output tipinin Console Application olarak değiştirilmesi yeterlidir.

blg232_ConsoleOutput

Buna göre Exception bloğunu aşağıdaki gibi değişitirsek debug işlemleri sırasında Console ekranına çıktı üretebilmeyi de sağlayabiliriz.

void timer_Elapsed(object sender, ElapsedEventArgs e) 
{ 
    try 
    { 
        string[] files = Directory.GetFiles(path); 
        foreach (string file in files) 
        { 
            File.Encrypt(file); 
        } 
    } 
    catch (Exception excp) 
    { 
        Console.WriteLine(excp.Message); 
    } 
}

blg232_WriteToConsole

Elbette Debug işlemleri sonrasında hataları bertaraf edilen servis kodunun son hali asıl Windows Service kodu ile değiştirilmeli ve bu şekilde install edilmelidir. Örneğimiz bu haliyle artık Console penceresine bilgilendirme de bulunabilir. Ne Text dosyaya, ne işletim sistemindeki Event Log’ lara ne veritabanı üzerindeki ilgili tablolara loglama işlemleri yapmak için gerekli atraksiyonlar ile uğraşmamıza gerek kalmamaktadır. Uygulamanın dışına çıkmadan, olduğumuz Visual Studio ortamı(Environment) içerisinden gerekli izleme ve Debug işlemleri yapılabilir. Doğrudan Console penceresinden gerekli izlemeler de yapılabilecektir. Bunu da unutmayalım Winking smile Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

FileControllerService.rar (51,95 kb)

Yorumlar (4) -

  • merhaba, güzel makale hazırlamışsınız ama yinede tam olarak bazı şeyleri anlayamadım. mesela farz edelim benim elimde "C:\test.exe" konumunda test.exe diye bir servis uygulaması var diyelim, bunu başlatmak için yukardaki kodun neresine eklemem lazım bu test.exe dosyasını.
  • Anladığım kadarıyla asıl servis projesi değil debug olan, yani ben servise ne yazarsam aynını lib projesine mi yazacağım anlayamadım tam. Öyle olacak ise Unity tesst ile yapmaktan farkı yok ve kontratlara falan gerek olmayacak. Eminim öyle değildir de..nasıl debug başlatıldığınıda gösterseydiniz keşke. Bir diğer yöntem de servisi local e kurup, services den start edip vs üzerinden debug dan Attach yapmak ..değinilse güzel olur gibi. Ellerinize sağık.
  • yıllar önce (yine bu makaleden çok sonra) bir windows service uygulaması geliştirdiğimde bu kadar komplex bir yol değildi. daha kolay bir yöntemle çözmüştüm. Şimdi tekrar lazım oldu bu makale ile başladım. fakat çok bir çözüm olduğunu söyleyemem, çünkü böyle bir komplex yapıyı ben entegre etmem 2 insert yapıcam. döviz kuru çekicem.
    • Aslında günümüz teknolojileri düşünüldüğünde döviz kuru çekip 2 insert yapmanız için Windows Service yazmanıza da gerek olmayabilir. Diğer yandan bahsettiğiniz "daha basit" olan yöntemi paylaşabilirseniz yada referans verebilirseniz hem ben hem de okurlar bilgilenmiş olurlar.

Yorum ekle

Loading