In this tutorial, we will learn how to build a movie table that is constantly updating in realtime using ASP.NET and Pusher.
To follow this tutorial, please ensure that you are familiar with the basics of ASP.NET using C# and Vue.js 2.x
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 real-time table of movie titles, which updates once there is new data.
Pusher is a hosted service that makes it super-easy to add real-time data and functionality to web and mobile applications.
Pusher sits as a real-time 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.
Also, copy out your App ID, Key and Secret from the “App Keys” section, as we will need them later on.
The next thing we need to do is create a new Asp.Net MVC application.
To do so, let’s:
Visual C#
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:
1Install-Package PusherServer
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:
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:
1public 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.
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.
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:
1ApplicationDbContext 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.
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.
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:
Next, let’s also create the view file to show us all the current movies we have in real-time.
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 real-time 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:
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.
The codebase to this guide can be found here. Feel free to download and play around with the code.