Real Time Web with SignalR and ASP.NET MVC

This afternoon I was thinking about an app where users will be notified in real time. Some suggestions were raised but at the moment I think only Node.JS and SignalR are the most viable candidate for this scenario. For this post, since I am going to use ASP.NET MVC, I will use SignalR which is native to .NET.

Okay, for those who are not familiar yet with SignalR. According to its site, it is a new library for ASP.NET developers that makes it incredibly simple to add real-time web functionality to your applications. For me, basically, it is the most convenient way in notifying the client that something happened from the server in real time. No need for work around or any sort.

Now, time for the development.

1. First we need to get a reference of SignalR from Nuget. You can also get it from Github. Or download it as zip and add it as reference. But if you are new, I would suggest using nuget.

– Install-Package Microsoft.AspNet.SignalR

SignalR_Nuget

2. Once you have it installed. Let’s create  a Hub. A SignalR Hub enables you to make remote procedure calls (RPCs) from a server to connected clients and from clients to the server.

– First we need to add a folder to our web project called Hubs.

– Then create a class called CustomerHub. Below is how it should look like. I intentionally made it empty since it just acts like a fake hub. All the crud operation happens in the actionresult and you will find out how i called the client method from the actionresult once you finished reading this post.

using Microsoft.AspNet.SignalR;

namespace CustomerSite.Hubs
{
    public class CustomerHub : Hub
    {
        
    }
}

– Then another important part is to add the startup class which does all the mapping.

using Owin;
using Microsoft.Owin;
[assembly: OwinStartup(typeof(CustomerSite.Hubs.Startup))]
namespace CustomerSite.Hubs
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // Any connection or hub wire up and configuration should go here
            app.MapSignalR();
        }
    }
}

3. Now lets get back to our view.

– Add a reference to SignalRJS.

<script src=”~/Scripts/jquery.signalR-2.1.2.js”></script>

– Set a reference to the available hubs that we created, in this case the CustomerHub that we created as shown below.

<script src=”/signalr/hubs”></script>

– This is how my Layout looks like.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Realtime Web with SignalR</title>
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
    <link href="~/Content/bootstrap.min.css" rel="stylesheet" type="text/css" />
    <script src="~/Scripts/modernizr-2.6.2.js"></script>
    @RenderSection("styles", false)
</head>
<body>
    <div class="container body-content">
        @RenderBody()
    </div>

    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <script src="~/Scripts/bootstrap.min.js"></script>
    <script src="~/Scripts/knockout.js"></script>
    <script src="~/Scripts/jquery.signalR-2.1.2.js"></script>
    <script src="/signalr/hubs"></script>
    @RenderSection("scripts", false)
</body>
</html>

4. The client side SignalR registration with knockout.
-> self.hub = $.connection.customerHub; //tells the hub object to point to customerhub on the server.
-> self.hub.client.getCustomers = function () { //is a method that will be called from the server.
self.loadCustomers();
}
-> $.connection.hub.start(); // the client side listener

function AppViewModel() {
    var self = this;

    self.hub = $.connection.customerHub;

    self.hub.client.getCustomers = function () {
        self.loadCustomers();
    }

    $.connection.hub.start();
    
    ... //Code removed here just for this post.

    //A function for loading the customer view via ajax.
    self.loadCustomers = function () {
        $.ajax({
            url: '/Home/CustomerList',
            success: function (data) {
                $("#customerlist").html(data);
            }
        });
    };

    //The function for updating and adding a customer.
    self.submitForm = function () {
        var url = '/Home/SaveCustomer';
        if (self.isEdit()) {
            url = '/Home/UpdateCustomer';
        } 
        $.ajax({
            url: url,
            data: {
                firstName: self.firstName(),
                lastName: self.lastName(),
                address: self.address(),
                phone: self.phone(),
                creditLimit: self.creditLimit(),
                customerSince: self.customerSince(),
                id: self.customerId()
            },
            success: function (data) {
                if (String(data.saved) == 'true') {
                    self.formVisible(false);
                    //self.loadCustomers();
                }
            }
        });
    };
}
ko.applyBindings(new AppViewModel());

5. For the server side to client interaction via ActionResult.

public ActionResult SaveCustomer([Bind(Exclude = "Id")]CustomerViewModel customer)
        {
            provider = new CustomerProvider();
            bool saved = provider.AddCustomer(customer);
            IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext();
            hubContext.Clients.All.getCustomers();
            return Json(new { saved = saved }, JsonRequestBehavior.AllowGet);
        }

