Login With Github

Streaming in ASP.NET Core SignalR

What is Streaming?

Streaming is a technique for transmitting data in the form of a steady continuous stream.

Usage Scenarios for Streaming

In some scenarios, if the amount of data returned by the server is larger and the waiting time is longer, the client has to wait for the server to return all data before other corresponding operations can be performed. Luckily, the server data can be fragmented by using streaming. Only the completed part will be transmitted once each of the data fragments finished being read, so you don't need to wait for all data to be read.

How to Enable Streaming in ASP.NET Core SignalR

In ASP.NET Core SignalR, when the returned value of a Hub method is ChannelReader or Task<ChannelReader>, the Hub method will become a streaming Hub method automatically.

Let's take a look at a simple example below.

Create an ASP.NET Core Web Application

First let's create an ASP.NET Core Web application by using Visual Studio 2017.

Then choose to create a Web Application of ASP.NET Core 2.1.

Create a Hub

Let's add a StreamHub class, and the code is as follows.

public class StreamHub : Hub
{   
    public ChannelReader<int> DelayCounter(int delay)
    {
        var channel = Channel.CreateUnbounded<int>();

        _ = WriteItems(channel.Writer, 20, delay);

        return channel.Reader;
    }

    private async Task WriteItems(ChannelWriter<int> writer, int count, int delay)
    {
        for (var i = 0; i < count; i++)
        {
            await writer.WriteAsync(i);
            await Task.Delay(delay);
        }

        writer.TryComplete();
    }
}
  • DelayCounter is a streaming method, which defines a delay parameter delay, and the delay parameter defines the interval between pushing data fragments.
  • WriteItems is a private method, and it returns a Task object
  • The last line writer.TryComplete() of the WriteItems method indicates that the streaming has been completed.

Configure SignalR

First let's add the SignalR service to the ConfigureService method of the Startup class.

services.AddSignalR();

Then we need to add a route to the SignalR streaming, so let's add the following code in the Configure method of the Startup class:

app.UseSignalR(routes =>
{
   routes.MapHub<StreamHub>("/streamHub");
});

Add the SignalR Client Script Library

In this step we need to add the SignalR JS library into the client.

Here we need to use npm to download the SignalR JS library.

npm install @aspnet/signalr

Copy the signalr.js from the <projectfolder>\node_modules@aspnet\signalr\dist\browser directory to the wwwroot\lib\signalr directory manually after installation.

Write Page

Copy the following code into Index.cshtml.

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="container">
    <div class="row">&nbsp;</div>
    <div class="row">
        <div class="col-6">&nbsp;</div>
        <div class="col-6">
            <input type="button" id="streamButton" value="Start Streaming" />
        </div>
    </div>
    <div class="row">
        <div class="col-12">
            <hr />
        </div>
    </div>
    <div class="row">
        <div class="col-6">&nbsp;</div>
        <div class="col-6">
            <ul id="messagesList"></ul>
        </div>
    </div>
</div>
<script src="~/lib/signalr/signalr.js"></script>
<script src="~/js/signalrstream.js"></script>

Enable Streaming in JavaScript

Create a new file signalrstream.js in the wwwroot\js directory with the following code:

var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};

var connection = new signalR.HubConnectionBuilder()
    .withUrl("/streamHub")
    .build();
document.getElementById("streamButton").addEventListener("click", (event) => __awaiter(this, void 0, void 0, function* () {
    try {
        connection.stream("DelayCounter", 500)
            .subscribe({
                next: (item) => {
                    var li = document.createElement("li");
                    li.textContent = item;
                    document.getElementById("messagesList").appendChild(li);
                },
                complete: () => {
                    var li = document.createElement("li");
                    li.textContent = "Stream completed";
                    document.getElementById("messagesList").appendChild(li);
                },
                error: (err) => {
                    var li = document.createElement("li");
                    li.textContent = err;
                    document.getElementById("messagesList").appendChild(li);
                },
            });
    }
    catch (e) {
        console.error(e.toString());
    }
    event.preventDefault();
}));

(() => __awaiter(this, void 0, void 0, function* () {
    try {
        yield connection.start();
    }
    catch (e) {
        console.error(e.toString());
    }
}))();

code interpretation

Unlike the typical SignalR, here we use a different syntax to create a SignalR connection.

var connection = new signalR.HubConnectionBuilder()
    .withUrl("/streamHub")
    .build();

For the common SignalR connection, we use the connection.on method to add the listener. However, we need to use the connection.stream method when using streaming, and this method has 2 parameters:

  • the name of the Hub method, and in this case it's DelayCounter.
  • the parameter of the Hub method, and in this case it's 500.
connection.stream("DelayCounter", 500)
    .subscribe({
        next: (item) => {
            var li = document.createElement("li");
            li.textContent = item;
            document.getElementById("messagesList").appendChild(li);
        },
        complete: () => {
            var li = document.createElement("li");
            li.textContent = "Stream completed";
            document.getElementById("messagesList").appendChild(li);
        },
        error: (err) => {
            var li = document.createElement("li");
            li.textContent = err;
            document.getElementById("messagesList").appendChild(li);
        },
});

There is a subscribe method in the returned object from the connection.stream method. You can register the following 3 events in the method:

  • next - to be executed when getting one data fragment
  • complete - to be executed when streaming is complete
  • error - to be executed when the streaming occurs an exception

Final effect

Sum up

Streaming is not a new concept, but it is a great feature for ASP.NET Core SignalR. Streaming can be used to guarantee a smooth user experience and reduce the server stress.

Most programmers know that SignalR can't transfer too much data, but after using streaming, the client doesn't need to wait for the server to return all the data at once, so if your project has a large amount of data for a single request, you're recommended to consider using streaming for SignalR to improve user experience and reduce server stress.

Source Code: https://github.com/lamondlu/StreamingInSignalR

3 Comments

temp

[DUPLICATED CONTENT]

This article is a clone made out of https://coderethinked.com/streaming-data-in-asp-net-core-signalr/.The author just changed the titles and modified some of the text. 

He also cloned project on github and tweaked just the source code of it.

Please do not duplicate the content on web to just attract users.

To be fair, both tutorials have just copied and regurgitated Microsoft's example.

https://docs.microsoft.com/en-us/aspnet/core/signalr/streaming?view=aspnetcore-3.0

ts file in transpile into js, so don't need to copy js code.