Quantcast
Channel: All About ASP.NET and ASP.NET Core 2 Hosting BLOG
Viewing all articles
Browse latest Browse all 427

ASP.NET MVC SignalR Hosting - ASPHostPortal.com :: Create a Real-Time Message Board Making use of ASP.Net MVC 5, SignalR 2, and KnockoutJS

$
0
0

Have you ever questioned how Facebook sends you notifications the instant you receive a new information, friend request, or remark? Does the customer constantly look for new events? Doesn’t that consume a whole lot of memory within the user’s side? What sort of pressure does that place on Facebook’s servers?


 

Due to HTMl five as well as the support of web sockets in modern browsers, this performance can be achieved with out over-taxing the user or even the server. Leveraging a library like SignalR for .NET or Socket.IO for Node.js, most of the very hard perform around working with web sockets is already completed to suit your needs.

On this tutorial, we’re planning to build a real-time concept board, very similar to Facebook. Although this will not be something you should model and drive to manufacturing as-is, it could supply you with the fundamental groundwork for creating your own personal real-time net application.

Let's Start

Start the Project - Initial, begin a new ASP.Net MVC 5 project from Visual Studio 2013. You can title it what ever you want, but I named the undertaking “MessageBoardTutorial”. Make sure you place a checkmark close to "Web API".


Get dependencies with NuGet - Let’s go on and get downloading our required libraries out of the best way. We can get every thing with NuGet, saving us plenty of your time. Within the Remedy Explorer, right-click around the project name and click on “Manage NuGet Packages…” from the context menu.

Initial, you'll want to update all the packages presently set up to their newest variations. Be aware that because MVC 4, all the MVC libraries are bin deployed, so you will need to update them when commencing a fresh undertaking. On the still left, simply click on “Updates->nuget.org” and also you will see each of the offers that must be up-to-date. Just click on on “Update All” and allow NuGet do its thing.


When that's done, near the window. Open up the Package Supervisor Console in Tools > NuGet Package Manager > Package Manager Console. We must set up a few a lot more deals for this tutorial. Run the subsequent commands to set up the deals we need for this tutorial.

PM> Install-Package EntityFramework
PM> Install-Package Microsoft.AspNet.SignalR
PM> Install-Package Moment.js
PM> Install-Package KnockoutJS

NuGet will put in several dependencies that SignalR 2 needs. Additionally, you will get yourself a good tiny readme.txt. Feel free to read this, but we are going to be covering this listed here also.

The Front End

We are going to now truly commence coding. The Layout - First, we've got to go into the structure file and simplify issues somewhat - we don't truly require the responsive menu for this tutorial. Open /Views/Shared/_Layout.cshtml. It ought to now search such as this, assuming you failed to modify the default directories:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <div class="container body-content">
        @RenderBody()
    </div>
 
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>

Edit the Home Controller and Index View - We will only need a single controller and view for this software. Inside the Remedy Explorer, open Controllers > HomeController and take away all Steps except for Index.

Now, open up the Index check out and just delete every thing. If you are a neat freak like me, truly feel totally free to eliminate another views inside the Views > Home.

Edit the View - Under is the see for your application. In case you are new to KnockoutJS, a few of this may appear unfamiliar. I will clarify a lot more when we get to the JavaScript inside the up coming section. Just be aware that wherever the thing is a "data-bind" attribute, the component is joined into a JS worth we are tracking with Knockout.

We're mostly using regular CSS courses from Bootstrap. Bootstrap gives us a quick way to incorporate a responsive grid framework for our view. The still left column includes our "login" type and also the proper the information board itself.

@{
    ViewBag.Title = "Land of Ooobook";
}
 
<div class="jumbotron">
    <h1>Land of Ooobook</h1>
    <p>Candy Kingdom's Social Network</p>
</div>
 