The most important part here are these 2 lines. Basically, what this does is it gets the CustomerHub context and call the getCustomers method that we declared on the client.

IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<CustomerHub>();
hubContext.Clients.All.getCustomers();

This is the screen capture of the app running side by side updating each other. We’re done.

AppRunning

By the way, I will post a followup blog on DependencyInjection with SignalR.

Advertisements

CRUD with KnockoutJS, ASP.NET MVC and NHibernate

A friend of mine asked me to teach him a simple CRUD with KnockoutJS using NHibernate because he is already used to EntityFramework. Well, I told him to buy me a grande coffee first at starbucks, and he did. So in order for him and for the others to get to see it in action here is how its done.

First of all, for those of you who doesn’t know what is NHibernate, it is an Or Mapper like EntityFramework. Here is a comparison just in case you might want to know the difference between the 2.

Best comparison so far.

http://www.devbridge.com/articles/entity-framework-6-vs-nhibernate-4/

Now let’s work.

1. First, let’s download the latest release of NHibernate from Nuget.

NHibernate

3. Now since we want xml less mapping, lets get Fluentnhibernate from nuget. During this post, the latest stable version is 1.4.0.

– From your Package Manager Console. type the following “Install-Package FluentNHibernate -Version 1.4.0”

4. After successfully installing FluentNHibernate, your datastore library should now look like below.

FluentNhibernate

5. Now let’s set up a helper for NHibernate which will allow us to connect to the database.
– Okay, basically what the below does is create a SessionFactory.
– Connect to a local data soure from my App_Data directory.

public class CustomerContext
    {
        private static ISessionFactory sessionFactory;
        private static ISession session;
        private static ISessionFactory SessionFactory {
            get {
                if (sessionFactory == null)
                    SetSessionFactory();

                return sessionFactory;
            }
        }
        private static void SetSessionFactory() {
            sessionFactory = Fluently.Configure().Database(MsSqlConfiguration.MsSql2012.ConnectionString(@"data source=(LocalDB)\v11.0;attachdbfilename=|DataDirectory|Customers.mdf;integrated security=true;"))
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf()<Customer>())
                .ExposeConfiguration(c => new SchemaExport(c))
                .BuildSessionFactory();
        }

        public static ISession OpenSession() {
            return SessionFactory.OpenSession();
        }
    }

6. Let’s create a mapping to our Customers table in the database to our Customer entity.

public class CustomerMap : ClassMap<Customer>
    {
        public CustomerMap()
        {
            Id(c => c.Id);
            Map(c => c.FirstName);
            Map(c => c.LastName);
            Map(c => c.Address);
            Map(c => c.Phone);
            Map(c => c.CreditLimit);
            Map(c => c.CustomerSince);
            Table("dbo.Customers");
        }
    }

7.  Now, below is self explanatory, I created the CRUD methods which inherited from an Interface.

public class CustomerViewModel : ICustomerProvider
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
        public string Phone { get; set; }
        public decimal CreditLimit { get; set; }
        public DateTime CustomerSince { get; set; }

        #region NHibernate
        public List<CustomerViewModel> GetCustomers()
        {
            using (var context = CustomerContext.OpenSession())
            {
                return context.Query<Customer>().Select(c => new CustomerViewModel()
                {
                    Id = c.Id,
                    FirstName = c.FirstName,
                    LastName = c.LastName,
                    Address = c.Address,
                    Phone = c.Phone,
                    CreditLimit = c.CreditLimit,
                    CustomerSince = c.CustomerSince
                }).ToList();
            }
        }

        public bool AddCustomer(CustomerViewModel customer)
        {
            bool saved = false;
            try
            {
                using (var context = CustomerContext.OpenSession())
                {
                    using (var transaction = context.BeginTransaction())
                    {
                        context.Save(new Customer()
                        {
                            FirstName = customer.FirstName,
                            LastName = customer.LastName,
                            Address = customer.Address,
                            Phone = customer.Phone,
                            CreditLimit = customer.CreditLimit,
                            CustomerSince = customer.CustomerSince
                        });
                        transaction.Commit();
                    }
                }
                saved = true;
            }
            catch (Exception ex)
            {
                //Log here
                throw ex;   
            }
            return saved;
        }
        public CustomerViewModel GetCustomer(int customerId)
        {
            using (var context = CustomerContext.OpenSession())
            {
                return context.Query<Customer>().Where(c => c.Id == customerId).Select(c => new CustomerViewModel()
                {
                    Id = c.Id,
                    FirstName = c.FirstName,
                    LastName = c.LastName,
                    Address = c.Address,
                    Phone = c.Phone,
                    CreditLimit = c.CreditLimit,
                    CustomerSince = c.CustomerSince
                }).FirstOrDefault();
            }
        }

        public bool UpdateCustomer(CustomerViewModel customer)
        {
            bool saved = false;
            try
            {
                using (var context = CustomerContext.OpenSession())
                {
                    using (var transaction = context.BeginTransaction())
                    {
                        var existingCustomer = context.Query<Customer>().Where(c => c.Id == customer.Id).FirstOrDefault();
                        existingCustomer.FirstName = customer.FirstName;
                        existingCustomer.LastName = customer.LastName;
                        existingCustomer.Address = customer.Address;
                        existingCustomer.Phone = customer.Phone;
                        existingCustomer.CreditLimit = customer.CreditLimit;
                        existingCustomer.CustomerSince = customer.CustomerSince;
                        context.SaveOrUpdate(existingCustomer);
                        transaction.Commit();
                    }
                }
                saved = true;
            }
            catch (Exception ex)
            {
                //Log here
                throw ex;
            }
            return saved;
        }

        public void DeleteCustomer(int customerId)
        {
            using (var context = CustomerContext.OpenSession())
            {
                using (var transaction = context.BeginTransaction())
                {
                    var existingCustomer = context.Query<Customer>().Where(c => c.Id == customerId).FirstOrDefault();
                    context.Delete(existingCustomer);
                    transaction.Commit();
                }
            }
        }
        #endregion

    }

