Bir İş Görüşmesinde Detaylı İstenen Proje Bölüm1
Selamlar;
Bugüne kadar hep teknik konular üzerinde kodlar yazdık. Ama gerçek hayat dışarıda. Bugün biraz da olsa gerçek yaşamdan bir kesit olarak bir iş görüşmesinde birebir karşılaştığım, firmanın benden istediği bir projeyi kodlayacağız. Normalde bu tarz görüşmeleri kabul etmememe rağmen sırf makalesini yazıp sizle de paylaşmak için bir hafta sonumu verip yazdım. Zaten projeyi teslim etmem için bana verilen süre de 48 saatdi. Sanırım bu zamandan sonra proje kendi kendini imha ediyor:)
Öncelikle gelin neler isteniyor maddeler halinde yazalım. Not: Tüm istenenler yani dökümanlar ingilizce idi. Ben aşağıya madde madde tükçelerini yazıyorum:
Kurların oranlarının gösterildiği TCMB servisine ilgili linkden ulaşabilirsiniz: http://www.tcmb.gov.tr/kurlar/kur2015_tr.html
Yapılması Gereken İşler:
- Birkaç XML’i download ediniz.
- Bu Xml dosyaları Parse ediniz.
- Basit bir UI form hazırlayıp gelen “Kur İsmlerini” Türkçeden İngilizceye çevirerek gösteriniz. TUR->ENG. Örnek(ABD DOLAR->USA Dollar İNGİLİZ STERLİNİ -> Great Britain Pound)
- Dataları sürekli Database’den dinleyip güncellenenleri, Jquery kullanarak Mvc bir sayfada gösteriniz.
- Dataları sürekli Database’den dinleyip güncellenenleri, SignalR kullanarak Mvc bir sayfada gösteriniz.
Aşşağıdaki Teknolojileri Kullanmak Artıdır:
- Asp.Net Mvc
- Entity Framework
- Jquery
- SignalR
- Sql Express/Server
- Design Pattern usage
Önemli:
- Projeyi tamamen bitirmenizi beklemiyoruz. Çözüme yaklaşımınızı görmek istiyoruz. Uygulamaya yorumlarınızı istediğiniz gibi eklemekde özgürsünüz.
- Toplam 48 saat süreniz vardır. Eğer uzayacak veya daha fazla zaman isterseniz bizi bilgilendirmekte özgürsünüz.
İyi şanslar.
Şimdi istenenleri gördüğümüze göre ben değişen kurları yakalama adına güncel kurların döndüğü sayfayı parse ettim. Yani: http://www.tcmb.gov.tr/kurlar/today.xml kullandım. Böylece bu sayfa üzerinden anlık değişen kurları yakalamak bana daha anlamlı geldi.
1-) İlgili today.xml’i download ettikten sonra bir text editor’ü ile açıp aşağıdaki stylesheet tanımlama satırnı temizleyip yeniden kaydettim. Burada amaç Xml sayfasını, yukarıda görüldüğü gibi sade hale getirmektir. Böylece element adları hem daha kolay hem de ayrıntılı bir şekilde gözükmektedir.
1 |
<?xml-stylesheet type="text/xsl" href="isokur.xsl"?> |
2-) Bu projede CodeFirst kullanılmıştır. Buna göre servisden dönen alanlara göre, DAL projesi altında poco nesnesi aşağıdaki gibi oluşturulur. Tablonun adı “CurrencyReport“‘dur.”[Table(“”)]” attribute ile atanmıştır. Arama işlemi “CurrencyName“‘e göre yapıldığı için Index “[Key]” attribute’ü ilgili kolona atanmıştır.
CurrencyReport.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
namespace DAL { using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.Spatial; [Table("CurrencyReport")] public partial class CurrencyReport { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } [Key] [StringLength(100)] public string CurrencyName { get; set; } public decimal? ForexBuying { get; set; } public decimal? ForexSelling { get; set; } public decimal? BanknoteBuying { get; set; } public decimal? BanknoteSelling { get; set; } public decimal? CrossRateUSD { get; set; } public DateTime? CreatedDate { get; set; } } } |
CurrencyContext.cs: Database adı “Currency” olarak aşağıdaki DbContext’de tanımlanmıştır. Ayrıca tablodaki fieldlara ait configuration propertyler “OnModelCreating()” methodunda aşağıdaki gibi tanımlanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
namespace DAL { using System; using System.Data.Entity; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; public partial class CurrencyContext : DbContext { public CurrencyContext() : base("name=CurrencyContext") { } public virtual DbSet<CurrencyReport> CurrencyReport { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<CurrencyReport>() .Property(e => e.CurrencyName) .IsUnicode(false); modelBuilder.Entity<CurrencyReport>() .Property(e => e.ForexBuying) .HasPrecision(18, 5); modelBuilder.Entity<CurrencyReport>() .Property(e => e.ForexSelling) .HasPrecision(18, 5); modelBuilder.Entity<CurrencyReport>() .Property(e => e.BanknoteBuying) .HasPrecision(18, 5); modelBuilder.Entity<CurrencyReport>() .Property(e => e.BanknoteSelling) .HasPrecision(18, 5); modelBuilder.Entity<CurrencyReport>() .Property(e => e.CrossRateUSD) .HasPrecision(18, 5); } } } |
İlgili Poco’dan oluşan Currency Database’indeki CurrencyReport tablosu aşağıdaki gibidir:
3-) Şimdi sıra geldi ilgili Xml’i parse edip database’e atmaya. Bu işlem için CurrencyWindowsServices adında bir WindowsService projesi oluşturulur. Amacı belli zaman aralıkları ile (1/dk) ilgili servise gidip kontrol işlemi yapmasıdır.
Program.cs: İlgili servis’in Debug modda test edilebilmesi için “#if DEBUG” ile uygulamanın debug modda çalıştırılması durumunda Service1() adlı sınıf çağrılmaktadır. Release modda ise servis normalde çalışması gerektiği gibi işlemleri başlatmaktadır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
using System; using System.Collections.Generic; using System.Linq; using System.ServiceProcess; using System.Text; using System.Threading.Tasks; namespace CurrencyWindowsServices { static class Program { /// <summary> /// The main entry point for the application. /// </summary> static void Main() { #if DEBUG var service = new Service1(); service.OnDebug(); System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); #else try { ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new Service1() }; ServiceBase.Run(ServicesToRun); } catch (Exception ex) { } #endif } } } |
Service1.cs(Timer): Aşağıda görüldüğü gibi dakikada 1 kere(60000) çalışacak timer tanımlanmış ve her çalışmasında “GetCurrencyData()” methodu asenkron olarak çağrılmıştır. Önemli bir nokta: Windows service uygulamalarda timer runtime’da aşağıda görüldüğü gibi yaratılmalıdır. Sürükle bırak şeklinde design time’da eklenen timer windows service’de hatalı çalışır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
public Service1() { InitializeComponent(); //GetCurrencyData(); } public void OnDebug() { OnStart(null); } protected override void OnStart(string[] args) { CurrencyTimer.Start(); } protected override void OnStop() { } private Timer currencyTimer; private Timer CurrencyTimer { get { if (currencyTimer == null) { currencyTimer = new Timer(); currencyTimer.Interval = 60000; currencyTimer.Enabled = true; currencyTimer.Elapsed += CurrencyTimer_Elapsed; } return currencyTimer; } set { currencyTimer = value; } } private async void CurrencyTimer_Elapsed(object sender, ElapsedEventArgs e) { CurrencyTimer.Stop(); try { await GetCurrencyData(); } catch (Exception ex) { } CurrencyTimer.Start(); } |
GetCurrencyData(): Timerla her dakikada bir çağrılan method öncelikle ilgili “today.xml”‘i “WebClient()” ile asenkron olarak donwload etmekte daha sonra “XmlDocument” ile bir “DataSet“‘e doldurmaktadır.
- Databasede herhangi bir kaydın olup olmadığı durumuna bakılarak, boş olması durumunda tüm kayıt tek tek gezilerek parse edilir ve “CurrencyReport” tablosuna doldurulur. Daha sonra Entitiy Framework kullanılarak ilgili tablo kaydedilir. Son olarak her eklenen yeni kayıt “NewDatas” listesine eklenir ve daha sonra bahsedeceğimiz “Currency” signalR Hub sınıfındaki “FillData()” methodunu “HubConnection” ve “IHubProxy” nesneleri yardımı ile parametre olarak gönderilir. Bu sayede “FillData()” methodu asenkron olarak tetiklenmesi ve tüm clientlarda yeni eklenen verilerin real time olarak gözükmesi sağlanır.
- Eğer databasede önceden kayıt var ise performansı arttırmak için çekilen tüm datalar “List<CurrencyReport> cacheDatas” türünde bir Liste’ye atılır. Tabi eğer Liste null ise bu işlem gerçekleştirilir. Nesnenin amacı servisden çekilen datalar ile önceden kayıtlı olan dataların birbirleri ile kıyaslanıp değişen dataların tespit edilmesi ve bu sayede her seferinde önceki datalar için database’e gidilmemesinin sağlanmasıdır. Böylece performance artışı sağlanmıştır. Yeni çekilen her bir data tek tek gezilip “CurrencyReport” modeli doldurulmakta ve “isDataChange()” methodu ile linq kullanılarak kayıtlı datalara göre değişiklik olup olmadığna bakılmaktadır. Eğer değişiklik var ise “changeDatas” listesine ilgili değişen kayıtlar eklenmektedir. Daha sonra herbir değişen data tek tek CurrencyRepeort tablosunda bulunarak güncellenir. Sadece değişen datalar “Currency” signalR Hub sınıfındaki “FillData()” methodunu “HubConnection” ve “IHubProxy” nesneleri yardımı ile parametre olarak gönderilerek asenkron olarak tetiklenmesi ve tüm clientlarda sadece değişen dataların real time olarak güncellenmesi performans açısından yapılan önemli bir adımdır. Ayrıca eski datalar değiştiği için “FillCache()” methodu ile cache nesnesi olan “changeDatas” güncellenmiş yani en son datalar ile databeseden tekrardan doldurulmuşdur. Böylece bir sonraki kıyaslama zamanında servisden gelen yeni datalar, güncellenmiş enson datalar ile karşılaştırılabilecektir.
- HubConnection: SignalR sınıfının bulunduğu sunucuya bağlanmayı sağlar.
- IHubProxy: İşlem yapılacak SignalR hub sınıfına erişimi sağlar. “Invoke()” methodu ile ilgili sınıfın methodu triger edilir yani tetiklenir. Böylece bağlı olan clientlardaki javascript function’ı server side taraftan client side tarafa doğru olmak üzere erişilip call edilmiş olunur.
- HubConnection.Start() methoduna dikkat edilir ise asenkrondur. Nedeni ilgili Hub sunucusuna erişimin ne kadar süreceğinin bilinmemesidir. Doğal olarak “GetCurrencyData()” methodunun da asenkron olmak zorunludur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
public async Task GetCurrencyData() { try { //Bütün currencyData'yı servisden alıyoruz. Uri uri = new Uri("http://www.tcmb.gov.tr/kurlar/today.xml"); string xmlStr; using (var wc = new WebClient()) { xmlStr = await wc.DownloadStringTaskAsync(uri); } var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xmlStr); //Parse işlemini Xpath ile yapabilirdim. Ama bu yöntem çok daha kolay:).. DataSet ds = new DataSet(); ds.ReadXml(new XmlNodeReader(xmlDoc)); //Bütün datayı çektik--------------------------- //DataBase'de herhangi bir kayıt var mı diye bakıyoruz? bool anyDataOnDb = false; using (CurrencyContext entity = new CurrencyContext()) { anyDataOnDb = entity.CurrencyReport.Any(); //Eğer database'de önceden kayıt var ise Update işlemi yapıyoruz... if (anyDataOnDb) { if (cacheDatas == null) { FillCache(); } changeDatas.Clear(); foreach (DataRow drow in ds.Tables[1].Rows) { if (drow["Isim"].ToString().Trim() != "") { CurrencyReport report = new CurrencyReport(); report.CurrencyName = drow["Isim"].ToString(); report.ForexBuying = Decimal.Parse(drow["ForexBuying"].ToString() != "" ? drow["ForexBuying"].ToString().Replace(".", ",") : "0"); report.ForexSelling = Decimal.Parse(drow["ForexSelling"].ToString() != "" ? drow["ForexSelling"].ToString().Replace(".", ",") : "0"); report.BanknoteBuying = Decimal.Parse(drow["BanknoteBuying"].ToString() != "" ? drow["BanknoteBuying"].ToString().Replace(".", ",") : "0"); report.BanknoteSelling = Decimal.Parse(drow["BanknoteSelling"].ToString() != "" ? drow["BanknoteSelling"].ToString().Replace(".", ",") : "0"); report.CrossRateUSD = Decimal.Parse(drow["CrossRateUSD"].ToString() != "" ? drow["CrossRateUSD"].ToString().Replace(".", ",") : "0"); if (!isDataChange(report)) { changeDatas.Add(report); } } } foreach (CurrencyReport report in changeDatas) { var changedData = entity.CurrencyReport.Find(report.CurrencyName); changedData.ForexBuying = report.ForexBuying; changedData.ForexSelling = report.ForexSelling; changedData.BanknoteBuying = report.BanknoteBuying; changedData.BanknoteSelling = report.BanknoteSelling; changedData.CrossRateUSD = report.CrossRateUSD; changedData.CreatedDate = DateTime.Now; } if (changeDatas.Any()) { entity.SaveChanges(); //Eğer değişen data var ise bağlı olan tüm Clientlar SignalR ile tetiklenerek sadece değişen datalar gönderilerek bilgilendirilir. HubConnection hubConnection = new HubConnection("http://localhost:1646"); IHubProxy hubProxy = hubConnection.CreateHubProxy("Currency"); await hubConnection.Start(new LongPollingTransport()); hubProxy.Invoke("UpdateData", changeDatas); //----------------- FillCache(); } } else { List<CurrencyReport> NewDatas = new List<CurrencyReport>(); //Eper databasede herhangi bir data yok ise çekilen tüm Data Insert edilir......... foreach (DataRow drow in ds.Tables[1].Rows) { if (drow["Isim"].ToString().Trim() != "") { CurrencyReport report = new CurrencyReport(); report.CurrencyName = drow["Isim"].ToString(); report.ForexBuying = Decimal.Parse(drow["ForexBuying"].ToString() != "" ? drow["ForexBuying"].ToString().Replace(".", ",") : "0"); report.ForexSelling = Decimal.Parse(drow["ForexSelling"].ToString() != "" ? drow["ForexSelling"].ToString().Replace(".", ",") : "0"); report.BanknoteBuying = Decimal.Parse(drow["BanknoteBuying"].ToString() != "" ? drow["BanknoteBuying"].ToString().Replace(".", ",") : "0"); report.BanknoteSelling = Decimal.Parse(drow["BanknoteSelling"].ToString() != "" ? drow["BanknoteSelling"].ToString().Replace(".", ",") : "0"); report.CrossRateUSD = Decimal.Parse(drow["CrossRateUSD"].ToString() != "" ? drow["CrossRateUSD"].ToString().Replace(".", ",") : "0"); report.CreatedDate = DateTime.Now; entity.CurrencyReport.Add(report); NewDatas.Add(report); } } entity.SaveChanges(); //Tüm Clientlar SignalR kullanılarak ilgili hub sınıfına ait method tetiklenerek yeni gelen tüm datalar real time olarak gösterilir. HubConnection hubConnection = new HubConnection("http://localhost:1646"); IHubProxy hubProxy = hubConnection.CreateHubProxy("Currency"); await hubConnection.Start(new LongPollingTransport()); hubProxy.Invoke("FillData", NewDatas); //----------------- } } } catch (Exception ex) { int i = 0; } } |
changeDatas, cacheDatas, FillCache() ve isDataChange(): Yukarıda kullanılan listeler ve methodlar aşağıda görüldüğü gibi tanımlanmıştır.
- “changeDatas” sadece değişen dataların tutulduğu bir Listedir. Bu liste sayesinde data güncellemesi olduğu zaman sadece bu liste içindekiler gezilip tek tek güncellenmektedir. Dikkat edilirse: Performance amaçlı tüm datalar güncellendikten sonra “entity.SaveChanges()” yapılmış hepsi için bir kere database’e gidilmişitir. Ayrıca sonradan bahsedeceğimiz signalR hub sınıfına sadece değişen datalar(changeDatas) gönderilerek performans artışı sağlanmıştır.
- “cacheDatas” enson kur bilgisinin tutulduğu listedir. Yeni kur bilgisi geldiği zaman hangi datanın güncellendiğinin anlaşılması için database’e değil database’in son güncel halinin tutulduğu “cacheDatas”‘a bakılır. Eğer değişen data var ise “cacheDatas”‘da güncellenir.
- FillCache() methodu “cacheData” listesini doldurur.
- isDataChane() methodu gelen kur datasının ilgili kolonlor ile önceki dataya göre, linq kullanarak değişip değişmediğine bakılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
/// <summary> /// We collect chaned data to this changeDatas list. After all we will update them. /// </summary> public List<CurrencyReport> changeDatas; /// <summary> /// We will check all data's value from this cache. And if there is anyChanges we will refresh it. /// </summary> public List<CurrencyReport> cacheDatas; public void FillCache() { changeDatas = new List<CurrencyReport>(); if (cacheDatas == null) { cacheDatas = new List<CurrencyReport>(); } else { cacheDatas.Clear(); } using (CurrencyContext entity = new CurrencyContext()) { cacheDatas = entity.CurrencyReport.ToList(); } } /// <summary> /// We checked here is the current Currency Data is change or not /// </summary> /// <param name="data"></param> /// <returns></returns> public bool isDataChange(CurrencyReport data) { return cacheDatas.Any(cd => cd.CurrencyName == data.CurrencyName && cd.BanknoteBuying == data.BanknoteBuying && cd.BanknoteSelling == data.BanknoteSelling && cd.ForexBuying == data.ForexBuying && cd.ForexSelling == data.ForexSelling); } |
Ayrıca aynı uygulama Windows Service yerine Azure tarafında, Cloud Service olarak aşağıdaki gibi alternatif bir çözüme gidilebilir.
Solution’a Cloud sekmesinden Azure Cloud Service aşağıdaki gibi eklenir:
Arkada çalışacak yapı Worker Role olarak aşağıdaki gibi eklenir.
WorkerRole.cs: Kodlar yukarıda yazdığımız Windows Service’e büyük çoğunlukla benzemektedir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.WindowsAzure; using Microsoft.WindowsAzure.Diagnostics; using Microsoft.WindowsAzure.ServiceRuntime; using Microsoft.WindowsAzure.Storage; using System.Data; using System.Xml; using DAL; using Microsoft.AspNet.SignalR.Client; using Microsoft.AspNet.SignalR.Client.Transports; namespace WorkerRole1 { public class WorkerRole : RoleEntryPoint { private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false); public override void Run() { Trace.TraceInformation("WorkerRole1 is running"); try { this.RunAsync(this.cancellationTokenSource.Token).Wait(); } finally { this.runCompleteEvent.Set(); } } public override bool OnStart() { // Set the maximum number of concurrent connections ServicePointManager.DefaultConnectionLimit = 12; // For information on handling configuration changes // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357. bool result = base.OnStart(); Trace.TraceInformation("WorkerRole1 has been started"); return result; } public override void OnStop() { Trace.TraceInformation("WorkerRole1 is stopping"); this.cancellationTokenSource.Cancel(); this.runCompleteEvent.WaitOne(); base.OnStop(); Trace.TraceInformation("WorkerRole1 has stopped"); } private async Task RunAsync(CancellationToken cancellationToken) { // TODO: Replace the following with your own logic. while (!cancellationToken.IsCancellationRequested) { Trace.TraceInformation("Working"); await GetCurrencyData(); await Task.Delay(60000); } } public async Task GetCurrencyData() { try { //We takes all currencyData from service Uri uri = new Uri("http://www.tcmb.gov.tr/kurlar/today.xml"); string xmlStr; using (var wc = new WebClient()) { xmlStr = await wc.DownloadStringTaskAsync(uri); } var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xmlStr); //I Could make here with Xpath. But this is more easy.. DataSet ds = new DataSet(); ds.ReadXml(new XmlNodeReader(xmlDoc)); //We took all data--------------------------- //We will check is there any data on DB bool anyDataOnDb = false; using (CurrencyContext entity = new CurrencyContext()) { anyDataOnDb = entity.CurrencyReport.Any(); //If any data on db we will check for it is Updated if (anyDataOnDb) { if (cacheDatas == null) { FillCache(); } changeDatas.Clear(); foreach (DataRow drow in ds.Tables[1].Rows) { if (drow["Isim"].ToString().Trim() != "") { CurrencyReport report = new CurrencyReport(); report.CurrencyName = drow["Isim"].ToString(); report.ForexBuying = Decimal.Parse(drow["ForexBuying"].ToString() != "" ? drow["ForexBuying"].ToString().Replace(".", ",") : "0"); report.ForexSelling = Decimal.Parse(drow["ForexSelling"].ToString() != "" ? drow["ForexSelling"].ToString().Replace(".", ",") : "0"); report.BanknoteBuying = Decimal.Parse(drow["BanknoteBuying"].ToString() != "" ? drow["BanknoteBuying"].ToString().Replace(".", ",") : "0"); report.BanknoteSelling = Decimal.Parse(drow["BanknoteSelling"].ToString() != "" ? drow["BanknoteSelling"].ToString().Replace(".", ",") : "0"); report.CrossRateUSD = Decimal.Parse(drow["CrossRateUSD"].ToString() != "" ? drow["CrossRateUSD"].ToString().Replace(".", ",") : "0"); if (!isDataChange(report)) { changeDatas.Add(report); } } } foreach (CurrencyReport report in changeDatas) { var changedData = entity.CurrencyReport.Find(report.CurrencyName); changedData.ForexBuying = report.ForexBuying; changedData.ForexSelling = report.ForexSelling; changedData.BanknoteBuying = report.BanknoteBuying; changedData.BanknoteSelling = report.BanknoteSelling; changedData.CrossRateUSD = report.CrossRateUSD; changedData.CreatedDate = DateTime.Now; } if (changeDatas.Any()) { entity.SaveChanges(); //Triger All Clients By SignalR HubConnection hubConnection = new HubConnection("http://localhost:1646"); IHubProxy hubProxy = hubConnection.CreateHubProxy("Currency"); await hubConnection.Start(new LongPollingTransport()); hubProxy.Invoke("UpdateData", changeDatas); //----------------- FillCache(); } } else { List<CurrencyReport> NewDatas = new List<CurrencyReport>(); //If there is no data on DB we will insert all Datas......... foreach (DataRow drow in ds.Tables[1].Rows) { if (drow["Isim"].ToString().Trim() != "") { CurrencyReport report = new CurrencyReport(); report.CurrencyName = drow["Isim"].ToString(); report.ForexBuying = Decimal.Parse(drow["ForexBuying"].ToString() != "" ? drow["ForexBuying"].ToString().Replace(".", ",") : "0"); report.ForexSelling = Decimal.Parse(drow["ForexSelling"].ToString() != "" ? drow["ForexSelling"].ToString().Replace(".", ",") : "0"); report.BanknoteBuying = Decimal.Parse(drow["BanknoteBuying"].ToString() != "" ? drow["BanknoteBuying"].ToString().Replace(".", ",") : "0"); report.BanknoteSelling = Decimal.Parse(drow["BanknoteSelling"].ToString() != "" ? drow["BanknoteSelling"].ToString().Replace(".", ",") : "0"); report.CrossRateUSD = Decimal.Parse(drow["CrossRateUSD"].ToString() != "" ? drow["CrossRateUSD"].ToString().Replace(".", ",") : "0"); report.CreatedDate = DateTime.Now; entity.CurrencyReport.Add(report); NewDatas.Add(report); } } entity.SaveChanges(); //Triger All Clients By SignalR [Show All New Datas to All Client] HubConnection hubConnection = new HubConnection("http://localhost:1646"); IHubProxy hubProxy = hubConnection.CreateHubProxy("Currency"); await hubConnection.Start(new LongPollingTransport()); hubProxy.Invoke("FillData", NewDatas); //----------------- } } } catch (Exception ex) { int i = 0; } } /// <summary> /// We collect chaned data to this changeDatas list. After all we will update them. /// </summary> public List<CurrencyReport> changeDatas; /// <summary> /// We will check all data's value from this cache. And if there is anyChanges we will refresh it. /// </summary> public List<CurrencyReport> cacheDatas; public void FillCache() { changeDatas = new List<CurrencyReport>(); if (cacheDatas == null) { cacheDatas = new List<CurrencyReport>(); } else { cacheDatas.Clear(); } using (CurrencyContext entity = new CurrencyContext()) { cacheDatas = entity.CurrencyReport.ToList(); } } /// <summary> /// We checked here is the current Currency Data is change or not /// </summary> /// <param name="data"></param> /// <returns></returns> public bool isDataChange(CurrencyReport data) { return cacheDatas.Any(cd => cd.CurrencyName == data.CurrencyName && cd.BanknoteBuying == data.BanknoteBuying && cd.BanknoteSelling == data.BanknoteSelling && cd.ForexBuying == data.ForexBuying && cd.ForexSelling == data.ForexSelling); } } } |
Böylece ilgili servis’in 1/dk’da pars edilip, eğer kayıt var ise değişenlerin, yok ise tamamının database’e güncellenerek yada kaydedilerek, signalR Hub sınfına gönderilmesinin, Windows Service ve Azure Cloud Servis yaklaşımlarını inceledik.
Son olarak database’e kaydedilen bu verileri dışarıya bir servis yardımı ile verelim. Bu işlem için aşağıdaki gibi CurrencyService adında yeni bir WebApi servisi yazılmıştır.
CurrencyController.cs: Aşağıdaki servisde “GetAllCurrency()” methodu ile “CurrencyReport” tablosundaki tüm data Linq ile çekilerek döndürülmektedir. Burada herhangi bir cache yapısının bulunmamasındaki amaç, esas cache’in bu servisi kullanan yapılarda olması ve eğer gerçekten servise bir sorgu atıldı ise gerçek zamanlı en son data bilgisinin istenmesidir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
using DAL; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; namespace CurrencyService.Controllers { public class CurrencyController : ApiController { // GET: api/Currency public IEnumerable<CurrencyReport> GetAllCurrency() { using (CurrencyContext entity = new CurrencyContext()) { return entity.CurrencyReport.ToList(); } } // GET: api/Currency/5 public string Get(int id) { return "value"; } // POST: api/Currency public void Post([FromBody]string value) { } // PUT: api/Currency/5 public void Put(int id, [FromBody]string value) { } // DELETE: api/Currency/5 public void Delete(int id) { } } } |
Geldik gerçek hayattan kesitli makalemizin ilk bölümünün sonuna. Bu bölümde code first yardımı ile DataBase’i oluşturduk. Sonra ilgili servisi belli zaman aralıkları ile parse edip database’e insert eden bunu da signalR websocket teknolojisini kullanarak tüm clientlara duyuran windows servisi ve alternatif çözüm olarak azure cloud servisini yazdık. Son olarak da ilgili kurların bilgisini veren WebApi servisini kodlayıp konunun ilk bölümünü tamamladık.
Makalenin devamında görüşmek üzere hoşçakalın.
1 kasa domates alıp satarsak ve bu satıştan kazanılan para ile 1 kasa domates alacak kadar kazanç saglarsak,aynı performans ve azimle tekrar satış yaptığımızda 4 kasa domates kazanmış oluruz. İf kazandığımız parayı yemeden ve kimselere kaptırmayan 4 kez dondurursek toplamda 16 kasa domatesin olur, 4.günün sonunda 16 kasa domates ten 800 TL kazanırsak ve bunu 7 kez yaparsak 28 günde yaklaşık 5600 TL kazaniriz..
Else köleliğe devam
Saygılarımla
Ben o kasadaki domateslerin 1/4’ü ile menemen yaparım. Böylece 5600 değil 4200TL kazanırım. Böyelece hem karnım doyar hem de kafam rahat olur:)
Hep düşünmüşümdür gerçek hayattaki projelerde neler isteniyor diye. Çokta aman aman şeyler değilmiş.Sanıyordum ki angulara takla attırıp , sql’i amuda kaldıracaz vs diye ama öyle değilmiş. Bu arada teşekkürler hocam.
Tcmb den dovizleri web client ile okuyup daha sonra dataset icerisine atmak yerine, linq to xml ile okumak, hatta direk dataset ile read xml yapmak daha mantikli.
Selamlar Taylan;
Bir işi yapmanın birçok farklı yolu vardır. Siz de belirtiğiniz yolların neden daha mantıklı olduğunu da söylerseniz çok daha yararlı bir yorum olabilir. Daha mı performaslı, daha mı anlaşılır, yoksa daha mı hızlı.
Bunlar gibi nedenler ile konuya yaklaşırsak bence zaten amacımız olan daha iyiye ulaşmak için bir adım daha atmış oluruz.
İyi çalışmalar.
Aklıma takılan soruların cevaplarını bu makalede buldum.Teşekkürler hocam
Selam Yunus,
Ben teşekkür ederim…
Dediğiniz gibi bir sorunun birden fazla çözüm yolu var. Paylaşmak için emek ve vakit harcadığınız için teşekkürler.
Ben teşekkür ederim ömer.
Bora hocam selamlar,
Çok doyurucu bir makale olmuş ve izninizle blogunuzdaki yazılarınızı pdf haline getirip kaydediyorum.
Çok teşekkürler.
Selamlar Yunusi
Teşekkür Ederim.
Sonunda hepsini yaparsan benle de bir dropbox, onedrive Url’i paylaşarısan sevinirim :)
Kolay Gelsin..
Hocam , teşekkür ederim.Çok faydalı oldu.
Sizi Azerbaycandan takip ediyorum.
Saygılar..
Selamlar Tagi,
Ben teşekkür ederim. Takibe devam :)