Makale Özeti

Sunucu-istemci modelini kullanan bir uygulama geliştiriyorsanız sunucunun hizmet noktası bilgilerinin (FQDN, IP numarası, protokol, port numarası vs) sık sık değişmesi çok can sıkıcı bir durum oluşturabilir. Zira istemci tarafındaki kaynak kodu veya bu bilgileri bir dosyada tutuyorsanız bu dosyayı güncellenmeniz gerekecektir. .NET Compact Framwork de  ConfigurationManager sınıfının bulunmayışı durumu daha da can sıkıcı bir hale getirebilir. Bu makalemizde bu iki sorunu aynı anda çözecek bir yapı geliştireceğiz.

Makale

Sunucu-istemci modelini kullanan bir uygulama geliştiriyorsanız sunucunun hizmet noktası bilgilerinin (FQDN, IP numarası, protokol, port numarası vs) sık sık değişmesi çok can sıkıcı bir durum oluşturabilir. Zira istemci tarafındaki kaynak kodu veya bu bilgileri bir dosyada tutuyorsanız bu dosyayı güncellenmeniz gerekecektir. .NET Compact Framwork de  ConfigurationManager sınıfının bulunmayışı durumu daha da can sıkıcı bir hale getirebilir. Bu makalemizde bu iki sorunu aynı anda çözecek bir yapı geliştireceğiz.

Bu dönem boyunca (2010 bahar) Mobile and Wireless Systema Lab dersi kapsamında mobil sistemler için uygulama geliştiriyorduk. Benim ekibim Windows Mobile 6.5 üzerinde çalışıyordu, gerçi dönem başında Google Android platformu için “Text-to-speech” ( “konuşabilen” diye tercüme edelim J ) bir RSS okuyucu geliştirdiysek de asıl projemiz  Windows Mobil sistemler içindi. Geliştirme esnasında sunucu tarafı uygulamayı bir çok daha Azure Cloud Computing sistemi ile yerel Windows Server arasında defalarca taşımak zorunda kaldık. Bu durum istemci tarafında çok sorun oluşturmaya başlayınca istemcilerin bu durumdan etkilenmemesi sağlayacak basit bir çözüm geliştirdim.

Öncellikle Windows Mobil sistem için bir konfigürasyon yöneticisi oluşturacağız. Bu yönetici sunucu tarafı hizmet noktası bilgilerini gerekli yerlerden okuyup hafızada saklı tutacak uygulama içindeki diğer modüllerden/katmanlardan herhangi bir ayarın detayları istediğinde, mesela sunucunun ip adresi, bu bilgileri sunacak.

Konfigürasyon yöneticisi bu işlevi üç aşamada gerçekleştirecek:

Adım 1: Yerel konfigürasyon dosyasının okunması:

phase_1.jpg

Bu aşamada konfigürasyon yöneticisi yerel dosyadan hizmet bilgilerinin (serviceSettings) saklandığı dosyanın adresini okuyacak.

string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);

 

string configFile = Path.Combine(appPath, "Settings.config");

      

//make sure that the local setting file exits

if (!File.Exists(configFile))

{

throw new FileNotFoundException(

            string.Format("Application configuration file '{0}' not found.",

configFile));

}

           

XmlDocument xmlDocument = new XmlDocument();

xmlDocument.Load(configFile);

XmlNodeList nodeList = xmlDocument.GetElementsByTagName("serviceSettings");

Settings = new NameValueCollection();

foreach (XmlNode node in nodeList)

{

foreach (XmlNode key in node.ChildNodes)

      {

            Settings.Add(key.Attributes["key"].Value.Trim(),

            key.Attributes["value"].Value.Trim());

      }

}

Örnek bir ayar dosyası şu şekilde olabilir:

Settings.config:

<?xml version="1.0"?>

<configuration>

  <serviceSettings>

    <add key="configurationFile" value="http://pages.cs.wisc.edu/~ilikhan/vnote/service.config" />

  </serviceSettings>

</configuration>

 

Adım 2: Uzak (remote) konfigürasyon dosyasının okunması:

phase_2.jpg

Bu aşamada hizmet bilgilerin saklandığı dosyaya erişip gerekli bilgileri okuyoruz.

xmlDocument = new XmlDocument();

 

string serviceConfigFile = Settings["configurationFile"];

           

//determine location of file ( local or remote)

if (serviceConfigFile.StartsWith("http",

StringComparison.InvariantCultureIgnoreCase) == true)

{

//check the internet connectivity

      if (Program.isDataConnectionAvailable)

      {

            Stream configFileStream = GetFileFromWeb(serviceConfigFile);

            xmlDocument.Load(configFileStream);

}

}

else

{

      xmlDocument.Load(serviceConfigFile);

}

 

nodeList = xmlDocument.GetElementsByTagName("servicePoint");

Settings = new NameValueCollection();

foreach (XmlNode node in nodeList)

{

foreach (XmlNode key in node.ChildNodes)

      {

            Settings.Add(key.Attributes["key"].Value.Trim(),

 key.Attributes["value"].Value.Trim());

}

}

Örnek bir hizmet bilgileri ayar dosyası şu şekilde olabilir:

Settings.config:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <servicePoint>

    <add key="remoteAddress" value="vnote.cloudapp.net" />

    <add key="IPAddress" value="" />

    <add key="protocol" value="tcp" />

    <add key="port" value="14500" />

  </servicePoint>

</configuration>

 

Adım 3: Son olarak gerekli bilgiler üzerinden sunucu tarafındaki uygulamaya bağlanabiliriz