8. And in our controller. Below are the controller functions.

public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult DeleteCustomer(int customerId) {
            ICustomerProvider provider = new CustomerViewModel();
            provider.DeleteCustomer(customerId);
            return PartialView("CustomerList", provider.GetCustomers());
        }

        public ActionResult CustomerList() {
            ICustomerProvider provider = new CustomerViewModel();
            return PartialView(provider.GetCustomers());
        }

        public ActionResult SaveCustomer([Bind(Exclude="Id")]CustomerViewModel customer) {
            ICustomerProvider provider = new CustomerViewModel();
            bool saved = provider.AddCustomer(customer);
            return Json(new { saved = saved }, JsonRequestBehavior.AllowGet);
        }

        public ActionResult UpdateCustomer([Bind]CustomerViewModel customer)
        {
            ICustomerProvider provider = new CustomerViewModel();
            bool saved = provider.UpdateCustomer(customer);
            return Json(new { saved = saved }, JsonRequestBehavior.AllowGet);
        }
        public JsonResult GetCustomer(int customerId)
        {
            ICustomerProvider provider = new CustomerViewModel();
            return Json(new { customer = provider.GetCustomer(customerId) }, JsonRequestBehavior.AllowGet);
        }
}

 

9. As you can see below, I have a very simple form. Don’t worry the source code is in github for you to look the entire running source.

– What I would like to highlight here is the data-bind attribute. The values and click events are bound to our js viewmodel.

@{
    Layout = "/Views/Shared/_Layout.cshtml";
    ViewBag.Title = "Customers";
}
<div class="row container">
    <h2>Customer List</h2>
    <br />
</div>

<div data-bind="visible: formVisible" class="container col-md-12">
    <div class="row margin-bottom-10">
        @ViewBag.Message
    </div>
    <div class="row">
        <div class="column">
            First Name
        </div>
        <div class="column">
            <input type="text" id="firstName" name="firstName" class="form-control" data-bind="value: firstName" />
        </div>
    </div>
    <div class="row">
        <div class="column">
            Last Name
        </div>
        <div class="column">
            <input type="text" id="lastName" name="lastName" class="form-control" data-bind="value: lastName" />
        </div>
    </div>
    <div class="row">
        <div class="column">
            Address
        </div>
        <div class="column">
            <input type="text" id="address" name="address" class="form-control" data-bind="value: address" />
        </div>
    </div>
    <div class="row">
        <div class="column">
            Phone
        </div>
        <div class="column">
            <input type="text" id="phone" name="phone" class="form-control" data-bind="value: phone" />
        </div>
    </div>
    <div class="row">
        <div class="column">
            Credit Limit
        </div>
        <div class="column">
            <input type="text" id="creditLimit" name="creditLimit" class="form-control" data-bind="value: creditLimit" />
        </div>
    </div>
    <div class="row">
        <div class="column">
            Customer Since
        </div>
        <div class="column">
            <input type="text" id="customerSince" name="customerSince" class="form-control" data-bind="value: customerSince" placeholder="yyyy-MM-dd" />
        </div>
    </div>
    <div class="row">
        <div class="column">
            &nbsp;
        </div>
        <div class="column">
            <input type="button" class="btn-info" value="Submit" data-bind="click: function() { submitForm() }" />
            <input type="button" class="btn-danger" value="Cancel" data-bind="click: hideForm" />
        </div>
    </div>
