Build a realtime table using ASP.NET

Introduction

We often need to display data in a table. However, unless it’s a realtime table, we need to reload to view new data each time that it’s added. For example, take a table of movies arranged by the year they were released. Each time a movie is added, we would not know of the changes until we reload our page - which is not the best experience for a user.

Today, we will solve this problem by creating a realtime table of movie titles, which updates once there is new data.

To follow this tutorial, please ensure that you are familiar with the basics of:

  • ASP.NET using C#
  • Vue.js 2.x

Setting up a Pusher account and app

Pusher is a hosted service that makes it super-easy to add realtime data and functionality to web and mobile applications.

Pusher sits as a realtime layer between your servers and your clients. Pusher maintains persistent connections to the clients - over Web-socket if possible and falling back to HTTP-based connectivity - so that as soon as your servers have new data that they want to push to the clients they can do, instantly via Pusher.

If you do not already have one, head over to Pusher and create a free account.

We will register a new app on the dashboard. The only compulsory options are the app name and cluster. A cluster represents the physical location of the Pusher server that will handle your app’s requests.

realtime-table-aspnet-create-app

Also, copy out your App ID, Key and Secret from the “App Keys” section, as we will need them later on.

Setting up the Asp.Net project in Visual Studio

The next thing we need to do is create a new Asp.Net MVC application. To do so, let’s:

  • Open Visual Studio and select new project from the sidebar
  • Under templates, select Visual C#
  • Next, select web
  • In the middle section, select ASP.NET Web Application.

For this tutorial, I named the project: pusher_realtime_table.

Now we are almost ready. The next step will be to install the official Pusher library for .Net using the NuGet Package.

To do this, we go to tools on the top bar, click on NuGet Package Manager, on the drop-down we select Package Manager Console.

We will see the Package Manager Console at the bottom of our Visual Studio. Next, let’s install the package by running:

    Install-Package PusherServer

Crafting our application

Now that our environment is set up and ready, let’s dive into writing code.

By default, Visual Studio creates three controllers for us, however we will use the HomeController for the application logic.

The first thing we want to do is to define a model that stores the list of movies we have in the database.

Under the ‘models’ folder, let’s create a file named realtimetable.cs and add the following content:

1using System;
2    using System.Collections.Generic;
3    using System.ComponentModel.DataAnnotations;
4    using System.Linq;
5    using System.Web;
6    
7    namespace pusher_realtime_table.Models
8    {
9        public class RealtimeTable
10        {
11            [Key]
12            public int id { get; set; }
13            [Required]
14            [MaxLength(225)]
15            public string title { get; set; }
16            [Required]
17            public int year { get; set;  } 
18    
19        }
20    }

In the above block of code, we have declared a model called RealtimeTable with three main properties:

  • id : This is the primary key of the model table.
  • title: The title of the movie we are saving to the database
  • year: The year the movie was released.

Now that we have defined our model, let’s go ahead and reference it in our default database context called ApplicationDbContext. To do this, let’s open up models\IdentityModels.cs file, then locate the class called ApplicationDbContext and add the following after the create function:

    public DbSet<RealtimeTable> realtime { get; set; }

In the code block above, DBSet class represents an entity set that is used for create, read, update, and delete operations. The entity which we will use to do CRUD operations is the realtimetable model we created earlier, and we have given it the name realtime.

Connecting our database

Although our model is setup, we still need to attach a database to our application. To do so, select the Server Explorer by the left hand side of our Visual Studio, right click on Data Connections and add a database.

Creating our index route

Now both our model and database is set to work, let’s go ahead creating our index route. Open the HomeController and replace it with the following code:

1using pusher_realtime_table.Models;
2    using System;
3    using System.Collections.Generic;
4    using System.Linq;
5    using System.Web;
6    using System.Web.Mvc;
7    using PusherServer;
8    using System.Net;
9    using System.Threading.Tasks;
10    
11    namespace pusher_realtime_table.Controllers
12    {
13        public class HomeController : Controller
14    
15        {
16            ApplicationDbContext db = new ApplicationDbContext();
17            public ActionResult Index()
18            {
19                return View();
20            }
21    
22            [HttpPost]
23            public async Task<ActionResult> Index(RealtimeTable data)
24            {
25                realtimetable setdata = new RealtimeTable();
26                setdata.title = data.title;
27                setdata.year = data.year;
28                db.realtime.Add(setdata);
29                db.SaveChanges();
30                var options = new PusherOptions();
31                options.Cluster = "XXX_APP_CLUSTER";
32                var pusher = new Pusher("XXX_APP_ID", "XXX_APP_KEY", "XXX_APP_SECRET", options);
33                ITriggerResult result = await pusher.TriggerAsync("asp_channel", "asp_event", data);
34                return RedirectToAction("view", "Home");
35            }       
36        }
37    }

In the code block above, we have defined our Index function for both GET and POST requests. Before looking at our GET and POST controller functions, we notice that there is an import of our db context into our class with the line that says:

    ApplicationDbContext db = new ApplicationDbContext();

This makes it possible to access our database model which we have defined using the DbSet class in our ApplicationDbContext class.

In the GET function, we have returned the view which we will be using to add a new movie into our database.

Notice that the POST method is set to be asynchronous. This is due to the fact that the Pusher .NET library uses the await operator to wait for the asynchronous response from the data emitted to Pusher.

In this function, we first add our new movie to the database, then we trigger an event. Once the event has been successfully emitted, we then return a redirect to our view function which we will be creating soon.

Creating our view route

