SignalR ve Angular Kullanarak Net Core ile Gerçek Zamanlı Grafikler

SignalR uygulamalarımız da gerçek zamanlı grafikler kullanmamıza olanak sağlayan bir kütüphanedir.

Burada yapacağımız projeyi Github sayfamdan indirebilirsiniz.

Bu yazımda SignalR’yi Angular ve .Net Core ile nasıl kullanacağımızı anlatacağım. Server ve Client olarak iki proje oluşturacağız, öncelikle server tarafı için bir .Net Core Boş Api projesi oluşturuyoruz.


Temel yapılandırma ayarları için oluşturduğumuz projede bulunan Properties altındaki “launchSettings.json” dosyasını şu şekilde ayarlıyoruz;

{
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false, 
    "anonymousAuthentication": true, 
    "iisExpress": {
      "applicationUrl": "http://localhost:60967",
      "sslPort": 44342
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": false,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "RealTimeCharts_Server": {
      "commandName": "Project",
      "launchBrowser": false,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Sunucu tarafında localhost:5001 ve client tarafında çalışacak localhost:4200 bunlar arasında iletişim sağlaması için de Cors’u etkinleştiriyoruz. Startup.cs dosyasındaki classı aşağıdaki şekilde değiştiriyoruz.

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy("CorsPolicy",
            builder => builder.AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials());
    });
 
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
 
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
       app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }
 
    app.UseHttpsRedirection();
    app.UseCors("CorsPolicy");
 
    app.UseMvc();
}

Şu şekilde görünmeli;

Konfigürasyon ve Kurulum;

SignalR kütüphanesi zaten “Microsoft.AspNetCore.App” paketinin içerisinde hali hazırda mevcut, fakat client projesin de bu kütüphaneyi ayrıca yüklememiz gerekecek bunu client projesi aşamasında ayrıca anlatacağım.

Server projemiz de Models isminde bir klasör oluşturuyoruz ve bu klasör altında ChartModel isminde bir class oluşturacağız. Class içeriğimiz aşağıdaki gibi olacak.

public class ChartModel
{
    public List<int> Data { get; set; }
    public string Label { get; set; }
 
    public ChartModel()
    {
        Data = new List<int>();
    }
}

Burada Angular projemiz tarafından kullanılacak Data ve Label alanlarını tanımladık.

Şimdi HubConfig isminde yeni bir klasör daha oluşturuyoruz ve altına ChartHub isminde bir class ekliyoruz, class içeriğimiz aşağıda.

public class ChartHub: Hub
{
        
}

Burada bir hub oluşturduk, bunun amacı server ve client arasında ki iletişim sağlanması, Hub bu iletişimin temelini oluşturuyor. Class içeriğimiz boş, çünkü herhangi bir yönteme ihtiyacımız yok.

SignalR yapılandırmasını tamamlamak için Startup.cs dosyasında 2 ekleme daha yapıyoruz;

 services.AddSignalR(); 


 app.UseSignalR(routes =>    {        routes.MapHub<ChartHub>("/chart");    }); 

Startup.cs dosyasımızın son hali aşağıdaki gibi olacaktır;

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy",
                    builder => builder.AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials());
            });
            services.AddSignalR();
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseCors("CorsPolicy");
            app.UseSignalR(routes =>
            {
                routes.MapHub<ChartHub>("/chart");
            });
            app.UseMvc();
        }
    }

Gerçek zamanlı veri akışını simüle edebilmek için Timer classı uygulayacağız. Yeni bir klasör oluşturuyoruz TimerFeatures ve altına TimerManager isminde bir class ekliyoruz.

public class TimerManager
{
    private Timer _timer;
    private AutoResetEvent _autoResetEvent;
    private Action _action;
 
    public DateTime TimerStarted { get; }
 
    public TimerManager(Action action)
    {
        _action = action;
        _autoResetEvent = new AutoResetEvent(false);
        _timer = new Timer(Execute, _autoResetEvent, 1000, 2000);
        TimerStarted = DateTime.Now;
    }
 
    public void Execute(object stateInfo)
    {
        _action();
 
        if((DateTime.Now - TimerStarted).Seconds > 60)
        {
            _timer.Dispose();
        }
    }
}

Burada action bize her 2 saniyede 1 saniye duraklayarak çağrı işlevi kazandırıyor, sonsuz döngüyü engellemek için 60 saniyelik bir zaman aralığı ayarladık.

Projede kullanacağımız örnek datalarımızı oluşturmak için DataStorage isminde bir klasör daha oluşturuyoruz ve altına DataManager isimli bir class yaratıyoruz.

public static class DataManager
{
    public static List<ChartModel> GetData()
    {
        var r = new Random();
        return new List<ChartModel>()
        {
           new ChartModel { Data = new List<int> { r.Next(1, 40) }, Label = "Data1" },          
           new ChartModel { Data = new List<int> { r.Next(1, 40) }, Label = "Data2" },
           new ChartModel { Data = new List<int> { r.Next(1, 40) }, Label = "Data3" }, 
           new ChartModel { Data = new List<int> { r.Next(1, 40) }, Label = "Data4" }
        };
    }
}

