Utilize Server Sent Events in ASP.NET MVC
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 HTML5 Server Sent Events are and how to develop
a ASP.NET MVC 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 using SSE with ASP.NET MVC Controller
To understand how SSEs can be used in ASP.NET MVC 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 MVC project and add HomeController to it. In
addition to the Index() action, the HomeController will have an action that acts
the event source. This action - Process() - is shown below:
public ActionResult Process()
{
StringBuilder sb = new StringBuilder();
using (Northwind db = new Northwind())
{
foreach (Customer obj in db.Customers)
{
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.
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.
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";
using (Northwind db = new Northwind())
{
foreach (Customer obj in db.Customers)
{
string jsonCustomer = JsonConvert.
SerializeObject(obj);
string data = $"data: {jsonCustomer}\n\n";
System.Threading.Thread.Sleep(5000);
Response.Write(data);
Response.Flush();
}
Response.Close();
}
}
In this case instead of using the Content() method to return the ActionResult,
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 Write() method. The Flush() methods of Response
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 !!