Now that we have defined our index route, we can add new movies to the database, though we cannot see the details of the movies we have added. To do that, we need to define our view route, which returns a table of all the movies we have in our database.

Let’s open our HomeController and add the following functions:

1public ActionResult seen()
2            {
3                return Json(db.realtime.ToArray(), JsonRequestBehavior.AllowGet);
4            }
5    
6    public ActionResult view()
7            {
8                return View();
9            }

In the seen function, we have exposed a webservice that returns a JSON result of all the movies we have in our database.

In the view function, we return our view which shows us the list of our movies rendered with Vue.

Creating our view files

Let’s open up our Views\Home\Index.cshtml and replace the content with the following:

1@model pusher_realtime_table.Models.RealtimeTable
2    
3    @{
4        ViewBag.Title = "Index";
5        Layout = "~/Views/Shared/_Layout.cshtml";
6    }
7    
8    <h2>Index</h2>
9    
10    @using (Html.BeginForm()) 
11    {
12        @Html.AntiForgeryToken()
13    
14        <div class="form-horizontal">
15            <h4>realtimetable</h4>
16            <hr />
17            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
18            <div class="form-group">
19                @Html.LabelFor(model => model.title, htmlAttributes: new { @class = "control-label col-md-2" })
20                <div class="col-md-10">
21                    @Html.EditorFor(model => model.title, new { htmlAttributes = new { @class = "form-control" } })
22                    @Html.ValidationMessageFor(model => model.title, "", new { @class = "text-danger" })
23                </div>
24            </div>
25    
26            <div class="form-group">
27                @Html.LabelFor(model => model.year, htmlAttributes: new { @class = "control-label col-md-2" })
28                <div class="col-md-10">
29                    @Html.EditorFor(model => model.year, new { htmlAttributes = new { @class = "form-control" } })
30                    @Html.ValidationMessageFor(model => model.year, "", new { @class = "text-danger" })
31                </div>
32            </div>
33    
34            <div class="form-group">
35                <div class="col-md-offset-2 col-md-10">
36                    <input type="submit" value="Create" class="btn btn-default" />
37                </div>
38            </div>
39        </div>
40    }
41    
42    <div>
43        @Html.ActionLink("Back to List", "Index")
44    </div>

In the above block of code, we have created our form which consists of three main inputs, which are:

  • Text input for the movie title
  • Text input for the movie year
  • Button to save the new entry into the database.

Next, let’s also create the view file to show us all the current movies we have in realtime.

Let's create a new file called view.cshtml in our Views\Home folder, and add the following content:

1@{
2        ViewBag.Title = "view";
3        Layout = "~/Views/Shared/_Layout.cshtml";
4    }
5    
6    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
7    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.2/axios.min.js"></script>
8    <script src="//js.pusher.com/4.0/pusher.min.js"></script>
9    
10    
11    <h2>Real-Time Table</h2>
12    
13    
14    <table class="table" id="app">
15        <tr>
16            <th>
17                Sn
18            </th>
19            <th>
20               Title
21            </th>
22            <th>
23                Year
24            </th>
25    
26        </tr>
27    
28    
29        <tr v-for="(mov, index) in sorted_movies">
30            <td>
31            {{index+1}}
32            </td>
33            <td>
34             {{mov.title}}
35            </td>
36            <td>
37            {{mov.year}}
38            </td>
39        </tr>
40    
41    
42    </table>
43    <script>
44        var pusher = new Pusher('XXX_APP_KEY, {
45            cluster: 'XXX_APP_CLUSTER'
46        });
47        var my_channel = pusher.subscribe('asp_channel');
48        var app = new Vue({
49            el: '#app',
50            data: {
51                movies: []
52            },
53            created: function () {
54                this.get_movies();
55                this.listen();
56            },
57            methods: {
58                get_movies: function () {
59                    axios.get('@Url.Action("seen", "Home")')
60                      .then((response)=> {
61    
62                          this.movies = response.data;
63    
64                      });
65    
66                },
67                listen: function () {
68                    my_channel.bind("asp_event", (data) => {
69                        this.movies.push(data);
70                    })
71                }
72            },
73            computed: {
74                sorted_movies: function () {
75                    var movies = this.movies;
76    
77                    movies =  movies.sort(function (a, b) {
78                        return parseInt(a.year) - parseInt(b.year);
79                    });
80    
81                    return movies;
82                }
83            }
84        });
85    </script>

In the view file above, notice that we have included three new libraries which are:

  • vue.min.js: This is the Vue js library which will be used to render our data.
  • axios.min.js: This is the official Axios library, which we will be using to make HTTP requests to our server.
  • pusher.min.js: This is the official Pusher JavaScript client, with which we will be receiving our realtime data.

Our markup is pretty simple. It consists of an HTML table which renders all our movies using Vue.

We need to pay attention to the script section of our View file. This is where all the magic goes on. Just before we declare our Vue app, we instantiated Pusher by calling the Pusher object while passing in our app key and cluster.

Next, we subscribe to the asp_channel event. In the created function, we fire the get_movies function, which uses Axios to fetch the list of all our movies.

Next, we fired the listen function which watches for the arrival of the new data, then pushes them to the array of all our movies.

Also, notice that we have a computed property called sorted_movies, which returns a sorted list of our movies based on the year.

Below is a picture of what we have built:

realtime-table-aspnet-demo

Conclusion

In the course of this tutorial, we have covered how to build a realtime table using .NET and Pusher. We have gone through the process of setting up the environment, using the NuGet Package Manager to install the required Pusher library.