<div class="container">
    <div class="row">
        <div class="col-md-4">
            <form class="pad-bottom" data-bind="visible: !signedIn(), submit: signIn">
                <div class="form-group">
                    <label for="username">Sign In</label>
                    <input class="form-control" type="text" name="username" id="username" placeholder="Enter your userame" />
                </div>
                <button type="submit" class="btn btn-primary">Sign In</button>
                <br />
            </form>
 
            <div data-bind="visible: signedIn">
                <p>You are signed in as <strong data-bind="text: username"></strong></p>
            </div>
        </div>
        <div class="col-md-8">
            <div data-bind="visible: notifications().length > 0, foreach: notifications">
                <div class="summary alert alert-success alert-dismissable">
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
                    <p data-bind="text: $data"></p>
                </div>
            </div>
 
            <div class="new-post pad-bottom" data-bind="visible: signedIn">
                <form data-bind="submit: writePost">
                    <div class="form-group">
                        <label for="message">Write a new post:</label>
                        <textarea class="form-control" name="message" id="message" placeholder="New post"></textarea>
                    </div>
                    <button type="submit" class="btn btn-default">Submit</button>
                </form>
            </div>
 
            <ul class="posts list-unstyled" data-bind="foreach: posts">
                <li>
                    <p>
                        <span data-bind="text: username" class="username"></span><br />
                    </p>
                    <p>
                        <span data-bind="text: message"></span>
                    </p>
 
                    <p class="no-pad-bottom date-posted">Posted <span data-bind="text: moment(date).calendar()" /></p>
 
                    <div class="comments" data-bind="visible: $parent.signedIn() || comments().length > 0">
                        <ul class="list-unstyled" data-bind="foreach: comments, visible: comments().length > 0">
                            <li>
 
                                <p>
                                    <span class="commentor" data-bind="text: username"></span>
                                    <span data-bind="text: message"></span>
                                </p>
                                <p class=" no-pad-bottom date-posted">Posted <span data-bind="text: moment(date).calendar()" /></p>
                            </li>
                        </ul>
 
                        <form class="add-comment" data-bind="visible: $parent.signedIn, submit: addComment">
                            <div class="row">
                                <div class="col-md-9">
                                    <input type="text" class="form-control" name="comment" placeholder="Add a comment" />
                                </div>
                                <div class="col-md-3">
                                    <button class="btn btn-default" type="submit">Add Comment</button>
                                </div>
                            </div>
                        </form>
                    </div>
                </li>
            </ul>
        </div>
    </div>
 
</div>
 
@section scripts {
    <script src="~/Scripts/moment.js"></script>
    <script src="~/Scripts/jquery.signalR-2.0.3.min.js"></script>
    <script src="~/signalr/hubs"></script>
    <script src="~/Scripts/knockout-3.1.0.js"></script>
    <script src="~/Scripts/board.js"></script>
}

Add a bit of CSS - We have a few custom CSS classes on top of what Bootstrap provides. Currently, /Content/Site.css is in your style bundle. Open it and replace everything above the validation helper classes with this:

.pad-bottom {
    margin-bottom: 10px;
}
 
.no-pad-bottom {
    margin-bottom: 0;
}
 
.new-post {
    background-color: #c9e0fc;
    padding: 15px 10px;
}
 
.username {
    font-weight: bold;
    color: #ff6a00;
}
 
.commentor {
    font-weight: bold;
    color: #004bff;
}
 
.posts li .date-posted {
    font-size: 12px;
    font-style: italic;
}
 
.posts li {
    border-bottom: 1px solid #CCC;
    padding: 15px 0;
}
 
.posts li:first-child {
    border-top: 1px solid #CCC;
}
 
.posts .comments {
    background-color: #efefef;
    margin-top: 10px;
    padding: 10px;
    padding-left: 30px;
}
 
.posts .comments li {
    padding-top: 10px;
    border: 0;
}
 
form.add-comment  {
    margin-top: 10px;
    margin-bottom: 10px;
}

The JavaScript

Create a script - In the Scripts directory, make a new script called board.js. This script will contain the client-side logic for the application.

Edit the script - Let's begin with our script.

A message board is made of posts and comments to those posts. Let's define those objects.

var post = function (id, message, username, date) {
    this.id = id;
    this.message = message;
    this.username = username;
    this.date = date;
    this.comments = ko.observableArray([]);
 
    this.addComment = function (context) {
        var comment = $('input[name="comment"]', context).val();
        if (comment.length > 0) {
            $.connection.boardHub.server.addComment(this.id, comment, vm.username())
            .done(function () {
                $('input[name="comment"]', context).val('');
            });
        }
    };
}
 