</div>
<br />
<div class="container col-md-12 margin-bottom-10 align-content-right">
    <br />
    <input type="button" class="btn-info" value="Add New Customer" data-bind="click: $root.showForm" /><br /><br />
</div>
<div id="customerlist" class="container col-md-12">
    @Html.Action("CustomerList", "Home")
</div>
@section scripts {
    <script src="~/Scripts/PageScripts/customers.js"></script>
}

 

10. And finally, our knockout view model.

function AppViewModel() {
    var self = this;
    //Since i hide and show the form to prevent turnaround from the server I created an observable property which is bound to the visibility of the form element.
    self.formVisible = ko.observable(false);

    //Here I instantiated all the properties bound to the input elements.
    self.firstName = ko.observable('');
    self.lastName = ko.observable('');
    self.address = ko.observable('');
    self.phone = ko.observable('');
    self.creditLimit = ko.observable('');
    self.customerSince = ko.observable('');
    self.customerId = ko.observable('');

    //Another property to toggle between add new and edit functionality.
    self.isEdit = ko.observable(false);

    //A function for showing the form which is called from the UI.
    self.showForm = function () {
        self.formVisible(true);
        self.resetForm();
        self.isEdit(false);
    };

    //A function for hiding the form which is called from the UI.
    self.hideForm = function () {
        self.formVisible(false);
    };

    //A function for resetting the values of the form.
    self.resetForm = function () {
        self.firstName('');
        self.lastName('');
        self.address('');
        self.phone('');
        self.creditLimit('');
        self.customerSince('');
    };

    //A function for getting the correct date.
    self.getCorrectDate = function (rawDate) {
        var dateStringValue = rawDate;
        var value = new Date (parseInt(dateStringValue.replace(/(^.*\()|([+-].*$)/g, '')));
        var correctDate = value.getFullYear() + '-' + value.getMonth() + 1 + "-" + value.getDate();
        return correctDate;
    }

    //A function which updates a customer
    self.editCustomer = function (id) {
        $.ajax({
            url: '/Home/GetCustomer',
            data: {
                customerId: id
            },
            success: function (data) {
                self.formVisible(true);
                self.isEdit(true);
                self.firstName(data.customer.FirstName);
                self.lastName(data.customer.LastName);
                self.address(data.customer.Address);
                self.phone(data.customer.Phone);
                self.creditLimit(data.customer.CreditLimit);
                self.customerSince(self.getCorrectDate(data.customer.CustomerSince));
                self.customerId(data.customer.Id);
            }
        });
    };

    //A function for deleting a customer
    self.deleteCustomer = function (id) {
        $.ajax({
            url: '/Home/DeleteCustomer',
            data: {
                customerId : id
            },
            success: function (data) {
                $("#customerlist").html(data);
            }
        });
    }

    //A function for loading the customer view via ajax.
    self.loadCustomers = function () {
        $.ajax({
            url: '/Home/CustomerList',
            success: function (data) {
                $("#customerlist").html(data);
            }
        });
    };

    //The function for updating and adding a customer.
    self.submitForm = function () {
        var url = '/Home/SaveCustomer';
        if (self.isEdit()) {
            url = '/Home/UpdateCustomer';
        } 
        $.ajax({
            url: url,
            data: {
                firstName: self.firstName(),
                lastName: self.lastName(),
                address: self.address(),
                phone: self.phone(),
                creditLimit: self.creditLimit(),
                customerSince: self.customerSince(),
                id: self.customerId()
            },
            success: function (data) {
                if (String(data.saved) == 'true') {
                    self.formVisible(false);
                    self.loadCustomers();
                }
            }
        });
    };
}

//Now here is the important part. Tell knockout to apply the necessary bindings between our dom elements and the above model.
ko.applyBindings(new AppViewModel());

 

And we are done. You now have a simple CRUD using NHibernate, FluentNHibernate, Knockout, and Bootstrap. The entire source is in github.

Get the entire source here https://github.com/francorobles/Blog/tree/master/CustomerSite.