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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: