.Net Core’da RabbitMQ ve SignalR İle Push Notification
Selamlar Arkadaşlar,
Bu makalede, .Net Core ile cross platform üzerinde RabbitMQ, signalR ve Microservice konularını hep beraber inceleyeceğiz. RabbitMQ hakkında daha detaylı bilgiye buradaki linkden erişebilirsiniz. .Net Core üzerinde RabbitMQ kullanabilmek için öncelikle RabbitMQ.Client kütüphanesine ihtiyaç duyulmaktadır. O zaman gelin bu RabbitMQ için kurulum işlemlerini Macbook üzerinde yapalım:
Makinanızda yok ise önce Homebrew kurulur. Var ise Update edilir:
1 2 3 4 5 |
# brew güncelle $ brew update # Server'ı kur $brew install rabbitmq |
Bash Profile Güncelle: RabbitMQ ilk kurulduğu zaman “/usr/local/sbin” altına kurulmaktadır. RabbitMQ server’ı kendi profilimiz altında ayağa kaldırmamız için ilgili yolu “.bash_profile” altına tanımlanması gerekmektedir. Bunun için “nano” aşağıdaki gibi kullanılır.
1 2 3 4 5 |
# Dosyayı aç. nano ~/.bash_profile # Alttaki satırı .basf_profile dosyasına eklenir. export PATH=$PATH:/usr/local/sbin |
RabbitMQ Server’ın çalıştırılması: “rabbitmq-server” komutu yazıldığı zaman, RabbitMQ ilgili client tarafında ayağa kaldırılmış olunur.
1 2 3 4 5 6 7 8 9 10 |
$ rabbitmq-server RabbitMQ 3.6.14. Copyright (C) 2007-2017 Pivotal Software, Inc. ## ## Licensed under the MPL. See http://www.rabbitmq.com/ ## ## ########## Logs: /usr/local/var/log/rabbitmq/rabbit@localhost.log ###### ## /usr/local/var/log/rabbitmq/rabbit@localhost-sasl.log ########## Starting broker... completed with 9 plugins. |
Şimdi gelin .Net Core üzerinde RabbitMQ ile ilk örneğimizi geliştirelim.
Seneryo: Seçilen tahvil, bitcoin gibi yatırım araçlarının fiyatları bir admin ekranında değiştirilecektir. Bu değiştirilen yatırım araçları daha sonra sıra ile işleme alınması amacı ile RabbitMQ’ya, yani bir kuyruğa atılacaktır. Her bir yatarım aracı, kendine özel farklı bir channelda ilgili Queue’da saklanacaktır. Daha sonra her bir channel için farklı .NetCore Console App oluşturup, her bir kanal tek tek dinlenecektir. Son olarak sadece değişen bu yatırım araçlarını satın alan clientlara, SignalR ile geri bildirim yani push notification atılacaktır.
ADMİN :
İşlem adımları:
- Bir .Net Core Mvc projesi oluşturulur. “dotnet new Mvc -o coreRabbitMQAdmin“
- Yatırım araçlarının bilgilerini tutan “Stoc” model aşağıdaki gibi oluşturulur.
Models/Stoc.cs:
1 2 3 4 5 6 7 8 |
using System.Collections.Generic; public class Stoc { public int ID { get; set; } public string Name { get; set; } public decimal Value { get; set; } } |
- Dummy data amaçlı HomeController altında aşağıdaki gibi static bir List of Stoc “stocList” tanımlanmıştır. Amaçdan uzaklaşmamak adına DB işin içine sokulmamıştır.
1 2 3 4 5 6 |
public static List<Stoc> stocList=new List<Stoc>() { new Stoc(){ID=1,Name="NetaşTelekom",Value=Decimal.Parse("14.56")}, new Stoc(){ID=2,Name="Bitcoin",Value=Decimal.Parse("41,583.99")}, new Stoc(){ID=3,Name="Ethereum",Value=Decimal.Parse("1863.92")} }; |
- Dummy dataların çekileceği “GetDummyData()” methodu aşağıdaki gibi oluşturulur.
Home/GetDummyData :
1 2 3 4 |
public List<Stoc> GetDummyData() { return stocList; } |
- Index sayfasına dummy data “GetDummyData()” methodu ile çekilerek ilgili data view’a gönderilir.
Home/Index :
1 2 3 4 5 |
public IActionResult Index() { var data=GetDummyData(); return View(data); } |
Index.cshtml: Aşağıda görüldüğü gibi öncelikle Model olarak =>List of Stoc beklenmektedir.
- “@using(Html.BeginForm(“Push”,”Home”,”FormMethod.Post”))” : Form post işleminde, Home Controller’ın Push() action’ına, değiştirilen Stoc modeli post edilir.
- “<select name=”Name” id=”Name” onchange=”setValue(this.options[this.selectedIndex])”>” : Kağıt Çeşitleri combobox’ı, model’den gelen Stoc Listesi gezilerek doldurulur. Güncelleme amaçlı yeni bir stok seçildiğinde, fiyat bilgisi ilgili text’e “setValue()” function’ı ile doldurulur.
- “<option value=”@stock.Name” id=”@stock.ID” price=”@stock.Value”>@stock.Name</option>” : Stoc ismi, combo’nun her bir elemanı olarak atanır.
- “<input type=”text” name=”Value” id=”Value”>” : Stoc Value yani fiyatı ve ID’si, Form içinde post edileceği için input alanlarda tanımlanır.
- “setValue()” : Combo listesinde bir seçim yapıldığı zaman fiyatı, input alana basmak için kullanılan bir function’dır.
- “var price=selectedOption.getAttribute(‘price’) ” : ComboBox’dan seçilen elemanın (option’ın) price alanı alınır.
- “document.getElementById(“Value”).value=price” : Value id’li input alana, seçilen kağıdın fiyat (price) bilgisi atanır.
- “var stocID=selectedOption.id” : ComboBox’dan seçilen elemanın (option’ın) ID alanı alınır.
- “document.getElementById(“ID”).value=stocID ” : “ID” id’li input alana seçilen kağıdın ID bilgisi atanı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 |
@model List<Stoc> <script> function setValue(selectedOption) { var price=selectedOption.getAttribute('price'); document.getElementById("Value").value=price; var stocID=selectedOption.id; document.getElementById("ID").value=stocID; } </script> <div class="container"> <div class="jumbotron"> <h3>Kağıt Fiyatını Güncelle</h3> </div> <hr> @using(Html.BeginForm("Push","Home","FormMethod.Post")) { <label>Kağıt Çeşidi:</label> <select name="Name" id="Name" onchange="setValue(this.options[this.selectedIndex])"> @foreach(var stock in Model) { <option value="@stock.Name" id="@stock.ID" price="@stock.Value">@stock.Name</option> } </select> <input type="text" name="Value" id="Value"> <input type="hidden" name= "ID" id="ID"> <input type="submit" value="Güncelle" class="btn btn-success"> } </div> |
Home/Push() : Değiştirilen kayıt Push() action’ına gönderilerek, kuyruğa eklenmesi sağlanır. Bu arada değişen Item, “UpdateDummyList()” methodu ile var olan tanımlı global Static stocList‘den de değiştirilir. Queue olarak, bu makalenin de asıl konusu olan RabbitMQ kullanılmıştır. Tüm işlemler tamamlandıktan sonra, “Index” sayfasına tekrardan geri dönülür.
1 2 3 4 5 6 7 8 |
[HttpPost] public IActionResult Push(Stoc stock) { UpdateDummyList(stoc); RabbitMQPost rabbitMq=new RabbitMQPost(stock); Console.WriteLine(rabbitMq.Post()); return RedirectToAction("Index"); } |
Home/UpdateDummyList(): Güncellenen yatırım aracı, ID’si ile ilgili static stocList’den bulunup, eski değeri ile değiştirilir. Bu işlem static bir listede yapıldığı için tüm clientlar bundan etikenmektedir.
1 2 3 4 5 |
public void UpdateDummyList(Stoc stoc) { int index= stocList.FindIndex(st=>st.ID==stoc.ID); stocList[index]=stoc; } |
RabbitMQPost : Aşağıda görüldüğü gibi Constructor’da kuyruğa atılacak Stoc modeli data değişkenine atanmaktadır.
Not: Projeye, RabbitMQ Client kütüphanesi aşağıdaki gibi eklenmektedir.
1 |
dotnet add package RabbitMQ.Client |
- ConnectionFactory :RabbitMQ hostuna bağlanmak için kullanılır. Bulunulan sunucudaki host name (localhost) ve eğer tanımlanmış ise credentials (password) girilir.
- CreateModel() : Bu method ile RabbitMQ üzerinde yeni bir channel yaratılır. Bu channel aşağıda görüldüğü gibi her bir yeni eklenen stoc için yaratılmaktadır. Bu şekilde açılan channel yani session ile, yeni bir queue oluşturulup, değiştirilen stok bilgisi kendisine özel açılan bu channel üzerinden gönderilmektedir.
- QueueDeclare() : Bu method ile oluşturulacak olan queue‘nin ismi tanımlanır. Bu uygulamada ilgili “queue” ismi, seçilen Stoc ismi ile aynı olucak şekilde tanımlanmıştır. “durable” ile datanın in-memory mi yoksa fiziksel olarak mı saklanacağı belirlenir. “exclusive” parametresi ile diğer connectionlar ile kullanılmasına izin verilir. Eğer queue deleted olarak işaretlenmiş ise ve tüm consumerlar bunu kullanmayı bitirmiş ise ya da son consumer iptal edilmiş veya channel kapanmış ise silme işlemi gerçekleştirilemez. İşte bu gibi durumlarda “autoDelete” ile delete method’u normal olarak çalıştırılır. Böylece ilgili queueler silinir. “arguments” belirlenen exchanges ile alakalı tanımlanacak parametrelerdir. Bu uygulamada exchange tanımlanmamıştır.
- Güncellenen “Stoc” modeli, JsonConvert ile Serialize edilip string bir ifadeye dönüştürülür. Daha sonra “byte[ ]” dizisine çevrilip post edilecek “body“ parametresine atanır.
- BasicPublish() : Bu methodda “exchange”, aslında mesajın alınıp bir veya daha fazla queue’ya konmasını sağlar. routingKey ile konucak olan kuyruğun adı tanımlanır.
- Ve son olarak değiştirilen Stoc’un, kuyruğa konduğuna dair bir bildiri console’a yazdırı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 |
using System.Text; using Newtonsoft.Json; using RabbitMQ.Client; public class RabbitMQPost { public Stoc data; public RabbitMQPost(Stoc _data){ this.data=_data; } public string Post() { var factory = new ConnectionFactory() { HostName = "localhost" }; using (var connection = factory.CreateConnection()) using (var channel = connection.CreateModel()) { channel.QueueDeclare(queue: data.Name, durable: false, exclusive: false, autoDelete: false, arguments: null); var stocData = JsonConvert.SerializeObject(data); var body = Encoding.UTF8.GetBytes(stocData); channel.BasicPublish(exchange: "", routingKey: data.Name, basicProperties: null, body: body); return $"[x] Sent {data.Name}"; } } } |
Örnek değişen yatırım araçlarının, RabbitMQ’ya bildirim amacı ile atılması.
CLIENT :
Şimdi sıra geldi yukarıda tanımlanan yatırım araçlarından birinin bir client tarafından alınıp, monitör ekranında izlenmesine :
İşlem adımlar:
- Yeni bir .Net Core Mvc Client projesi oluşturulur. “dotnet new Mvc -o coreRabbitMQClient“
- Yatırım araçlarının bilgilerini tutan “Stoc” model, aşağıda görüldüğü gibi aynı Admin ekranında olduğu gibi oluşturulur.
Models/Stoc.cs:
1 2 3 4 5 6 7 8 |
using System.Collections.Generic; public class Stoc { public int ID { get; set; } public string Name { get; set; } public decimal Value { get; set; } } |
Seçilecek olan yatırım araçları, örnek amaçlı aşağıda görüldüğü gibi static olarak tanımlanır.
1 2 3 4 5 6 |
public static List<Stoc> stocList = new List<Stoc>() { new Stoc(){ID=1,Name="NetasTelekom",Value=Decimal.Parse("14.56")}, new Stoc(){ID=2,Name="Bitcoin",Value=Decimal.Parse("41,583.99")}, new Stoc(){ID=3,Name="Ethereum",Value=Decimal.Parse("1863.92")} }; |
Index.cshtml:
Home/Index : Static olarak tanımlı List<Stoc> view model, seçilmek amacı ile Index sayfasına View Model olarak gönderilir.
1 2 3 4 |
public IActionResult Index() { return View(stocList); } |
Index.cshtml : Client’ın, örnek amaçlı ürün satın aldığı ekrandır.
- Sayfada view model olarak “List<Stoc>” kullanılmıştır.
- “@using(Html.BeginForm(“Detail”,”Home”,”FormMethod.Post”))” : Kaydetme işlemi yapıldığında “Detail()” action’ına seçilen “Stoc” datası Post edilir.
- “@foreach(var stoc in Model)” : Stoc listesi gezilerek, radio button olarak ekrana alınabilecek yatırım araçları listelenir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@model List<Stoc> <div class="container"> <div class="jumbotron"> <h3>Yatırımını Seç</h3> </div> @using(Html.BeginForm("Detail","Home","FormMethod.Post")) { @foreach(var stoc in Model) { <div class="radio"> <label for="female" style="width : 125px"> <input type="radio" name="ID" id="ID" value="@stoc.ID">@stoc.Name </label> <b>Fiyat</b> : <label>@stoc.Value ₺</label> </div> } <br> <input type="submit" value="Satın Al" class="btn btn-success"> } |
Detail.cshtml:
Home/Detail: Alınan yatırım aracının, monitor edildiği ekrandır.
- “Stoc stoc=stocList.FirstOrDefault(s=>s.ID==ID)” : Seçilen yatırım aracına ait data, ID bilgisi ile linq kullanılarak çekilir. Daha sonra dönen Stock resut, Detail view’a model olarak gönderilir.
1 2 3 4 5 6 |
[HttpPost] public IActionResult Detail(int ID) { Stoc stoc=stocList.FirstOrDefault(s=>s.ID==ID); return View(stoc); } |
Detail.cshtml: Bu sayfa, satın alınan yatırım aracının real time olarak izlendiği monitor ekranıdır.
- @model Stoc : View model olarak sayfa üzerinde Stoc kullanılmıştır.
- “<script src=”~/scripts/signalr-client-1.0.0-alpha2-final.min.js”></script>” :.Net Core üzerinde ilgili signalR kütüphanesi eklenir. SignalR, makalenin devamında detaylıca incelenecektir. .Net Core üzerinde SignalR hakkında ön bir bilgi edinmek isterseniz, buradaki makaleye bir göz atabilirsiniz.
- İlgili signalR script kütüphanesi “npm install @aspnet/signalr-client –save” komutu ile indirilebilir.
- “npm install @aspnet/signalr-client” komutu ile ilgili kütüphane, golbal’e de indirilebilir. İlgili dosya globalden, yandaki klasörden erişilebilir==>”/usr/local/lib/node_modules/@aspnet/signalr-client/dist/browser/signalr-client-1.0.0-alpha2-final.min.js“. Bu script dosyasının projede “wwwroot” altında “scripts” klasörü altına kopyalanması gerekmektedir.
- “let connection = new signalR.HubConnection(‘/stochub’)” : Makalenin devamında, signalR sınıfı olarak “StocHub: Hub” sınıfı oluşturulacaktır. İşte bu yaratılacak sınıfa client tarafından “HubConnection” ile erişilir.
- “connection.on(‘SetConnectionId’, data => {” : Server Side taraftan, ilgili client side tarafa tetiklenecek olan “SetConnection()” function’ı tanımlanmıştır. Burada “data” ===> client’ın SignalR sınıfında, yani server side tarafında “OnConnectedAsync()” methodunda çekilen ConnectionID değeridir.
- “var result=connection.invoke(‘ConnectGroup’,’@Model.Name’,data);” : Seçilen Stoc ismi server side SignalR : Hub sınıfındaki “ConnectGroup()” methoduna parametre olarak gönderilmiştir.
- “connection.on(‘ChangeStocValue’, data => {” : İlerde seçilen ürünün fiyatı değiştiği zaman, son güncel fiyatın client’a real time bildirilmesi amacı ile server side taraftan, client side tarafa tetiklenen “ChangeStocValue()” function’ıdır. Bu şekilde son güncel fiyat, server side tarafından alınıp, client üzerinde bulunan “stocValue” “<span>” html elementine bası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 |
@model Stoc <head> <script src="~/scripts/signalr-client-1.0.0-alpha2-final.min.js"></script> <script> let connection = new signalR.HubConnection('/stochub'); connection.on('SetConnectionId', data => { console.log("ConnectionID : "+data); var result=connection.invoke('ConnectGroup','@Model.Name',data); console.log(result); }); connection.on('ChangeStocValue', data => { console.log(JSON.stringify(data)); alert("New Price : "+data.value +" ₺"); document.getElementById("stocValue").innerHTML=data.value; }); connection.start() </script> </head> <div class="container"> <div class="jumbotron"> <h3>Alınan = @Model.Name : <span id="stocValue">@Model.Value</span> ₺</h3> </div> </div> |
Image Source: https://www.c-sharpcorner.com/UploadFile/abhijmk/what-why-and-how-about-signalr/Images/2.png
Önemli Not: Index sayfasından seçilen bir yatırım aracı, sessionda saklanıp, burada yani Detail sayfasında server side “OnConnectedAsync()” methodunda direk kullanılabilirdi. Ama bu örnekde session’dan kaçınılıp önce 1-) data ==> “Detail.cshtml” sayfaya View Model olarak gönderilmiş daha sonra 2-) “Detail.cshtml” sayfadan ==> İlerde yazacağımız Server Side taraftaki “SetConnectionId()” methoduna “Stoc.Name” olarak aktarılmıştır. Bu server side ve client side arasındaki trafik, örnek amaçlı yukarıdaki çizimde detaylıca gösterilmektedir.
Ödev: Yüksek trafikli bir sitede, ilgili datayı Session’a mı koymak daha performanslı yoksa client side ve server side taraflar arasında birden fazla trafik oluşturmak mı?
SignalR :
Bu projede kullanılma amacı, fiyatı değişen yatırım araçlarının güncellenen son fiyatının, bu ürünü alan clientlara bildirilmesidir. Bunun için realt time websocket teknolojisi olan SignalR kullanılmıştır.
Kurulum:
- Oluşan Mvc projede “coreRabbitMQClient.csproj” dosyası aşağıdaki gibi güncellenir. ==>”<PackageReference Include=”Microsoft.AspNetCore.SignalR” Version=”1.0.0-alpha2-final” />” dosyası eklenir. Kaydetme işleminden sonra VS Code’un sorduğu Restore işlemine izin verilir. Ya da “dotnet restore” komutu ile ilgili dosyalar yüklenir.
- “<Folder Include=”wwwroot\scripts\” />” : Aşağıda görülen bu ekleme ile “.cshtml” sayfa üzerinde eklenecek script dosyalarına kısa yol verilmiş olunur.
coreRabbitMQClient.csproj: SignalR için gerekli kütüphane eklenmiş olunur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.0-alpha2-final" /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" /> <Folder Include="wwwroot\scripts\" /> </ItemGroup> </Project> |
Startup.cs: İlgili SignalR kodları aşağıdaki gibi Startup.cs’e eklenir. Böylece signalR Routing tanımlaması yapılmış olunur.
- “ConfigureServices()” methodunda ==> “services.AddSignalR()” ile ilgili signalR services tanımlaması yapılır.
- “routes.MapHub<StocHub>(“stochub”)” : Mvc routing işlemlerinde “stochub” key’i ile “StocHub” sınıfına yönlenilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public void ConfigureServices(IServiceCollection services) { ............... services.AddSignalR(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { ............... app.UseSignalR(routes => { routes.MapHub<StocHub>("stochub"); }); } |
StocHub(class):
- “OnConnectedAsync()” : Bu StocHub signalR sınıfında ilk önce connect olan client’ın connectionID’si o an bağlanan client’a gönderilir:
- “Context.ConnectionId” : O an bağlanan client’ın connectionID’sidir.
- “Clients.Client(Context.ConnectionId)” : Connect olan client’a connectionID’si ile erişilir.
- “InvokeAsync(“SetConnectionId”, Context.ConnectionId)” : Client Side tarafta “SetConnectionId()” function’ı tetiklenerek parametre olarak connect olan client’ın ConnectionID’si gönderilir.
- “ConnectGroup()” : Connect olan client’ın, seçtiği yatırım aracının adı ile bir guruba atanması sağlanmıştır.
- “await Groups.AddAsync(connectionID,stocName)” : Asenkron olarak connect olan ilgili connectionID’li client’ın, seçilen stockName adındaki guruba asenkron olarak eklenmesi sağlanır.
- “return $”{connectionID} is added {stocName}”” : Geriye, connect olan client’ın connectionID’si ve atandığı gurubun ismi dönülür.
- “PushNotify()” : Herhangi bir yatırım aracının fiyatı değiştiği zaman, bunu satın alan clientlara real time bildirilmesini sağlanır.
- “return Clients.Group(stocData.Name).InvokeAsync(“ChangeStocValue”, stocData)” :
- Clientların atandığı gurup ismi, satın alınan yatırım aracı ile aynı olduğu için “Clients.Group(stocData.Name)” komutu ile bu guruba dahil olan yani bu ürünü alam tüm clientlara erişilir.
- “InvokeAsync(“ChangeStocValue”, stocData)” : Değişen stok bilgisi, o guruba ait clientların (yukarıda Detail.cshtml sayfada anlatılan client side tarafdaki) “ChangeStocValue()” function’ına parametre olarak gönderilip tetiklenmesi sağlanır.
- “return Clients.Group(stocData.Name).InvokeAsync(“ChangeStocValue”, stocData)” :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class StocHub : Hub { public override Task OnConnectedAsync() { return Clients.Client(Context.ConnectionId).InvokeAsync("SetConnectionId", Context.ConnectionId); } public async Task<string> ConnectGroup(string stocName,string connectionID) { await Groups.AddAsync(connectionID,stocName); return $"{connectionID} is added {stocName}"; } public Task PushNotify(Stoc stocData) { return Clients.Group(stocData.Name).InvokeAsync("ChangeStocValue", stocData); } } |
Service :
Amaç: RabbitMQ’ye atılmış Stoc datasını, bağlı olduğu channel üzerinden dinlenip, data gelindiği zaman ilgili queue’den çekilmesi ve daha sonra bu yatırım aracını almış clientlara, son güncel verinin gönderilmesidir.
Kurulum :
- Yeni bir .Net Core Console projesi oluşturulur. “dotnet new console -o coreRabbitMQService“
- Yatırım araçlarının bilgilerini tutan “Stoc” model, aşağıda görüldüğü gibi aynı Admin ekranında olduğu gibi tanımlanır.
Models/Stoc.cs:
1 2 3 4 5 6 7 8 |
using System.Collections.Generic; public class Stoc { public int ID { get; set; } public string Name { get; set; } public decimal Value { get; set; } } |
- Oluşan Mvc projede “coreRabbitMQService.csproj” dosyası aşağıdaki gibi güncellenir. ==>”<PackageReference Include=”Microsoft.AspNetCore.SignalR.Client” Version=”1.0.0-alpha2-final” />” dosyası eklenir. Kaydetme işleminden sonra VS Code’un sorduğu Restore işlemine izin verilir. Ya da “dotnet restore” komutu ile ilgili signalR kütüphanesi yüklenir.
coreRabbitMQService.csproj:
1 2 3 4 5 6 7 8 9 10 11 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> <PackageReference Include="RabbitMQ.Client" Version="5.0.1" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="1.0.0-alpha2-final" /> </ItemGroup> </Project> |
- Projeye RabbitMQ Client kütüphanesi aşağıdaki gibi eklenir.
1dotnet add package RabbitMQ.Client
Program.cs : Localdeki RabbitMQ kuyruğu, tanımlanan channel’a göre dinlenip, yeni gelen Stoc datası aynı guruptaki Clientlara SignalR kullanılarak push edilir.
- “static HubConnection connectionSignalR” : Main() methodunun tepesinde global olarak static HubConnection değişkeni tanımlanır.
- “var factory = new ConnectionFactory() { HostName = “localhost” }” : Localdeki RabbitMQ Queue’ya bağlanılır.
- “using (var connection = factory.CreateConnection())” : Connection açılır.
- “using (var channel = connection.CreateModel())” : İlgili kanal açılır.
- ” Connect().Wait()” : Methodunda yeni bir HubConnection asenkron olarak Main() methodunun dışında tanımlanır. Çünkü Main methodu şu an için asenkron çalışmayı desteklememektedir.
- “public static async Task Connect()” : Connect methodunda asenkron olarak signalR StocHub sınıfına bağlanılır.
- “var connectionSignalR = new HubConnectionBuilder()”: SignalR StocHub sınıfına asenkron olarak bağlanmak için kullanılan bir methoddur.
- “.WithUrl(“http://localhost:1453/stochub”)” : SignalR sınıfı dünyanın başka bir yerinde de olabilir. Bu nedenle erişim IP’si tanımlanır.
- “await connectionSignalR.StartAsync()” : SignalR sınıfına asenkron bağlanılır. Bunun nedeni, ilgili sınıfa bağlanılma süresinin tam olarak bilinmemesi ve bundan dolayı çalışan sistemin boşuna bekletilmek istenmemesidir.
- Not: Dikkat edilir ise StartAsync() asenkron bir methoddur. Ama Halen .Net Core’da Program sınıfında “Main()” methodu asenkron yapıyı desteklememektedir. Daha önceki .Net Core versiyonunda “async” desteklense de bu özellik şu an için tekrardan kaldırılmıştır. Bu nedenlerden dolayı signalR sınıfa bağlanma işlemi, main methodu dışında asenkron olarak tanımlanmış ve Main() methodu içinde çağrılmıştır.
- “var connectionSignalR = new HubConnectionBuilder()”: SignalR StocHub sınıfına asenkron olarak bağlanmak için kullanılan bir methoddur.
- “channel.QueueDeclare(queue: “Bitcoin”” : RabbitMQ’da dinlecek channel’ın “Bitcoin” olduğu tanımlanır.
- “var consumer = new EventingBasicConsumer(channel)” RabbitMQ’da dinleyici, ya da alıcı da diyebileceğimiz tanımlama yapılır.
- “consumer.Received += (model, ea) =>” : RabbitMQ’da yeni bir “Stoc” paketi yakalandığında, Received event’i tetiklenir. Ve bu eventin altındaki kodlar çalıştırılır.
- “var body = ea.Body” : Yakalanan Stoc modeli body değişkenine atanır.
- “var data = Encoding.UTF8.GetString(body)” : Stoc model string’e çevrilir.
- “Stoc stoc = JsonConvert.DeserializeObject<Stoc>(data)” : string’e çevrilen data NewtosSoft kütüphanesi ile “Stoc” modele deserialize edilir.
- “connectionSignalR.InvokeAsync(“PushNotify”, stoc)” : Bu Stoc modele çevrilen data, SignalR “StocHub/PushNotify()” methodu tetiklenerek parametre olarak gönderilir. Böylece güncellenen yatırım aracı, kendisini takip eden clientlara push notification olarak gönderilir.
- “channel.BasicConsume()” : Methodu ile ilgili Queue’den mesajları çekme işlemine başlanır. Burada “autoAck” parametresi true olarak atanır ise, ilgili mesaj alındıktan sonra Queue’den otomatik olarak silinir.
- “Console.ReadLine()” : Methodu ile bu satırda kod bekletilip, RabbitMQ dinlenmeye devam edilir. Ve yeni bir paket yakalandığında “Received” tekrardan tetiklenip, yukarıda yapılan işlemlere baştan başlanı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 51 52 53 54 55 56 57 58 |
using System; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using Newtonsoft.Json; using RabbitMQ.Client; using RabbitMQ.Client.Events; namespace coreRabbitMQService { class Program { static HubConnection connectionSignalR; static void Main(string[] args) { var factory = new ConnectionFactory() { HostName = "localhost" }; using (var connection = factory.CreateConnection()) using (var channel = connection.CreateModel()) { //Trigger The SignalR Connect().Wait(); channel.QueueDeclare(queue: "Bitcoin", durable: false, exclusive: false, autoDelete: false, arguments: null); var consumer = new EventingBasicConsumer(channel); consumer.Received += (model, ea) => { var body = ea.Body; var data = Encoding.UTF8.GetString(body); Stoc stoc = JsonConvert.DeserializeObject<Stoc>(data); Console.WriteLine(" [x] Received {0}", stoc.Name + " : " + stoc.Value); connectionSignalR.InvokeAsync("PushNotify", stoc); //------------------------- }; channel.BasicConsume(queue: "Bitcoin", autoAck: true, consumer: consumer); Console.WriteLine(" Press [enter] to exit."); Console.ReadLine(); } } public static async Task Connect() { connectionSignalR = new HubConnectionBuilder() .WithUrl("http://localhost:1453/stochub") .WithConsoleLogger() .Build(); await connectionSignalR.StartAsync(); } } } |
Yukarıda yazılan console application sadece “Bitcoin” channel’ı dinlemek için yapılmıştır. Diğer channellar için de, tek tek konsol applicationlar yazılmalıdır.(Bu örnekteki diğer channelları dinleyen servisleri, Soruce Code’da bulabilirsiniz) Bu tabi çok fazla yükün olduğu durumlarda, performans amaçlı gidilen bir dağıtık mimarili microservis çözümüdür. İstenir ise tüm channellar için tek bir Console App de yazılabilirdi. Sonuçta gelen “Stoc.Name“‘e göre dinlenecek channel’ın, değişken olarak tanımlanması yeterlidir. İstenir ise, her bir yeni tanımlanacak yatırım ürünü için oluşturulan channel’i dinleyen yeni console applicationlar, otomatik olarak yaratılıp devreye alınabilir. Bu da size ödev olsun :) Unutulmamalıdır ki ne kadar çok MicroServices, o kadar zor Monitor ve Yönetilebilme demektir.
Geldik bir makalenin daha sonuna. Başlangıç videosunda da bahsettiğim gibi, bu senaryoyu gerçek hayatta birçok sorunu çözmek için uyarlıyabilirsiniz. Her şeyden önemlisi .Net Core 2.0, hayal ettiğim tüm Apilere tam destek vermekte ve hepsi macOS High Sierra’da canavar gibi çalışmaktadır. Yeni bir makalede görüşmek üzere hoşçakalın.
Source:
- https://www.rabbitmq.com/documentation.html
- https://www.rabbitmq.com/tutorials/tutorial-two-dotnet.html
- https://docs.microsoft.com/tr-tr/aspnet/signalr/overview/getting-started/introduction-to-signalr
- https://www.c-sharpcorner.com/UploadFile/abhijmk/what-why-and-how-about-signalr/
Source Code: https://github.com/borakasmer/CoreRabbitMQ
Selamlar Bora bey. Yazılarınızı ilgiyle takip ediyorum. Değerli paylaşımlarınız için çok teşekkür ederim. Size belki komik gelebilecek ama merak ettiğim 2 soruyu sormak ve 1 tane de ricada bulunmak istiyorum.
Sorularım,
1) Örnekleriniz hep local mac üzerinde geçiyor. Ancak bildiğiniz gibi server ve hostingler linux ya da windows üzerine oluyor. NET Core tercihim ise projelerimi linux işletim sistemleri üzerinde çalıştırmak için. Bu durumda hosting firmasından rabbitmq kurmalarını mı talep etmem gerekiyor? Böyle bir durumda kodlarda güncelleme yapmak gerekir mi?
2) Projelerinizi hazırlarken mac üzerinde core uygulamasını kullanıyorsunuz. Ancak mac için visual studio sürümü de mevcut. Core’u tercih etmenizin sebebi nedir? Yani VS yerine core mu kullanmayı önerirsiniz
Rica,
Baştan sona herşeyiyle mükemmel, yayına girecek bir proje olmasa da scratch olarak, entity framework, üyelik (facebook login vs dahil), signalr konularını içeren örnek bir core projesi geliştirebilir misiniz? Sizin gibi bir profesyonelin nasıl yaptığını görmek isteyecek benim gibi çok fazla takipçiniz-yazılım meraklısı olduğundan eminim.
Teşekkürler.
Selamlar Hakan Bey;
Öncelikle teşekkür ederim. .Net Core ile mac’de yazdığım kodun, windows ortamındakinden hiçbir farkı yoktur. Zaten github’a attığım kodları windows makinada da çalıştırabilirsiniz. Aynı :)
Hosting firmasına rabbitmq evet kurulumunu istemek zorundasınız. Ya da projeniz hosting firmasında kalsın RabbitMQ’yu azure gibi cloud bir ortama alın. Onlar ile derdiniz kalmasın.
Neden Core ve VsCode. Çünkü VsCode efsane hızlı ve light weight ondan. Neden Core çünkü kodlar istersem Linux istersem de windows’da çalıştırabilirim. Yani Platform özgürlüğümü arttırıyor ondan :)
Son ricanızın aslında biraz eski olsa da makalesi var ama core değil ==> http://www.borakasmer.com/mvcde-facebook-twitter-ve-google-oauth2-login-islemleri/
Bu da çok favori bir makalem. Şiddetle tavsiye ederim DropBox’a attığın resimleri SignalR ile RealTime gösteriyor.DropBox’dan çekip :)==> http://www.borakasmer.com/mvcde-coklu-resim-yukleme-ve-dropboxa-yedekleme/
selam lar bora bey,
yazılarınızı takip ediyorum elinize sağlık ufak bir sorum olacak sizlere.
Ben örnek olarak whatsapp ı vermek istiyorum.
A kullanıcısı B kullanıcısına mesaj ını attı fakat B kullanıcısı offline buraya kadar bir sıkıntı yok B kullanıcısı online a düştüğünde B kullanıcısına atılmış ve okunmamış mesajları Db den çekip yollamak mı daha mantıklı yoksa her kullanıcı için rabbitmq da her kullanıcı için bir chanel mı oluşturmak daha mantklı olur. Bir de ben kafka yı daha başarılı buluyorum nedersiniz
Selamlar,
RabbitMQ veya Kafka karşılaştırmasında kaybeden olmaz :) 2sinin de kendine göre + ları ve – leri var. Herbir client’a channel açılmaz. Bunun yolu DB de değil. Distributed bir cache. Mesela Redis. UserID;’ye ait mesajı Redis de tutun. Sonra Client SignalR ile Connect olunca ilgili UserID’ye ait tüm mesajları redisden çekip, yeni oluşan SigbalR ConnectionID’sine gönderin. Mesajlaşmalar yerine önemli belgeleir SQLveya başka bir DB’de tutun. Esas amaç DB’ye mümkün olduğunca yük bindirmemek.
İyi çalışmalar.
Merheba Bora Hocam;
Tüm Makalelerinizi heyecanla takip ediyorum.Biraz basit olacak beliki ama anlaymadım birşey var.
Api içerinde bulunan bir controller’dan BasicPublish ettikten sonra Bu mesajı örneğin aynı API içerisinde Repository’de karşılayıp Db’ye kaydedebilirmiyim.
Yoksa Console Application Gibi aktif çalışan bir service mi gerekiyor.Veya böyle bir durumda ne yapılmalı..
Selam Şükrü,
Öncelikle teşekkürler. BasicPublish etmenin amacı, farklı bir sunucudan vakit alıcak isleri asenkron olarak yürütmektir. Aynı sunucu üzerindeki bir class içinde işlemi yapıcak isen BasicPublish anlamsızdır.
İyi çalışmalar.
Hocam Dediğiniz doğru fakat aynı sunucu üzerinde olsa bile bir backgroundservice ile sırası ile Database’e kaydetmek yükü hafifletmezmi.. Veya yine Aynı sunucu üzerinde olsa bile Arka planda üyelere bilgilendirme email’i gibi görevleri Message Broker vasıtasıyla halletmek yükü hafifletir diye düşünüyorum.
merhabalar,
windows 10 makinasında Subsystem for Linux application aktiv ettim ve microsotf store dan ubuntu kurdum. daha sonra ubuntu uzerine rabbitmq kurdum. windows makinamdan visual studio da .net framework uygulamasından rabbitmq bağlanmaya çalıştığım zaman hata almaktayım. RabbitMQ.Client.Exceptions.BrokerUnreachableException: ‘None of the specified endpoints were reachable’ . IOException: connection.start was never received, likely due to a network timeout
Selamlar,
Sankim RabbitMQ ayakda değil gibi. Diğer bir ihtimal, RabbitMQ’nun 5672 portu sunucuda kapalı,firewall’a takılıyor ya da başka bir uygulama tarafından kullanılıyor.
İyi çalışmalar.
yardımlarınız için şimdiden teşekküğrler
üstad ben sunu anlayamadım bu yazdıgın consol uygulaması 3 ayrı servis gelen veriyi signal r ile ayrı bir uygulama mvc web projesi olan clinet a veriyi gönderiyor peki web uygulaması diger 3 uygulama HostName localhost diyerek anlaşabiliyorlarmı ?