Build a photo feed using ASP.NET
A basic understanding of C# and JavaScript is needed to follow this tutorial.
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.
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.N``ET MVC Web Applicat``ion
.
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:
Install-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:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace Real_time_photo_feed.Models
{
public class PhotoFeed
{
[Key]
public int Id { get; set; }
[Required]
public string Comment { get; set; }
public string Imagepath { get; set; }
}
}
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:
public 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:
using PusherServer;
using Real_time_photo_feed.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
namespace Real_time_photo_feed.Controllers
{
public class HomeController : Controller
{
ApplicationDbContext db = new ApplicationDbContext();
public ActionResult Index()
{
var me = db.FeedModel.AsQueryable();
return View(me);
}
[HttpPost]
public async Task<ActionResult> Index(PhotoFeed feed, HttpPostedFileBase upload)
{
if (ModelState.IsValid)
{
if (upload != null && upload.ContentLength > 0)
{
var FileName = System.IO.Path.GetFileName(upload.FileName);
var newpath = Path.Combine(HttpContext.Server.MapPath("~/UploadedFiles"), FileName);
upload.SaveAs(newpath);
PhotoFeed setdata = new PhotoFeed();
setdata.Comment = feed.Comment;
setdata.Imagepath = "/UploadedFiles/"+FileName;
db.FeedModel.Add(setdata);
db.SaveChanges();
var options = new PusherOptions();
options.Cluster = "XXX_APP_CLUSTER";
var pusher = new Pusher("XXX_APP_ID", "XXX_APP_KEY", "XXX_APP_SECRET", options);
ITriggerResult result = await pusher.TriggerAsync("a_channel", "an_event", setdata);
}
}
return Content("ok");
}
}
}
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 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:
@model IEnumerable<Real_time_photo_feed.Models.PhotoFeed>
@{
Layout = null;
}
<html>
<head>
<title>ASP.NET Photo feed</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<script src="//js.pusher.com/4.0/pusher.min.js"></script>
</head>
<body>
<div class="container">
<form method="post" enctype="multipart/form-data" action="/Home/Index" onsubmit="return feed_it()">
<div class="form-group">
<label for="usr">Image:</label>
<input type="file" id="upload" name="upload" class="form-control" required>
</div>
<div class="form-group">
<label for="pwd">comment:</label>
<input type="text" id="Comment" name="Comment" class="form-control" required>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success">Feed it</button>
</div>
</form>
<div class="row" id="feeds">
@foreach (var item in Model)
{
<span>
<h2>@item.Comment</h2>
<img src="@item.Imagepath">
</span>
}
</div>
</div>
</body>
</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.
<script>
var files;
// Add events
$(document).ready(function() {
$('input[type=file]').on('change', prepareUpload);
})
// Grab the files and set them to our variable
function prepareUpload(event) {
files = event.target.files;
}
function feed_it() {
var data = new FormData();
$.each(files, function(key, value) {
data.append('upload', value);
});
data.append('Comment', document.getElementById('Comment').value);
$.post({
url: '/Home/Index',
data: data,
processData: false, // Don't process the files
contentType: false, // Set content type to false as jQuery will tell the server it's a query string request
success: function(data) {
if (data == "ok") {
alert('done');
document.getElementById('Comment').value = '';
}
},
error: function(error) {
alert('an error occured, please try again later')
}
});
return false;
}
var pusher = new Pusher("XXX_APP_KEY", {
cluster: "XXX_APP_CLUSTER"
});
var my_channel = pusher.subscribe('a_channel');
my_channel.bind("an_event", function(doc) {
var new_message = `<span>
<h2>` + doc.Comment + `</h2>
<img src="` + doc.Imagepath + `">
</span>`;
$('#feeds').prepend(new_message);
});
</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 into a variable called
files
. - We defined a function called
feed_it
which creates a newFormData
, then appends our image and description to the form data. This function then makes anAJAX POST
request to ourindex
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 havepre-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.
28 September 2017
by Samuel Ogundipe