All Components

Consume SignalR Events

You can use ASP.NET Core SignalR for pushing events from the server into the client application.

This sample application features a client-side Todo project with Angular and a Kendo UI for Angular Grid which consumes events from an ASP.NET Core SignalR backend. When the user adds a new item or updates an existing one, the data updates across multiple clients simultaneously.

Setting Up the Server

  1. Create the Hub and the Controller.

    using System.Threading.Tasks;
       using Microsoft.AspNetCore.SignalR;
       using server.Controllers;
    
       namespace server.Hubs
       {
           public class TodoHub : Hub
           {
    
           }
       }
    using System;
       using System.Collections.Concurrent;
       using System.Collections.Generic;
       using System.Linq;
       using System.Threading.Tasks;
       using Microsoft.AspNetCore.Mvc;
       using Microsoft.AspNetCore.SignalR;
       using server.Hubs;
    
       namespace server.Controllers
       {
           [Route("api/[controller]")]
           [ApiController]
           public class TodosController : ControllerBase
           {
               private ConcurrentDictionary<Guid, TodoItem> _store = new ConcurrentDictionary<Guid, TodoItem>();
    
               private readonly IHubContext<TodoHub> _todoHubContext;
    
               public TodosController(IHubContext<TodoHub> todoHubContext)
               {
                   _todoHubContext = todoHubContext;
               }
    
               [HttpGet]
               public ActionResult<IEnumerable<TodoItem>> Get()
               {
                   return Ok(_store.Values);
               }
    
               [HttpPost]
               public ActionResult<TodoItem> Post([FromBody] TodoItem todoItem)
               {
                   Guid key = Guid.NewGuid();
                   todoItem.Id = key;
                   _store.TryAdd(key, todoItem);
    
                   _todoHubContext.Clients.All.SendAsync("itemAdded", todoItem);
    
                   return Ok(todoItem);
               }
    
               [HttpPut("{id}")]
               public ActionResult<TodoItem> Put(Guid id, [FromBody] TodoItem todoItem)
               {
                   _store.TryGetValue(todoItem.Id, out TodoItem oldValue);
                   _store.TryUpdate(todoItem.Id, todoItem, oldValue);
    
                   _todoHubContext.Clients.All.SendAsync("itemUpdated", todoItem);
    
                   return Ok(todoItem);
               }
           }
    
           public class TodoItem
           {
               public Guid Id { get; set; }
               public string Value { get; set; }
               public bool Done { get; set; }
           }
       }
  2. Configure the pipeline for the HTTP request.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.HttpsPolicy;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using server.Hubs;
    
    namespace server
    {
       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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
               services.AddCors(options => options.AddPolicy("CorsPolicy",
                   builder =>
                   {
                       builder.AllowAnyMethod().AllowAnyHeader()
                           .AllowAnyOrigin()
                           .AllowCredentials();
                   }));
               services.AddSignalR();
           }
    
           // 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
               {
                   app.UseHsts();
               }
    
               app.UseCors("CorsPolicy");
               app.UseHttpsRedirection();
               app.UseSignalR(routes =>
               {
                   routes.MapHub<TodoHub>("/todohub");
               });
               app.UseMvc();
           }
       }
    }

Setting Up the Client

  1. Create an Angular service that will handle the communication with the server.

    import { Injectable } from '@angular/core';
    import { HubConnection, HubConnectionBuilder } from '@aspnet/signalr';
    import { Subject } from 'rxjs';
    import { Todo } from '../models/todo.model';
    
    @Injectable({
    providedIn: 'root'
    })
    export class SignalRService {
    private connection: HubConnection;
    itemUpdated: Subject<Todo> = new Subject<Todo>();
    itemAdded: Subject<Todo> = new Subject<Todo>();
    
    constructor() {
       this.connection = new HubConnectionBuilder()
       .withUrl('https://localhost:5001/todohub')
       .build();
       this.registerOnEvents();
       this.connection.start().catch(err => console.log(err.toString()));
    }
    
    registerOnEvents() {
       this.connection.on('itemAdded', item => {
       console.log('itemAdded');
       this.itemAdded.next(item);
       });
    
       this.connection.on('itemUpdated', item => {
       console.log('itemUpdated');
       this.itemUpdated.next(item);
       });
    }
    }
  2. Hook the host Grid component so that it consumes the service and the SignalR backend.

    constructor(
       private readonly signalrService: SignalRService,
       private readonly http: HttpClient
    ) {
       signalrService.itemAdded.subscribe(item => {
       this.items = [item, ...this.items];
       });
       signalrService.itemUpdated.subscribe(item => {
       this.items = this.items.filter(x => x.id !== item.id);
       this.items = [item, ...this.items];
       });
    }
    ngOnInit() {
       this.http
       .get<Todo[]>('https://localhost:5001/api/todos/')
       .subscribe(items => {
           this.items = items;
       });
    
       this.form = new FormGroup({
       todoValue: new FormControl('', Validators.required)
       });
    }
    
    addTodo() {
       const toSend = { value: this.form.value.todoValue };
    
       this.http
       .post('https://localhost:5001/api/todos/', toSend)
       .subscribe(() => console.log('added'));
    
       this.form.reset();
    }
    
    markAsDone(item: Todo) {
       item.done = true;
       this.http
       .put('https://localhost:5001/api/todos/' + item.id, item)
       .subscribe(() => console.log('updated'));
    }
  3. Bind the Grid.

    <form (ngSubmit)="addTodo()" [formGroup]="form">
    <input class="k-textbox" type="text" formControlName="todoValue">
    <button [disabled]="form.invalid">Add Todo</button>
    </form>
    
    <kendo-grid [kendoGridBinding]="items"
    [pageable]="true"
    [pageSize]="5"
    [filterable]="true"
    [sortable]="true">
    <kendo-grid-column field="id" width="350" title="ID"></kendo-grid-column>
    <kendo-grid-column field="value" title="Task"></kendo-grid-column>
    <kendo-grid-column field="done" filter="boolean" title="Status" width="200">
       <ng-template kendoGridCellTemplate let-dataItem>
       <input type="checkbox" disabled="disabled" checked="{{ dataItem.done ? 'checked': undefined }}" />
       </ng-template>>
    </kendo-grid-column>
    <kendo-grid-column>
       <ng-template kendoGridCellTemplate let-dataItem>
           <button class="k-button k-primary" [disabled]="dataItem.done" (click)="markAsDone(dataItem)">Mark as done</button>
       </ng-template>
    </kendo-grid-column>
    </kendo-grid>
In this article