Son olarak Controllers klasörü altında ChartController isimli bir controller oluşturuyoruz.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using SignalRDemo.DataStorage;
using SignalRDemo.HubConfig;
using SignalRDemo.TimerFeatures;

namespace RealTimeCharts_Server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ChartController : ControllerBase
    {
        private IHubContext<ChartHub> _hub;

        public ChartController(IHubContext<ChartHub> hub)
        {
            _hub = hub;
        }

        public IActionResult Get()
        {
            var timerManager = new TimerManager(() => _hub.Clients.All.SendAsync("receivechartdata", DataManager.GetData()));

            return Ok(new { Message = "Request Completed" });
        }
    }
}

Server tarafı projemizi böylece tamamladık şimdi client tarafı için Angular projesi oluşturacağız bunun için ben Visual Studio Code kullanıyorum ve projeyi orada oluşturuyorum.

Siz Github sayfamda bulunan örnek client projeyi indirerek de devam edebilirsiniz.

Angular’da grafikleri kullanabilmemiz için gerekli olan iki kütüphaneyi indirerek işleme başlıyoruz.

npm install ng2-charts --save
npm install chart.js --save

Gerekli kütüphaneleri yükledikten sonra angular.json dosyasını aşağıdaki kodu ekleyerek başlıyoruz.

"scripts": [
    "./node_modules/chart.js/dist/Chart.js"
]

ve son olarak app.module.ts dosyasını değiştirelim.

import { ChartsModule } from 'ng2-charts';
import { HttpClientModule } from '@angular/common/http';

HTTP isteklerini server tarafına ileteceğimiz için HttpClientModule ihtiyacımız var;

imports: [
    BrowserModule,
    ChartsModule,
    HttpClientModule
  ],

Yine app.module.ts dosyası üzerinde bir servis oluşturuyoruz

ng g service services/signal-r --spec false

ek olarak bir interface oluşturuyoruz;

export interface ChartModel {
    data: [],
    label: string
}

Bunu yaptıktan sonra servis dosyamızı aşağıdaki gibi değiştirelim

import { Injectable } from '@angular/core';
import * as signalR from "@aspnet/signalr";
import { ChartModel } from '../_interfaces/chartmodel.model';
 
@Injectable({
  providedIn: 'root'
})
export class SignalRService {
  public data: ChartModel[];
 
private hubConnection: signalR.HubConnection
 
  public startConnection = () => {
    this.hubConnection = new signalR.HubConnectionBuilder()
                            .withUrl('https://localhost:5001/chart')
                            .build();
 
    this.hubConnection
      .start()
      .then(() => console.log('Connection started'))
      .catch(err => console.log('Error while starting connection: ' + err))
  }
 
  public addTransferChartDataListener = () => {
    this.hubConnection.on('transferchartdata', (data) => {
      this.data = data;
      console.log(data);
    });
  }
}

app.component.ts dosyamızı değiştirerek devam ediyoruz.

import { Component, OnInit } from '@angular/core';
import { SignalRService } from './services/signal-r.service';
import { HttpClient } from '@angular/common/http';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
 
  constructor(public signalRService: SignalRService, private http: HttpClient) { }
 
  ngOnInit() {
    this.signalRService.startConnection();
    this.signalRService.addTransferChartDataListener();   
    this.startHttpRequest();
  }
 
  private startHttpRequest = () => {
    this.http.get('https://localhost:5001/api/chart')
      .subscribe(res => {
        console.log(res);
      })
  }
}

Şimdi app.component.html dosyamızı ekleyelim

<div style="display: block" *ngIf='signalRService.data'>
  <canvas baseChart
          [datasets]="signalRService.data"
          [labels]="chartLabels"
          [options]="chartOptions"
          [legend]="chartLegend"
          [chartType]="chartType"
          [colors]="colors"
          (chartClick)="chartClicked($event)"></canvas>
</div>

ve app.component.ts

import { Component, OnInit } from '@angular/core';
import { SignalRService } from './services/signal-r.service';
import { HttpClient } from '@angular/common/http';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  public chartOptions: any = {
    scaleShowVerticalLines: true,
    responsive: true,
    scales: {
      yAxes: [{
        ticks: {
          beginAtZero: true
        }
      }]
    }
  };
  public chartLabels: string[] = ['Real time data for the chart'];
  public chartType: string = 'bar';
  public chartLegend: boolean = true;
  public colors: any[] = [{ backgroundColor: '#5491DA' }, { backgroundColor: '#E74C3C' }, { backgroundColor: '#82E0AA' }, { backgroundColor: '#E5E7E9' }]
 
  constructor(public signalRService: SignalRService, private http: HttpClient) { }
 
  ngOnInit() {
    this.signalRService.startConnection();
    this.signalRService.addTransferChartDataListener();
    this.startHttpRequest();
  }
 
  private startHttpRequest = () => {
    this.http.get('https://localhost:5001/api/chart')
      .subscribe(res => {
        console.log(res);
      })
  }
}

Ve ulaştığımız nokta (http://localhost:4200)

Exit mobile version