Instructor-led online courses in ASP.NET Core, ASP.NET MVC, and ASP.NET Design Patterns. Read more...
Live instructor-led online courses in ASP.NET Core and Design Patterns. Registration is open for December 2018 batch. More details are available here.

Utilize Server Sent Events (SSE) in ASP.NET Core

Some web applications application need to show data in real-time. As soon as the data is made available at the server, it immediately needs to be displayed to the end user. Traditionally developers used to poll the server periodically to check if a new data is available. This approach has its own drawbacks and may prove to be unsuitable in certain cases. Wouldn't it be nice if server notifies the client of new data rather than client checking with the server periodically? That is what HTML5 Server Sent Events (SSE) allow you to do. In this article you will learn what Server Sent Events or SSEs are and how to develop a ASP.NET Core application that receives real-time data from the server.

Ajax polling vs. SSEs

Web applications that need to display real-time data often use "polling" techniques to fetch the latest data from the server. For example, you may develop an ASP.NET web form that makes Ajax requests to the server periodically to check whether new data is available. Accordingly the web form renders itself showing the latest data. However, such techniques have their own drawbacks including:

  • Polling techniques make too many requests to the web server if the frequency of polling is too short. This causes burden on the server.
  • Since polling operations are performed by the client there is no way to tell the client whether new data is really available on the server. A client may keep polling the server periodically even if there is no data available. This is unnecessary overhead on the overall system.
  • Polling technique is less accurate. Since polling frequency is decided by the client and is independent of server side data availability it may so happen that data is available on the server now but the client is able to show the new data after some time gap.

The Server Sent Events or SSEs are dispatched by the server. Using SSE you can notify the client application whenever something interesting (say availability of new data) happens on the server. The client can then take appropriate action (say displaying the new data to the user).

Example of using SSE with ASP.NET Core

To understand how SSEs can be used in ASP.NET Core application you will develop an application as shown below:

The application has a view with Start Listening button. Clicking on this button causes the browser to open a connection with a specified Event Source. The event source is responsible to send the notification data back to the browser. Once the even source sends the data the connection to the event source is closed.

Begin by creating a new ASP.NET Core web application and set it up as usual by adding various folders and startup code.

NorthwindDbContext and Customer class

We need to fetch data from the Customers table of the Northwind database. This data is then sent to the client. So, add the NorthwindDbContext and Customer classes as shown below :

[Table("Customers")]
public class Customer
{
    [Key]
    public string CustomerID { get; set; }
    public string CompanyName { get; set; }
    public string ContactName { get; set; }
    public string Country { get; set; }
}
public class NorthwindDbContext:DbContext
{

    public NorthwindDbContext(
DbContextOptions<NorthwindDbContext> 
options) : base(options)
    {

    }

    public DbSet<Customer> Customers { get; set; }

}

The Customer class consists of four public properties namely CustomerID, CompanyName, ContactName, and Country. The NorthwindDbContext class houses the Customers DbSet.

The DbContext will be injected from the startup class as follows :

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddEntityFrameworkSqlServer();

    services.AddDbContext<NorthwindDbContext>
