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" class="k-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

Not finding the help you need?