Azure Cloude Services ile Bir Web Sitesini Pars Edip İlgili Datayı Clientlara SignalR ile Push Etme
Selamlar;
Bugün bir web sitesindeki bilgileri çekip clientlara banner olarak verilen bir iframe, sayfasına basıcaz. Bu sırada web sayfasını sürekli pars etmeye devam edicez. Herhangi bir değişiklik durumunda ilgili bannerları güncelliyiceğiz. Bu amaç ile Azure Cloude Services’de, watin.dll’ini kullanarak, http://www.sahibinden.com/ portalında araba ilanları sayfasını pars edip, son 6 ilanın datasını çekicez. Eğer önceden çekilen datalardan farklılık var ise bir hub class’ının method’unu tetikleyip signalR kullanarak clientlara push edicez. Bu şekilde yeni girilen araba ilanları kullanıcılara gösterilmiş olucak. Öncelikle Pars edilecek siteyi aşağıda görüldüğü gibi http://www.sahibinden.com/otomobil inceleyelim.
Kaydedilecek alanlar İlan Başlığı :Title, Yıl:Year, Km:Km, Renk: Color, Fiyat: Price, İlan Tarihi: Date, İl/İlçe:Place .Solution’a yeni bir DAL isminde CodeFirst kullanılan bir projesi yaratılır.Banner isminde bir DBcontext ve SahibindenCars şeklinde Data Model aşağıda görüldüğü gibi oluşturulur. CodeFirst için önceden yazdığım makaleyi inceleyebilirsiniz.
SahibindenCars.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 32 33 34 |
namespace DAL { using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.Spatial; public partial class SahibindenCars { public int ID { get; set; } [StringLength(200)] public string ImageUrl { get; set; } [StringLength(1000)] public string Title { get; set; } public int? Year { get; set; } public int? Km { get; set; } [StringLength(20)] public string Color { get; set; } [StringLength(50)] public string Price { get; set; } public DateTime? Date { get; set; } [StringLength(50)] public string Place { get; set; } } } |
Banner.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 32 33 34 35 36 37 38 39 40 |
namespace DAL { using System; using System.Data.Entity; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; public partial class Banner : DbContext { public Banner() : base("name=Banner") { } public virtual DbSet<SahibindenCars> SahibindenCars { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<SahibindenCars>() .Property(e => e.ImageUrl) .IsUnicode(false); modelBuilder.Entity<SahibindenCars>() .Property(e => e.Title) .IsUnicode(false); modelBuilder.Entity<SahibindenCars>() .Property(e => e.Color) .IsUnicode(false); modelBuilder.Entity<SahibindenCars>() .Property(e => e.Price) .IsUnicode(false); modelBuilder.Entity<SahibindenCars>() .Property(e => e.Place) .IsUnicode(false); } } } |
SahibindenCars Tablosunun design’ı azure tarafında aşağıdaki gibidir.
Tüm projeler database işlemlerinde bu DAL projesini kullanacaktır. Şimdi de siteyi pars işlemini inceleyelim.
Öncelikle solution’a Cloud altından Windows Azure Cloud Service projesi eklenir.
Daha sonra karşımıza çıkan Rolelerden Worker Role seçilir.
Karşımıza altta görüldüğü gibi sonsuz while döngüsü içinde belli zaman aralığında bekliyen bir kod bloğu çıkar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public override void Run() { while (true) { Thread.Sleep(30000); } } 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. return base.OnStart(); } |
Şimdi istenen siteyi pars etmek için aşağıda görülen Watin package indirilir. Watin’in esas amacı sitelere load test yapmaktır. Explorer bir browser’ı açarak belirlenen işlem adımlarını siteye uygular. Ama amaca yönelik olarak bu örnekde de olduğu gibi pars işlemi içinde kullanılabilir:)
Şimdi aşağıdaki kodları inceleyelim.
- Öncelikle watin’in cloude serviste çalışması için yeni bir thread açılır ve ApertmentState’i STA olarak seçilir. Thread başlatıldıktan sonra tekrar çağrılana kadar 30sn bekletilir.
1 2 3 4 |
Thread thread = new Thread(new ThreadStart(GetSahibindenCars)); thread.SetApartmentState(ApartmentState.STA); thread.Start(); Thread.Sleep(30000); |
- Watin için timeout ve tamamlanma süreleri set edilir. Pars işlemi yapılırken ilgili browserın gizlenmesi Settings.Instance.MakeNewIeInstanceVisible = false; ile ayarlanır. Ayrıca karşımıza çıkabilecek dialog kutucukları otomatik olarak kapatılması Settings.AutoCloseDialogs = true; ile ayarlanır. Gidilicek url string url = “http://www.sahibinden.com/otomobil”; şeklinde set edilir.
1 2 3 4 5 6 7 |
Settings.AttachToBrowserTimeOut = 240; //240 seconds Settings.WaitUntilExistsTimeOut = 240; Settings.WaitForCompleteTimeOut = 240; Settings.Instance.MakeNewIeInstanceVisible = false; Settings.AutoCloseDialogs = true; string url = "http://www.sahibinden.com/otomobil"; |
- Ilgili url’e using (var browser = new IE(url)) ile gidilir. Browser’ın yüklenmesi browser.WaitForComplete(); ile beklenilir. Sahibinden’den tüm resultlar aşağıda görüldüğü gibi searchResultsTable adlı tabloda altında gösterilmektedir. İlgili table ekranda gözükene kadar beklemek için browser.Table(Find.ById(“searchResultsTable”)).WaitUntilExists(); komutu kullanılır. Daha sonra ilgili table bulunarak altındaki ilk 6 row tek tek foreach (TableRow trow in browser.Table(Find.ById(“searchResultsTable”)).TableRows.Skip(1).Take(6)) şeklinde alınır. İlk satır başlık olduğu için atlanmıştır. Alınan her satırın hücreleri gezilerek ilgili data model doldurulur. Böylece ilgili site pars edilmiş ve data model oluşturulmuş olur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
try { List<SahibindenCars> datas; using (var browser = new IE(url)) { datas = new List<SahibindenCars>(); browser.WaitForComplete(); System.Threading.Thread.Sleep(2000); browser.Table(Find.ById("searchResultsTable")).WaitUntilExists(); foreach (TableRow trow in browser.Table(Find.ById("searchResultsTable")).TableRows.Skip(1).Take(6)) { SahibindenCars data = new SahibindenCars(); data.ImageUrl = trow.TableCells[0].Images[0].Src; data.Title = trow.TableCells[1].Text; data.Year = int.Parse(trow.TableCells[2].Text ?? "0"); data.Km = int.Parse(trow.TableCells[3].Text == null ? "0" : trow.TableCells[3].Text.Replace(".", "").Trim()); data.Color = trow.TableCells[4].Text; data.Price = trow.TableCells[5].Text; IFormatProvider theCultureInfo = new System.Globalization.CultureInfo("tr-TR", true); data.Date = DateTime.ParseExact(trow.TableCells[6].Text.Trim().Replace("\r\n", string.Empty), "dd MMMM yyyy", theCultureInfo); data.Place = trow.TableCells[7].Text; datas.Add(data); } //} |
- Önceden bu datalar çekilmemiş ise ilgili data model bir cacheDatas’ına atanır. Eğer çekilmiş ise değişiklik olup olmadı yani yeni ilan olup olmadığına ilanın resim yoluna bakılarak isDataChange() methodu ile yapılır. Değişiklik yok ise hernagi bir işlem yapılmaz. Eğer yeni ilan gelmiş ise data model var olan cacheDatas’ına tekrardan FillCache() methodu ile atanır. Database truncate edilip yeni data model insert edilir.
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 |
bool isChange = false; cacheDatas = null;//Tekrardan database silinince yenilenmesi için koyduk. if (cacheDatas == null) { FillCache(); } foreach (SahibindenCars item in datas) { if (!isDataChange(item)) { isChange = true; } } if (isChange) { FillCache(); using (Banner dbContext = new Banner()) { dbContext.Database.ExecuteSqlCommand("TRUNCATE TABLE SahibindenCars"); dbContext.SahibindenCars.AddRange(((IEnumerable<SahibindenCars>)datas)); dbContext.SaveChanges(); } //Triger SignalR Triger(); } } } catch (Exception ex) { int i = 0; } } public List<SahibindenCars> cacheDatas; public void FillCache() { if (cacheDatas == null) { cacheDatas = new List<SahibindenCars>(); } else { cacheDatas.Clear(); } using (Banner dbContext = new Banner()) { cacheDatas = dbContext.SahibindenCars.ToList(); } } public bool isDataChange(SahibindenCars data) { return cacheDatas.Any(cd => cd.ImageUrl == data.ImageUrl); } |
- Son olarak clientlar’ın olan değişiklikten haberdar olması için signalR methodu asenkron olarak tetiklenir. Bunu yapılabilmek için projemize alttaki package’in indirilmesi gerekir.
Bu işlem için öncelikle Hub’ın bulunduğu domain belirlenir(http://sahibindenbanner2.azurewebsites.net/). Daha sonra bağlanılacak olan hub class’ı tanımlanır(GetCars). Son olarak hub’daki hangi method çağrılacak ise ismi belirtilir(RefreshCars). Bu işlemin asenkron yapılmasındaki amaç dış bir sisteme bağlanılırken var olan akışın durmasını engellemek ve bu işlemi arka planda bağımsız olarak çalıştırmaktır.
Tüm bu adımlar 30 sn’de bir tekrarlanmaktadır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public async void Triger() { try { //var hubConnection = new HubConnection("http://localhost:39325"); var hubConnection = new HubConnection("http://sahibindenbanner2.azurewebsites.net/"); IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("GetCars"); await hubConnection.Start(new LongPollingTransport()); stockTickerHubProxy.Invoke("RefreshCars"); } catch (Exception ex) { int i = 0; } } |
WorkerRole.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 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 |
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Threading; using Microsoft.WindowsAzure; using Microsoft.WindowsAzure.Diagnostics; using Microsoft.WindowsAzure.ServiceRuntime; using Microsoft.WindowsAzure.Storage; using WatiN.Core; using DAL; using Microsoft.AspNet.SignalR.Client; using Microsoft.AspNet.SignalR.Client.Transports; namespace WorkerRole1 { public class WorkerRole : RoleEntryPoint { public override void Run() { while (true) { Thread thread = new Thread(new ThreadStart(GetSahibindenCars)); thread.SetApartmentState(ApartmentState.STA); thread.Start(); Thread.Sleep(30000); } } 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. return base.OnStart(); } public void GetSahibindenCars() { Settings.AttachToBrowserTimeOut = 240; //240 seconds Settings.WaitUntilExistsTimeOut = 240; Settings.WaitForCompleteTimeOut = 240; Settings.Instance.MakeNewIeInstanceVisible = false; Settings.AutoCloseDialogs = true; string url = "http://www.sahibinden.com/otomobil"; try { List<SahibindenCars> datas; using (var browser = new IE(url)) { datas = new List<SahibindenCars>(); browser.WaitForComplete(); System.Threading.Thread.Sleep(2000); browser.Table(Find.ById("searchResultsTable")).WaitUntilExists(); foreach (TableRow trow in browser.Table(Find.ById("searchResultsTable")).TableRows.Skip(1).Take(6)) { SahibindenCars data = new SahibindenCars(); data.ImageUrl = trow.TableCells[0].Images[0].Src; data.Title = trow.TableCells[1].Text; data.Year = int.Parse(trow.TableCells[2].Text ?? "0"); data.Km = int.Parse(trow.TableCells[3].Text == null ? "0" : trow.TableCells[3].Text.Replace(".", "").Trim()); data.Color = trow.TableCells[4].Text; data.Price = trow.TableCells[5].Text; IFormatProvider theCultureInfo = new System.Globalization.CultureInfo("tr-TR", true); data.Date = DateTime.ParseExact(trow.TableCells[6].Text.Trim().Replace("\r\n", string.Empty), "dd MMMM yyyy", theCultureInfo); data.Place = trow.TableCells[7].Text; datas.Add(data); } } bool isChange = false; cacheDatas = null;//Tekrardan database silinince yenilenmesi için koyduk. if (cacheDatas == null) { FillCache(); } foreach (SahibindenCars item in datas) { if (!isDataChange(item)) { isChange = true; } } if (isChange) { FillCache(); using (Banner dbContext = new Banner()) { dbContext.Database.ExecuteSqlCommand("TRUNCATE TABLE SahibindenCars"); dbContext.SahibindenCars.AddRange(((IEnumerable<SahibindenCars>)datas)); dbContext.SaveChanges(); } //Triger SignalR Triger(); } } catch (Exception ex) { int i = 0; } } public async void Triger() { try { //var hubConnection = new HubConnection("http://localhost:39325"); var hubConnection = new HubConnection("http://sahibindenbanner2.azurewebsites.net/"); IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("GetCars"); await hubConnection.Start(new LongPollingTransport()); stockTickerHubProxy.Invoke("RefreshCars"); } catch (Exception ex) { int i = 0; } } public List<SahibindenCars> cacheDatas; public void FillCache() { if (cacheDatas == null) { cacheDatas = new List<SahibindenCars>(); } else { cacheDatas.Clear(); } using (Banner dbContext = new Banner()) { cacheDatas = dbContext.SahibindenCars.ToList(); } } public bool isDataChange(SahibindenCars data) { return cacheDatas.Any(cd => cd.ImageUrl == data.ImageUrl); } } } |
Aynı işlemi bir de claude services olmadan windows application ile yazalım. Amaç azure kullanılmadan uygulamanın local makinada da test edilebilmesidir. İlgili kodlar aşağıdadır. Burada cloude servicesden farklı olarak while sonsuz döngüsü yoktur ve yerine timer vardır. Timer 30 sn de bir çalışmaktadır. Windows application’da watin için thread’e gerek yoktur. İlgili siteyi pars etmek için açılacak explorer browser başlangıçta protected override void OnVisibleChanged(EventArgs e){ base.OnVisibleChanged(e); this.Visible = false; } komutu ile gizlenmektedir. Ayrıca notifyIcon1 ile exit’e basılarak uygulama kapatılabilmektedir.
SahibindenWidget/Form1.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 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 |
using System.Collections; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using WatiN.Core; using DAL; using Microsoft.AspNet.SignalR.Client; using Microsoft.AspNet.SignalR.Client.Transports; namespace SahibindenWidget { public partial class Form1 : System.Windows.Forms.Form { public Form1() { InitializeComponent(); } public void exitToolStripMenuItem_Click(object sender, EventArgs e) { this.Close(); System.Windows.Forms.Application.Exit(); } protected override void OnVisibleChanged(EventArgs e) { base.OnVisibleChanged(e); this.Visible = false; } public bool isToolMenuShowen = true; public void notifyIcon1_Click(object sender, EventArgs e) { if (isToolMenuShowen) { contextMenuStrip1.Show(System.Windows.Forms.Control.MousePosition); isToolMenuShowen = false; } else { contextMenuStrip1.Hide(); isToolMenuShowen = true; } } private void Form1_Load(object sender, EventArgs e) { Settings.Instance.MakeNewIeInstanceVisible = false; Settings.AutoCloseDialogs = true; GetSahibindenCars(); timer1.Start(); } public void GetSahibindenCars() { Settings.AttachToBrowserTimeOut = 240; //240 seconds Settings.WaitUntilExistsTimeOut = 240; Settings.WaitForCompleteTimeOut = 240; string url = "http://www.sahibinden.com/otomobil"; try { List<SahibindenCars> datas; using (var browser = new IE(url)) { datas = new List<SahibindenCars>(); browser.WaitForComplete(); System.Threading.Thread.Sleep(2000); browser.Table(Find.ById("searchResultsTable")).WaitUntilExists(); foreach (TableRow trow in browser.Table(Find.ById("searchResultsTable")).TableRows.Skip(1).Take(6)) { SahibindenCars data = new SahibindenCars(); data.ImageUrl = trow.TableCells[0].Images[0].Src; data.Title = trow.TableCells[1].Text; data.Year = int.Parse(trow.TableCells[2].Text ?? "0"); data.Km = int.Parse(trow.TableCells[3].Text == null ? "0" : trow.TableCells[3].Text.Replace(".", "").Trim()); data.Color = trow.TableCells[4].Text; data.Price = trow.TableCells[5].Text; IFormatProvider theCultureInfo = new System.Globalization.CultureInfo("tr-TR", true); //data.Date = DateTime.Parse(trow.TableCells[6].Text); data.Date =DateTime.ParseExact(trow.TableCells[6].Text.Trim().Replace("\r\n", string.Empty), "dd MMMM yyyy", theCultureInfo); data.Place = trow.TableCells[7].Text; datas.Add( data); } } bool isChange = false; cacheDatas = null; //Tekrardan database silinince yenilenmesi için koyduk. if (cacheDatas == null) { FillCache(); } foreach (SahibindenCars item in datas) { if (!isDataChange(item)) { isChange = true; } } if (isChange) { FillCache(); using (Banner dbContext = new Banner()) { dbContext.Database.ExecuteSqlCommand("TRUNCATE TABLE SahibindenCars"); dbContext.SahibindenCars.AddRange(((IEnumerable<SahibindenCars>) datas)); dbContext.SaveChanges(); } //Triger SignalR Triger(); } } catch (Exception ex) { int i = 0; } } public async void Triger() { try { //var hubConnection = new HubConnection("http://localhost:39325"); var hubConnection = new HubConnection("http://sahibindenbanner.azurewebsites.net/"); IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("GetCars"); await hubConnection.Start(new LongPollingTransport()); stockTickerHubProxy.Invoke("RefreshCars"); } catch (Exception ex) { int i = 0; } } public List<SahibindenCars> cacheDatas; public void FillCache() { if (cacheDatas == null) { cacheDatas = new List<SahibindenCars>(); } else { cacheDatas.Clear(); } using (Banner dbContext = new Banner()) { cacheDatas = dbContext.SahibindenCars.ToList(); } } public bool isDataChange(SahibindenCars data) { return cacheDatas.Any(cd => cd.ImageUrl == data.ImageUrl); } private void timer1_Tick(object sender, EventArgs e) { timer1.Stop(); GetSahibindenCars(); timer1.Start(); } } } |
Şimdi de clientlara göstereceğimiz view’ı mvc ile yazalım. Öncelikle HomeController’da Index Action’ını aşağıda görüldüğü gibi yazalım. GetCars() diye bir method oluşturulur. Amcı sahibinden pars edilen son 6 datayı database’den çekmektir. Url’de clear adlı request parametresi var ise cache temizlenir. İlgi data 1 dakikalık cache’e konup Index view’ına gönderilir. Bir de GetCars:Hub sınıfı signalR ile clientları, data değişiminde tetiklemek için oluşturulur. Datada farklılık olduğu zaman ilgili data, databaseden çekilip SahibindenCars.cshtml partial view’ı ile birlikte Helper.GetRazorViewAsString() method’una gönderilip Result View string’e dönüştürülür. Ve client’lardaki reloadCars() function’ına browser destekliyor ise websocket ile desteklemiyorsa önceden SignalR makalesinde bahsettiğim diğer yöntemler ile push edilir.
HomeController.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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
using DAL; using Microsoft.AspNet.SignalR; using SahibindenBanner.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Caching; using System.Web.Mvc; namespace SahibindenBanner.Controllers { public class HomeController : Controller { // GET: Home public ActionResult Index() { var data = GetCars(); return View(data); } public List<SahibindenCars> GetCars() { if (Request.QueryString["clear"] == "1") { HttpContext.Cache.Remove("Cars"); } if (HttpContext.Cache["Cars"] == null) { using (Banner dbContext = new Banner()) { HttpContext.Cache.Add("Cars", dbContext.SahibindenCars.ToList(), null, Cache.NoAbsoluteExpiration, new TimeSpan(0, 1, 0), CacheItemPriority.Default, null); } } // Bitti Cache return ((List<SahibindenCars>)HttpContext.Cache["Cars"]); } } public class GetCars : Hub { public void RefreshCars() { List<DAL.SahibindenCars> data; using (Banner dbContext = new Banner()) { data=dbContext.SahibindenCars.ToList(); } string viewName = @"~/Views/Shared/SahibindenCars.cshtml"; var carsScreenPrint = Helper.GetRazorViewAsString(data, viewName); Clients.All.reloadCars(carsScreenPrint); } } } |
Helper.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 |
using SahibindenBanner.Controllers; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace SahibindenBanner.Models { public class Helper { public static string GetRazorViewAsString(object model, string filePath) { var st = new StringWriter(); var context = new HttpContextWrapper(HttpContext.Current); var routeData = new RouteData(); var controllerContext = new ControllerContext(new RequestContext(context, routeData), new HomeController()); var razor = new RazorView(controllerContext, filePath, null, false, null); razor.Render(new ViewContext(controllerContext, razor, new ViewDataDictionary(model), new TempDataDictionary(), st), st); return st.ToString(); } } } |
View tarafında aşağıda görüldüğü gibi jquery ve signalR.js’ler konur. Bir de signalr/hubs aslında olmayan magic script’i eklenir. GetCars hub classına var messagehub = $.connection.getCars ile connect olunur. <div id=”sahibindenCars”> içine çekilen ilan datalarının gösterildiği SahibindenCars partial view’ı basılır. Ayrıca signalR’a reloadCars()’ şeklinde client script’i tanımlanır. İlanlarda değişim olduğu zaman 30sn de bir çalışan Cloud Servis’i GetCars hub classındaki RefreshCars() methodunu invoke()’ lar.Bu da bağlı olan tüm clientlar’daki reloadCars() scriptini tetikler ve gelen değişmiş data yukarıda bahsedilen sahibindenCars div’ine basılır. SignalR için önceden yazdığım makaleyi inceleyebilirsiniz.
SignalR’ın projede çalışması için öncelikle nuget’den aşadaki package’in indirilmesi gerekir.
Ayrıca aşağıda görüldüğü gibi StartUp.cs’de app.MapSignalR()’ın configuratin’a eklenmesi gerekir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace SahibindenBanner { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR(); } } } |
Index.cshtml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@model List<DAL.SahibindenCars> @{ ViewBag.Title = "Index"; } <script src="@Url.Content("~/Scripts/jquery-1.10.2.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.signalR-2.1.1.js")" type="text/javascript"></script> <script src="@Url.Content("~/signalr/hubs")"></script> <script> var messagehub = $.connection.getCars; messagehub.client.reloadCars = function (data) { $("#sahibindenCars").html(data); } $.connection.hub.start(); </script> <div id="sahibindenCars"> @{Html.RenderPartial("SahibindenCars", Model);} </div> |
Sahibinden Banner:
SahibindenCars partial view’ında aşağıda görüldüğü gibi yaratılan table içine her 2 kayıtta bir, yeni bir row eklenerek, gelen data model içinde gezilip herbir ilan view’a razor engine ile basılmaktadır. Örnek çıktı yukarıda gözükmektedir.
SahibindenCars.cshtml:
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 |
@model List<DAL.SahibindenCars> @{ int rowCount = 0; } <table border="1" width="500px" style="horiz-align:left;"> @foreach (var item in Model) { if (rowCount % 2 == 0) { @:<tr> } <td style="vertical-align:top; padding:10px" width="20%;"> <table> <tr> <td style="vertical-align: top" colspan="2"> <div><font size="1"><b> @item.Title</b></font></div> </td> </tr> <tr> <td style="padding:10px; vertical-align: top;" width="10%"> <img src="@item.ImageUrl" /> </td> <td style="vertical-align: top;" width="90%"> <div><h4><b><font color="red">@item.Price</font></b></h4></div> <div><font>@item.Year Model</font></div> <div><font>@item.Km Km'de</font></div> <div><font>@item.Place</font></div> </td> </tr> </table> </td> if (rowCount % 2 == 1) { @:</tr> } rowCount++; } </table> |
Bu sayfanın client’lara banner olarak verilmesi için writeFrame.js aşağıdaki gibi yazılır.
1 |
document.write("<iframe src='http://sahibindenbanner2.azurewebsites.net/' width='530' height='665'></iframe>"); |
Clientlar sahibinden banner’ının kendi sayfalarında gözükmesi için; banner’ın çıkmasını istedikleri div’in altına aşadaki gibi yukarıda belirtilen script dosyasını eklemeleri gerekmektedir.
1 |
<script src="http://sahibindenbanner2.azurewebsites.net/Scripts/writeFrame.js" type="text/javascript"></script> |
Sonuç olarak yukarıdaki örmekte de görüldüğü gibi, sahibinden sitesi pars edilip son eklenen 6 ilan clientlara frame olarak script yardımı ile basılmaktadır. Yazılan azure cloud veya windows servis ile ilgili portal 30sn de bir pars edilmekte ve kayıt değişikliği olması durumunda signalR teknolojisi kullanılarak çekilen yeni datalar clientlardaki frame bannerlara pushlanmaktadır. Böylece sayfa refresh olmadan sahibinden sitesindeki son 6 ilan güncel olarak takip edilebilmektedir.
Geldik bir makalenin daha sonuna.
Yeni bir makalede görüşmek üzere hoşçakalın.
Örnek Banner Url (İptal): http://sahibindenbanner2.azurewebsites.net/?clear=1
Örnek Sayfaya Konucak Script Url(İptal): writeFrame.js
Örnek Source Code: http://www.borakasmer.com/projects/SahibindenWidget.rar
Merhaba, Buradaki “ServicePointManager.DefaultConnectionLimit” tam olarak ne anlama geliyor, 13 kişi aynı anda sistemi kuallanamaz mı? Teşekkürler.
Selamlar Bilal;
Öncelikle DefaultConnectionLimit’in user ile alakası yok. Bu uygulamayı zaten azure üzerindeki bir windows servis olarak düşünürsen tek bir user yani admin user üzerinden işlem yürütülmektedir. DefaultConnectionLimit aynı anda ihtiyaca göre kullanılabilecek cpu sayısını belirtmek için kullanılır Zaten .Net 4.5 ile bu değer default olarak atanmaktadır. Ama ben genede her ihtimale karşı değeri setledim.
İyi çalışmalar.