(o => o.UseSqlServer(
"data source=.;initial catalog=Northwind;integrated 
security=true;multipleactiveresultsets=true;"));

}

The injected DbContext is received in the HomeController as shown below :

public class HomeController : Controller
{
    private NorthwindDbContext db;

    public HomeController(NorthwindDbContext db)
    {
        this.db = db;
    }
}

Process() action

In addition to the Index() action, the HomeController will have an action that acts the event source. This action - Process() - is shown below:

public IActionResult Process()
{
    StringBuilder sb = new StringBuilder();
    foreach (Customer obj in db.Customers.
OrderBy(i => i.CustomerID).Take(5))
    {
        string jsonCustomer = 
JsonConvert.SerializeObject(obj);
        sb.AppendFormat("data: {0}\n\n", jsonCustomer);
    }
    return Content(sb.ToString(), "text/event-stream");
}

 

The Process() action creates a StringBuilder for storing the data that is to be sent back to the browser. The for loop simply iterates through the Customers DbSet and grabs an individual Customer object. This Customer object is converted to its JSON representation using Json.Net component's JsonConvert class. The SerializeObject() method accepts a .NET object and returns its JSON equivalent. The JSON data thus obtained is pushed into the StringBuilder object. Notice how the data is pushed into the StringBuilder. It's data followed by a : and then followed by a JSON object. The \n\n at the end indicates end of the data (a single Customer object in this case).

Also notice how the data is returned from the Process() action. The Content() method of the Controller base class takes two parameters - data to be send to the browser and its Content-Type. The data comes from the StringBuilder. The Content-Type is specified as text/event-stream. This content type is required for the proper functioning of SSEs.

This completes the server side code. Let's shift our attention to the client side code.

Index view

The Index view consists of a simple form as shown below:

<form>
    <input type="button" id="btnListen" 
           value="Start Listening" />
    <br /><br />
    <div id="headerDiv" class="tickerheading"></div>
    <div id="targetDiv" class="ticker"></div>
    <div id="footerDiv" class="tickerfooter"></div>
</form>

The form contains the Start Listening button and three <div> elements. The headerDiv displays the header message, the footerDiv displays the footer message and the targetDiv displays the data returned by the server.

jQuery code that uses the EventSource

The jQuery code that implements SSE in the Index view is shown below:

$(document).ready(function () {

    $("#btnListen").click(function () {

        var source = new EventSource('/home/process');

        source.addEventListener("open", function (event) {
            $('#headerDiv').append
             ('<h1>Processing started...</h1>');
        }, false);


        source.addEventListener("error", function (event) {
            if (event.eventPhase == EventSource.CLOSED) {
                $('#footerDiv').append
                 ('<h1>Connection Closed!</h1>');
                source.close();
            }
        }, false);

        source.addEventListener("message", function (event) {
            var data = JSON.parse(event.data);
            $("#targetDiv").append
             ("<div>" + data.CustomerID + 
              " - " + data.CompanyName + "</div>");
        }, false);

    });
});

The code consists of four pieces.

  • In the click event handler an EventSource is created pointing to /home/process. Thus EventSource object will attempt to open a connection to this specified resource.
  • If the connection is opened successfully, the open event is raised by the EventSource object. The open event can be handled using the addEventListener() method. The open event handler simply displays a message in the headerDiv.
  • If there is any error while communicating the error event is raised. Inside, you can check the eventPhase and take appropriate action. In this case the code checks whether the underlying event source has been closed. If so it displays a message in the footerDiv and also calls the close() method of the EventSource. If you don't call the close() method the browser will again trigger the source after three seconds. Calling close() ensures that browser doesn't repeatedly trigger the event source. Of course, if you wish to have such repeated triggering, you shouldn't close the event source.
  • When the server sends event notification to the browser the message event is raised. The message event handler retrieves the data using the event.data property. In this case event.data will be a single Customer object in JSON format. And message will be raised multiple times depending on the data being sent from the server. The CustomerID and CompanyName are then appended to the targetDiv element.

If you run the application, you should get an output similar to the figure shown earlier.

Dealing with lengthy server side processing

So far so good. In the above example, the server side processing was quite straightforward. However, at times the server side processing might be quite lengthy and a lot of data might need to be sent to the client. In such cases, rather than waiting for the computation of all the data it would be better to send data in small chunks (say one Customer at a time). This way the client will receiving something from the server even if the server side processing is yet to complete. If you wish to accomplish such a task you can modify the Process() action like this:

public void Process()
{
    Response.ContentType = "text/event-stream";
    foreach (Customer obj in db.Customers)
    {
        string jsonCustomer = JsonConvert.SerializeObject(obj);
        string data = $"data: {jsonCustomer}\n\n";
        System.Threading.Thread.Sleep(5000);
        HttpContext.Response.WriteAsync(data);
        HttpContext.Response.Body.Flush();
    }
    Response.Body.Close();
}

In this case instead of using the Content() method to return the IActionResult, the code uses Response object. The ContentType property of the Response is set to text/event-stream. A for each loop iterates through all the Customer objects. A Customer is converted into its JSON representation as before and written to the Response stream using WriteAsync() method. The Flush() methods of Response body ensures that the data is flushed to the browser. The Close() closes the Response stream thus terminating the event source. Note that in the above code a delay of 5000 milliseconds is introduced to mimic some lengthy processing.

If you run the modified application, you will observe that a Customer is displayed after every five seconds instead of displaying all the customers at a time.

That's it for now! Keep coding !!


Bipin Joshi is a software consultant, trainer, author and spiritual yoga mentor having 23+ years of experience in software development. He teaches online training courses in ASP.NET Core, Angular, and Design Patterns to individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 12 March 2018


Tags : ASP.NET ASP.NET Core MVC .NET Framework C#


Subscribe to our newsletter

Get monthly email updates about new articles, tutorials, code samples, and how-tos getting added to our knowledge base.