How to build a photo feed using .NET and Pusher

build-a-photo-feed-using-net-and-pusher-header.png

In this tutorial, we will learn how you can create a realtime photo feed and a handling file uploads using .NET and Pusher.

Introduction

Today, we will make a realtime photo feed using .NET and Pusher.

We will build a mini system that allows people to upload their images/photographs for everyone to view in realtime. While this can be likened to a mini-Instagram, it is without the comment, like and views aspect. Sounds cool? Let’s ride on.

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

  • C# ASP.NET MVC
  • jQuery

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 WebSockets if possible and falling back to HTTP-based connectivity – so that as soon as your servers have new data they want to push to the clients they can do so 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, select jQuery as the front-end technology, and ASP.NET as the back-end tech for this tutorial. For other projects, you can choose as per your requirements.
Next, 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 MVC Web Application.

For this tutorial, I named the project: Real-time-photo-feed.
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, via the menu 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

Alternatively, we can also install the Pusher library using the NuGet Package Manager UI. To do this, in the **S**``olution Explorer, right-click either References or a project and select Manage NuGet Packages. The Browse tab displays available packages by popularity. Search for the Pusher package by typing in PusherServer into the search box on the top right. Select the Pusher package to display the package information on the right and to enable the Install button.

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 images we have in the database.
Under the models folder, let’s create a file named PhotoFeed.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 Real_time_photo_feed.Models
8    {
9        public class PhotoFeed
10        {
11            [Key]
12            public int Id { get; set; }
13            [Required]
14            public string Comment { get; set; }
15
16            public string Imagepath { get; set; }
17
18        }
19    }

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

  • Id: This is the primary key of the model table.
  • Comment: The description of the image.
  • Imagepath: The path to the stored image.

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

1public DbSet<PhotoFeed> FeedModel { get; set; }

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

Connecting our database

Although our model is set up, we still need to attach a database to our application. To do so, select the Server Explorer on the left-hand side of our Visual Studio, right click on Data Connections and add a database.
There are various databases that are lightweight and can fit into the application we are building, such as:

  • Microsoft Access database
  • SQLite Database
  • MSSQL Server
  • Firebird
  • VistaDb

For this tutorial, I used the MSSQL Server.

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 PusherServer;
2    using Real_time_photo_feed.Models;
3    using System;
4    using System.Collections.Generic;
5    using System.IO;
6    using System.Linq;
7    using System.Threading.Tasks;
8    using System.Web;
9    using System.Web.Mvc;
10
11    namespace Real_time_photo_feed.Controllers
12    {
13        public class HomeController : Controller
14        {
15            ApplicationDbContext db = new ApplicationDbContext();
16            public ActionResult Index()
17            {
18               var me = db.FeedModel.AsQueryable();
19
20                return View(me);
21            }
22            [HttpPost]
23            public async Task<ActionResult> Index(PhotoFeed feed, HttpPostedFileBase upload)
24            {
25
26                if (ModelState.IsValid)
27                {
28                    if (upload != null && upload.ContentLength > 0)
29                    {
30                        var FileName = System.IO.Path.GetFileName(upload.FileName);
31                        var newpath = Path.Combine(HttpContext.Server.MapPath("~/UploadedFiles"), FileName);
32                        upload.SaveAs(newpath);
33                        PhotoFeed setdata = new PhotoFeed();
34                        setdata.Comment = feed.Comment;
35                        setdata.Imagepath = "/UploadedFiles/"+FileName;
36                        db.FeedModel.Add(setdata);
37                        db.SaveChanges();
38
39                        var options = new PusherOptions();
40                        options.Cluster = "XXX_APP_CLUSTER";
41                        var pusher = new Pusher("XXX_APP_ID", "XXX_APP_KEY", "XXX_APP_SECRET", options);
42                        ITriggerResult result = await pusher.TriggerAsync("a_channel", "an_event", setdata);
43                    }
44                }
45                    return Content("ok");
46            }
47
48        }
49    }

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 with which we will handle the addition and realtime updating of our feed.

Notice that in the GET function, we pass a variable into the view function called me. This variable is a queryable version of our BlogFeed model. This will be passed to the view, which is later looped and rendered.

Observe that the POST method is set to be asynchronous. This is because the Pusher .NET library uses the await operator to wait for the asynchronous response from the data sent to Pusher.
In this function, we first add our new movie to the database, then we trigger an event. Once the event has been emitted, we then return an ok string.