var comment = function (id, message, username, date) {
    this.id = id;
    this.message = message;
    this.username = username;
    this.date = date;
}

A submit has an observable variety of remarks. This enables it in order that when new remarks come in, it's immediately displayed within the view.

We've outlined our submit and remark objects. Now let's define our view design that is the glue from the application.

var vm = {
    posts: ko.observableArray([]),
    notifications: ko.observableArray([]),
    username: ko.observable(),
    signedIn: ko.observable(false),
    signIn: function () {
        vm.username($('#username').val());
        vm.signedIn(true);
    },
    writePost: function () {
        $.connection.boardHub.server.writePost(vm.username(), $('#message').val()).done(function () {
            $('#message').val('');
        });
    },
}
 
ko.applyBindings(vm);

We have an observable array of posts (which have an observable array of remarks). We even have an observable assortment of notifications, which inform customers of latest posts or comments on their posts. Our view model tracks the username and if a user is signed in. Finally, a view design can include techniques.

Next, we've a way for loading posts, which is known as when the web page hundreds and the user connects into a SignalR hub. The posts are loaded from the Net API controller which we are going to make later. Each and every submit is additional to the observable assortment of posts. Each and every from the post's feedback are added to the comments observable variety of the submit. This really is a really verbose method of performing this, but I wanted to make it distinct what was happening right here.

function loadPosts() {
$.get('/api/posts', function (data) {
    var postsArray = [];
    $.each(data, function (i, p) {
        var newPost = new post(p.Id, p.Message, p.Username, p.DatePosted);
        $.each(p.Comments, function (j, c) {
            var newComment = new comment(c.Id, c.Message, c.Username, c.DatePosted);
            newPost.comments.push(newComment);
        });
 
        vm.posts.push(newPost);
        });
    });
}

The rest of the script has to do with SignalR connections and methods. We'll get back to that later. Below is the entire board.js script.

var post = function (id, message, username, date) {
    this.id = id;
    this.message = message;
    this.username = username;
    this.date = date;
    this.comments = ko.observableArray([]);
 
    this.addComment = function (context) {
        var comment = $('input[name="comment"]', context).val();
        if (comment.length > 0) {
            $.connection.boardHub.server.addComment(this.id, comment, vm.username())
            .done(function () {
                $('input[name="comment"]', context).val('');
            });
        }
    };
}
 
var comment = function (id, message, username, date) {
    this.id = id;
    this.message = message;
    this.username = username;
    this.date = date;
}
 
var vm = {
    posts: ko.observableArray([]),
    notifications: ko.observableArray([]),
    username: ko.observable(),
    signedIn: ko.observable(false),
    signIn: function () {
        vm.username($('#username').val());
        vm.signedIn(true);
    },
    writePost: function () {
        $.connection.boardHub.server.writePost(vm.username(), $('#message').val()).done(function () {
            $('#message').val('');
        });
    },
}
 
ko.applyBindings(vm);
 
function loadPosts() {
    $.get('/api/posts', function (data) {
        var postsArray = [];
        $.each(data, function (i, p) {
            var newPost = new post(p.Id, p.Message, p.Username, p.DatePosted);
            $.each(p.Comments, function (j, c) {
                var newComment = new comment(c.Id, c.Message, c.Username, c.DatePosted);
                newPost.comments.push(newComment);
            });
 
            vm.posts.push(newPost);
        });
    });
}
 