phase_3.jpg

Örnek bir kullanım şu şekilde olabilir:

public static void PrepareConnectionSettings()

{

try

      {

string hostNameOrAddress =

      MobileConfigManager.Settings["remoteAddress"];

               

if (hostNameOrAddress == string.Empty ||

 hostNameOrAddress == null)

szIPSelected = MobileConfigManager.Settings["IPAddress"];

 

            else

            {

                  IPHostEntry ipE = Dns.GetHostEntry(hostNameOrAddress);

                  IPAddress[] IpA = ipE.AddressList;

                  szIPSelected = IpA[0].ToString();

}

 

            szPort = MobileConfigManager.Settings["port"];

            alPort = System.Convert.ToInt16(szPort, 10);

System.Net.IPAddress remoteIPAddress =

                    System.Net.IPAddress.Parse(szIPSelected);

remoteEndPoint =

new System.Net.IPEndPoint(remoteIPAddress, alPort);

}

      catch

      {     }

}

Yukarıdaki kod konfigürasyon yöneticisinde sunucu adını (FQDN) istiyor, eğer sunucu adı belirtilmişse DNS sorgusu yapıp IP Adresini tespit ediyor. Eğer sunucu adı belirtilmemiş ise konfigürasyon yöneticisinden sunucunun IP adresini vermesini istiyor. Gerekli bilgiler elde edilince bağlantıda kullanılacak uç noktayı oluşturuyor.

Oluşturduğumuz sınıfın kaynak kodunun tamamı aşağıdaki gibi olabilir:

    public static class MobileConfigManager

    {

          public static NameValueCollection Settings;

 

          public static void InitializeConfiguration()

          {

                string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);

            //string appPath = Directory.GetCurrentDirectory();

            string configFile = Path.Combine(appPath, "Settings.config");

      

            //make sure that the local setting file exits

            if (!File.Exists(configFile))

            {

                  throw new FileNotFoundException(

                      string.Format("Application configuration file '{0}' not found.", configFile));

            }

           

            XmlDocument xmlDocument = new XmlDocument();

            xmlDocument.Load(configFile);

            XmlNodeList nodeList = xmlDocument.GetElementsByTagName("serviceSettings");

            Settings = new NameValueCollection();

            foreach (XmlNode node in nodeList)

            {

                foreach (XmlNode key in node.ChildNodes)

                {

                    Settings.Add(key.Attributes["key"].Value.Trim(),

                        key.Attributes["value"].Value.Trim());

                }

            }

 

            xmlDocument = new XmlDocument();

 

            string serviceConfigFile = Settings["configurationFile"];

           

            //determine location of file ( local or remote)

            if (serviceConfigFile.StartsWith("http", StringComparison.InvariantCultureIgnoreCase) == true)

            {

                //check the internet connectivity

                if (Program.isDataConnectionAvailable)

                {

                    Stream configFileStream = GetFileFromWeb(serviceConfigFile);

                    xmlDocument.Load(configFileStream);

                }

            }

            else

            {

                xmlDocument.Load(serviceConfigFile);

            }

            nodeList = xmlDocument.GetElementsByTagName("servicePoint");

            Settings = new NameValueCollection();

            foreach (XmlNode node in nodeList)

            {

                foreach (XmlNode key in node.ChildNodes)

                {

                    Settings.Add(key.Attributes["key"].Value.Trim(), key.Attributes["value"].Value.Trim());

                }

            }

       }//end

 

       private static Stream GetFileFromWeb( string configFileURI)

       {

           // Begin the WebRequest to the desired configuration file

           System.Net.WebRequest webRequest = System.Net.WebRequest.Create( configFileURI );

           HttpWebRequest request = webRequest as HttpWebRequest;

           HttpWebResponse response = (HttpWebResponse)request.GetResponse();

          

           // Convert the file into an XML document

           System.IO.Stream configFileStream =  response.GetResponseStream();

           return configFileStream;

       }

 

   }//end of class

 

Tabi bu işlemlerin yerine getirilebilmesi için program açılır açılmaz konfigürasyon yöneticisinin çağrılması gerekmektedir.

MobileConfigManager.InitializeConfiguration();

Bu işin de otomatik yapılmasını istiyorsak InitializeConfiguration() çağrısını bu sınıf için tanımlanan constructor gövdesinde yapabiliriz. Çünkü static bir sınıf tanımladığımız için uygulama çalıştırıldığında bu sınıf otomatik olarak sisteme yüklenecektir. Fakat uzak ayar dosyasının (remote settings) bir web request ile erişilmeye çalıştığımda garip bir şekilde sistemin çok yavaş cevap verdiğini fark ettim. Bu nedenle contructor içinden bu işlemleri yapmak yerine ma programın başlangıcında manuel olatak çağırmak zorunda kaldım. Benim kullandığım sistem  Windows Mobile 6.5, ve Compact Framework 3.5 yüklü. Farklı sürümlerde aynı sorunun olup olmadığını test etmedim.

Özet olarak bu yazımızda mobil sistemler için nasıl basit bir konfigürasyon yöneticisi geliştirmeğimizi, ayrıca istemci tarafında herhangi bir değişiklik yapmadan nasıl sunucumuzu taşıyabileceğimizi öğrendik.

Faydalı olması ümidiyle…

Özcan İLİKHAN

PhD Student
Department of Computer Sciences
University of Wisconsin-Madison
http://pages.cs.wisc.edu/~ilikhan