However, please note that the code above would not handle any error if the Image was saved in DB but not posted using Pusher. We might need to use a try and catch statement to handle failures in posting to Pusher.

Creating our view files

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

1@model IEnumerable<Real_time_photo_feed.Models.PhotoFeed>
2
3    @{
4        Layout = null;
5    }
6
7
8       <html>
9    <head>
10        <title>ASP.NET Photo feed</title>
11        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
12        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
13        <script src="//js.pusher.com/4.0/pusher.min.js"></script>
14    </head>
15    <body>
16
17        <div class="container">
18            <form method="post" enctype="multipart/form-data" action="/Home/Index" onsubmit="return feed_it()">
19
20                <div class="form-group">
21                    <label for="usr">Image:</label>
22                    <input type="file" id="upload" name="upload" class="form-control" required>
23                </div>
24                <div class="form-group">
25                    <label for="pwd">comment:</label>
26                    <input type="text" id="Comment" name="Comment" class="form-control" required>
27                </div>
28                <div class="form-group">
29                    <button type="submit" class="btn btn-success">Feed it</button>
30                </div>
31            </form>
32            <div class="row" id="feeds">
33
34
35                @foreach (var item in Model)
36                {
37                <span>
38                    <h2>@item.Comment</h2>
39                    <img src="@item.Imagepath">
40                </span>
41                }
42
43            </div>
44        </div>
45    </body>
46    </html>

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

  • Text input for the comment of the image.
  • File input for selecting the image we want to feed.
  • Button to save the new entry into the database.

Also, note we have included some required libraries such as:

  • Bootstrap CSS
  • jQuery JavaScript library
  • Pusher JavaScript library

Pusher Bindings And jQuery Snippet

Below is our example jQuery snippet used to handle the file upload and Pusher’s realtime updates.

1<script>
2         var files;
3
4            // Add events
5            $(document).ready(function() {
6                $('input[type=file]').on('change', prepareUpload);
7            })
8
9
10            // Grab the files and set them to our variable
11            function prepareUpload(event) {
12                files = event.target.files;
13            }
14
15            function feed_it() {
16                var data = new FormData();
17                $.each(files, function(key, value) {
18                    data.append('upload', value);
19                });
20                data.append('Comment', document.getElementById('Comment').value);
21
22
23                $.post({
24                    url: '/Home/Index',
25                    data: data,
26                    processData: false, // Don't process the files
27                    contentType: false, // Set content type to false as jQuery will tell the server it's a query string request
28                    success: function(data) {
29                        if (data == "ok") {
30                            alert('done');
31                            document.getElementById('Comment').value = '';
32                        }
33                    },
34                    error: function(error) {
35                        alert('an error occured, please try again later')
36                    }
37                });
38                return false;
39            }
40            var pusher = new Pusher("XXX_APP_KEY", {
41
42                cluster: "XXX_APP_CLUSTER"
43            });
44            var my_channel = pusher.subscribe('a_channel');
45            my_channel.bind("an_event", function(doc) {
46
47                var new_message = `<span>
48                            <h2>` + doc.Comment + `</h2>
49                            <img  src="` + doc.Imagepath + `">
50                        </span>`;
51                $('#feeds').prepend(new_message);
52            });
53    </script>

In the code block above, we notice we have done two major activities, which are:

Uploading Image Code
To process the upload of images from the client side to the server, the following steps were followed:

  • We attached an event listener to our file input button that stores our image in a variable called files.
  • We defined a function called feed_it which creates a new FormData, then appends our image and description to the form data. This function then makes an AJAX POST request to our index route.

Subscribing for Feed Additions on Server from other clients
After the image has been sent to the server, a request is sent to Pusher to return an event with the new data we have broadcasted. To listen for this realtime events, we have:

  • Initialized a Pusher object while passing our app key and cluster.
  • Subscribed to our channel called a_channel.
  • Declared a binding to our event called an_event. In the callback function of this binding, we have pre-pended the new data to our list of feeds.

That’s it! Now, once a photo gets uploaded, it also gets broadcast and we can listen using our channel to update the feed in realtime.

Below is an image of what we have built:

Conclusion

In this article, we have covered how to create a realtime photo feed using .NET and Pusher as well as handling file uploads in .NET.
The code base to this tutorial is available in a public Github repository. You can download it for educational purposes.
Have a better way we could have built our application, reservations or comments, let us know in the comments.