$(function () {
    var hub = $.connection.boardHub;
    $.connection.hub.start().done(function () {
        loadPosts(); // Load posts when connected to hub
    });
 
    // Hub calls this after a new post has been added
    hub.client.receivedNewPost = function (id, username, message, date) {
        var newPost = new post(id, message, username, date);
        vm.posts.unshift(newPost);
 
        // If another user added a new post, add it to the activity summary
        if (username !== vm.username()) {
            vm.notifications.unshift(username + ' has added a new post.');
        }
    };
 
    // Hub calls this after a new comment has been added
    hub.client.receivedNewComment = function (parentPostId, commentId, message, username, date) {
        // Find the post object in the observable array of posts
        var postFilter = $.grep(vm.posts(), function (p) {
            return p.id === parentPostId;
        });
        var thisPost = postFilter[0]; //$.grep returns an array, we just want the first object
 
        var thisComment = new comment(commentId, message, username, date);
        thisPost.comments.push(thisComment);
 
        if (thisPost.username === vm.username() && thisComment.username !== vm.username()) {
            vm.notifications.unshift(username + ' has commented on your post.');
        }
    };
});

The Data Layer - We will be making use of Entity Framework as well as the Code First sample for our straightforward info layer.

Inside the Models directory, develop a brand new C# class referred to as MessageBoard.cs. Our data designs are little, and in our scenario we'll just place them in a single file alongside with all the data context.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Linq;
using System.Web;
 
namespace MessageBoardTutorial.Models
{
    public class MessageBoardContext : DbContext
    {
        public DbSet<Post> Posts { get; set; }
        public DbSet<Comment> Comments { get; set; }
    }
 
    public class Post
    {
        [Key]
        public int Id { get; set; }
        public string Message { get; set; }
        public string Username { get; set; }
        public DateTime DatePosted { get; set; }
        public virtual ICollection<Comment> Comments { get; set; }
    }
 
    public class Comment
    {
        [Key]
        public int Id { get; set; }
        public string Message { get; set; }
        public string Username { get; set; }
        public DateTime DatePosted { get; set; }
        public virtual Post ParentPost { get; set; }
    }
}

Although the actual database doesn't yet exist, it will likely be produced if the application deems it essential. Should you maintain your internet.config unchanged, it's going to develop it within your LocalDB instance of SQL Server Specific.

The API

The script requirements a technique to get in touch with to get the posts to the concept board. Whilst we could just tack a technique on to our HomeController that would try this, we can equally as effortlessly create an ASP.Web Internet API controller which will fetch and immediately convert the posts into a JSON format.

Create the API Controller - In the Solution Explorer, right click on the Controllers directory and click on Add > Web API Controller Class (v2) in the context menu. Add a new Web API controller and call it PostsController.cs

Write the API
- Replace the code with the below. Our API will simply return a JSON or XML-serialized array of all of the posts and comments on the message board when a client calls http://[yoursite]/api/posts.

using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;
    using MessageBoardTutorial.Models;
 
    namespace MessageBoardTutorial.Controllers
    {
        public class PostsController : ApiController
        {
            private MessageBoardContext _ctx;
            public PostsController()
            {
                this._ctx = new MessageBoardContext();
            }
 
            // GET api/<controller>
            public IEnumerable<post> Get()
            {
                return this._ctx.Posts.OrderByDescending(x => x.DatePosted).ToList();
            }
        }
    }
</post></controller>

Handle circular references - If you look at our data models, a Post contains a virtual collection of comments, and a Comment contains a virtual Post that refers to the comment's parent. This circular reference will freak out Web API's serialization method. To solve this, just add the following line of code to the Register method in the WebApiConfig class located at App_Start/WeApiConfig.cs
   
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling =
    Newtonsoft.Json.ReferenceLoopHandling.Ignore;

Circular references will now be ignored.

Implementing the SignalR Hub

SignalR provides a relationship between clientele and also the server. Clients connect to a SignalR Hub. The Hub can then contact client-side occasions on a single, some, or all related clients. The Hub tracks which consumers are linked to it. This can be the center of real-time net programs. Within our case, a client generates a fresh submit or comment, which phone calls a method around the Hub. The Hub then calls a method on all of the message board's connected users that inserts the brand new submit or remark.

In a nutshell, the awesomeness of SignalR is that it may call JavaScript capabilities on any browser linked to it. The outdated assemble is clientele could only get in touch with server-side features. This type of functionality could only be achieved via long-polling (a customer working an infinite loop that calls a server method).


Newer browsers connect to hubs via HTML 5 web sockets. For older browsers, there are long-polling and "forever frame" fallbacks.

Map the hubs - In order for SignalR to begin and for the web application to map the connection hubs, we must create an OWIN startup class. Lucky for us, this just involves a couple of clicks and a line of code. Right-click on the solution and add a new item. Find the OWIN Startup Class template. Name this file "Startup.cs". Now, you just have to add one line of code in the Configuration method to map the hubs.

using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
 
[assembly: OwinStartup(typeof(MessageBoardTutorial.Startup))]
 
namespace MessageBoardTutorial
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

Create the Hub - Make a new directory in the root of your project called Hubs. Now, right-click on this new directory and click on Add > New Item... in the context menu. Create a new SignalR Hub Class called BoardHub.cs.


Replace the code in the file with the below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using MessageBoardTutorial.Models;
 
namespace MessageBoardTutorial.Hubs
{
    public class BoardHub : Hub
    {
        public void WritePost(string username, string message)
        {
            var ctx = new MessageBoardContext();
            var post = new Post { Message = message, Username = username, DatePosted = DateTime.Now };
            ctx.Posts.Add(post);
            ctx.SaveChanges();
 
            Clients.All.receivedNewPost(post.Id, post.Username, post.Message, post.DatePosted);
        }
 
        public void AddComment(int postId, string comment, string username)
        {
            var ctx = new MessageBoardContext();
            var post = ctx.Posts.FirstOrDefault(p => p.Id == postId);
 
            if (post != null)
            {
                var newComment = new Comment { ParentPost = post, Message = comment, Username = username, DatePosted = DateTime.Now };
                ctx.Comments.Add(newComment);
                ctx.SaveChanges();
 
                Clients.All.receivedNewComment(newComment.ParentPost.Id, newComment.Id, newComment.Message, newComment.Username, newComment.DatePosted);
            }
        }
    }

}

The Hub contains a method for writing a new post and a method for writing a new comment. Both methods are called from the client and ultimately call client-side methods for connected users.

For example, the writePost client-side method is called when a user writes a new post:

writePost: function () {
    $.connection.boardHub.server.writePost(vm.username(), $('#message').val()).done(function () {
        $('#message').val('');
    });
},

This method calls the WritePost method on the Hub. After the new post is added to the database, the Hub then calls the receivedNewPost method on all connected clients.

Clients.All.receivedNewPost(post.Id, post.Username, post.Message, post.DatePosted);

From BoardHub.cs

hub.client.receivedNewPost = function (id, username, message, date) {
    var newPost = new post(id, message, username, date);
    vm.posts.unshift(newPost);
 
    // If another user added a new post, add it to the activity summary
    if (username !== vm.username()) {
        vm.notifications.unshift(username + ' has added a new post.');
    }
};

From board.js

The Hub sends info about the new submit to this client method, which then helps make a brand new Publish JavaScript object and adds it to the starting from the view model's posts observable array. This new post is instantaneously displayed for all related customers since Knockout is observing this array. A brand new notification can also be produced for all customers other than the creator of the new publish.

Adding a remark follows the same pattern, although the client callback method differs marginally:

hub.client.receivedNewComment = function (parentPostId, commentId, message, username, date) {
    // Find the post object in the observable array of posts
    var postFilter = $.grep(vm.posts(), function (p) {
        return p.id === parentPostId;
    });
    var thisPost = postFilter[0]; //$.grep returns an array, we just want the first object
 
    var thisComment = new comment(commentId, message, username, date);
    thisPost.comments.push(thisComment);
 
    if (thisPost.username === vm.username() && thisComment.username !== vm.username()) {
        vm.notifications.unshift(username + ' has commented on your post.');
    }
};

The ID of the parent post of the comment is sent to the client, which then adds the new comment to the comments observable array of the post

Finally, let us run this point

Now you can construct and operate the project! Ooo is in dire need of a social community.

Go on and open up two browser windows and "log in" as a various person on every. Recognize how once you include a submit in a single window, it instantaneously is additional towards the other window in addition to a notification. Now, the Ice King can annoy princesses on-line too! Effectively, possibly that is not all that good.



Viewing all articles
Browse latest Browse all 427

Trending Articles