Sunday, April 12, 2015

Create edit in place table using AngularJS and ASP.NET MVC

Create edit in place table using AngularJS and ASP.NET MVC

How to use AngularJS in ASP.MVC project.

Hello everybody, 

Today I will show you how to use AngularJS in your projects. I like AngularJS very much, so I will try to explain a practical side of the using the framework, if you are not familiar with a theory you can find it on AngularJS web site https://angularjs.org/ . So let’s begin.



Demo project on GitHub:


The Business logic of the project is simple. I want to show ordinary bank rates, for example “Opening an account” in a table, where end user can just edit all cell in a place.
We will create a simple project for this demo lesson. Open Visual Studio and create a new asp.net mvc project with the following structure as I did it.

<YourSolution>
<YourProject>
<YourProject>.Domain
<YourProject>.Tests


I prefer to use Empty template to create a project, it allows me to control the project references and structure. As a following step I need to install and AngularJS and jQuery frameworks into my projects. I use NuGet commands to do this:
            Install-Package AngularJS.Core
Install-Package AngularJS.Route
Install-Package jQuery
Install-Package EntityFramework
Install-Package Angular.UI.Bootstrap
Install-Package modernizr
Install-Package bootstrap
Install-Package Microsoft.AspNet.Web.Optimization

After installing all necessary packages my Scripts folder looks like this


In folder Content create a new folder with name AngularJS. There we will put files for our angularjs Controller.js and Service.js
Create these files Controller.jsService.js and Module.js


Open Module.js file and add the following  code snippet:

var myApp = angular.module('BankRatesApp', []);

Open file _Layout.cshtml and add ng-app directive in html tag. 

<html ng-app='BankRatesApp'>

 Add the references for angular.js, angular-route.js, Module.js, Controller.js, Service.js as well.

Listing 1-1 _Layout.cshtml
<!DOCTYPE html>

<html ng-app='BankRatesApp'>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title</title>
    <script src="~/Scripts/angular.js"></script>
    <script src="~/Scripts/angular.min.js"></script>
    <script src="~/Scripts/angular-route.js"></script>
    <script src="~/Content/Angular/Module.js"></script>
    <script src="~/Content/Angular/Service.js"></script>
    <script src="~/Content/Angular/Controller.js"></script>
     @Styles.Render("~/Content/css")
</head>
<body>
    <div>
        @RenderBody()
    </div>
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>

·         Add „Class Library” project to your solution and name it <YourSolution>.Domain. This project will serve as Domain Layer which will store our DbContext and Models
·         Install EntityFramework into the project
·         Add reference System.Web.Mvc
·         Create folders Conrete and Entities
·         Add Rate.cs class file into the folder Entities
·         Modify Rate.cs file according below Listing 1-2

Listing 1-2 Rate.cs file
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace <YourSolution>.Domain.Entities
{
    public class Rate
    {
        public int RateID { get; set; }
        public string Name { get; set; }
        public string Value { get; set; }
    }
}

·         Add EFDbContext.cs file into the folder Conrete
·         Save Listing 1-3 into your EFDbContext.cs file

Listing 1-3 EFDbContext.cs

using <YourSolution>.Domain.Entities;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace <YourSolution>.Domain.Concrete
{
    public class EFDbContext : DbContext
    {
        public EFDbContext()
            : base("EFDbContext")
        {
        }
        public DbSet<Rate> Rates { get; set; }
   

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        }
    }
}

·         Modify web.config file
<connectionStrings>
<add name="EFDbContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=BankRatesDB2015;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>

·         Add EFDbInitializer.cs file into the folder Conrete
·         Save Listing 1-4 into your EFDbInitializer.cs file

Listing 1-4 EFDbInitializer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using <YourSolution>.Domain.Entities;
using System.Data.Entity.Validation;
namespace <YourSolution>.Domain.Concrete
{
    public class EFDbInitializer : System.Data.Entity.DropCreateDatabaseIfModelChanges<EFDbContext>
    {
        protected override void Seed(EFDbContext context)
        {
            var rateList = new List<Rate>
            {
                new Rate { Name = "Opening an account", Value = "10 EUR" },
                new Rate { Name = "Account maintenance", Value = "5 EUR per year" },
                new Rate { Name = "Maintenance of an inactive account", Value = "10 EUR per year" },
                new Rate { Name = "Closing an account", Value = "Free of charge" }
            };

            rateList.ForEach(s => context.Rates.Add(s));
            try
            {
                context.SaveChanges();
            }
            catch (DbEntityValidationException e)
            {

                foreach (var eve in e.EntityValidationErrors)
                {

                    string name = eve.Entry.Entity.GetType().Name;
                    string name2 = eve.Entry.State.ToString();
                    foreach (var ve in eve.ValidationErrors)
                    {

                        string pn = ve.PropertyName;
                        string errM = ve.ErrorMessage;
                    }
                }
            }
        }
    }
}

Seed method will add 4 records into the table Rate. In this example, we use abstract bank rates data from price list for private customers. For example, it could be a price of 10 EUR for opening an account.
·         Add GenericRepository.cs file into the folder Conrete
·         Add UnitOfWork.cs file into the folder Conrete
·         Modify GenericRepository.cs file according below Listing 1-5

Listing 1-5 EFDbInitializer.cs
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace <YourSolution>.Domain.Concrete
{
    public class GenericRepository<TEntity> where TEntity : class
    {
        internal EFDbContext context;
        internal DbSet<TEntity> dbSet;

        public GenericRepository(EFDbContext context)
        {
            this.context = context;
            this.dbSet = context.Set<TEntity>();
        }
                     
        public virtual IEnumerable<TEntity> Get()
        {
            IQueryable<TEntity> query = dbSet;
            return query.ToList();
        }

        public virtual TEntity GetByID(object id)
        {
            return dbSet.Find(id);
        }

        public virtual void Insert(TEntity entity)
        {
            dbSet.Add(entity);
        }

        public virtual void Delete(object id)
        {
            TEntity entityToDelete = dbSet.Find(id);
            Delete(entityToDelete);
        }

        public virtual void Delete(TEntity entityToDelete)
        {
            if (context.Entry(entityToDelete).State == EntityState.Detached)
            {
                dbSet.Attach(entityToDelete);
            }
            dbSet.Remove(entityToDelete);
        }

        public virtual void Update(TEntity entityToUpdate)
        {
            dbSet.Attach(entityToUpdate);
            context.Entry(entityToUpdate).State = EntityState.Modified;
        }

    }
}

·         Modify UnitOfWork.cs file according below Listing 1-6

Listing 1-6 UnitOfWork.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using <YourSolution>.Domain.Entities;

namespace <YourSolution>.Domain.Concrete
{
    public class UnitOfWork : IDisposable
    {
        private EFDbContext context = new EFDbContext();

        private GenericRepository<Rate> rateRepository;
        public GenericRepository<Rate> RateRepository
        {
            get
            {
                if (this.rateRepository == null)
                    this.rateRepository = new GenericRepository<Rate>(context);
                return rateRepository;
            }
        }
        public void Save()
        {
            context.SaveChanges();
        }

        private bool disposed = false;
        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;

            if (disposing)
            {
                //Free any other managed objects here.
                context.Dispose();
            }

            // Free any unmanaged objects here.
            disposed = true;
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}


·         Modify <YourProject> web.config add in section <entityFramework>.... below Listing 1-7
Listing 1-7  web.config
<contexts>
<context type="<YourProject>.Domain.Concrete.EFDbContext, <YourProject>.Domain">
<databaseInitializer type="<YourProject>.Domain.Concrete.EFDbInitializer, <YourProject>.Domain" />
</context>
</contexts>

Now we need to create a controller and view for our model entity. Usually I create a new controller for model class, but for this example, I will use Index.cshtml under Home project and HomeController to simplify the example.

·         Open HomeController.cs file in Controllers folder and modify file according Listing 1-8

Listing 1-8  HomeController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using <YourSolution>.Domain.Concrete;
using <YourSolution>.Domain.Entities;

namespace <YourSolution>.Controllers
{
    public class HomeController : Controller
    {

        UnitOfWork unitOfWork = new UnitOfWork();
        //
        // GET: /Home/
        public ActionResult Index()
        {
            return View();
        }

        public JsonResult GetRates()
        {
            List<Rate> ratesList = unitOfWork.RateRepository.Get().ToList();
            return Json(ratesList, JsonRequestBehavior.AllowGet);
        }
       }
}




Now if everything was done right, we can run the project and see our action method has returned 4 records from local database table „Rate” see pic.



We can see JSON string in browser as the result of action GetRates
·         Open Service.js file and add the following code snippet
this.getRate = function () {
        return $http.get("/Home/GetRates");
    };


·         Next open Index.cshtml file in View folder under Home.
·         Modify Index.cshtml according Listing 1-9

Listing 1-9 Index.cshtml

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}
  <div class="CSSTableGenerator" ng-controller="BankRatesCtrl">
      <p>{{Operation}}</p>
       <input type="button" value="Add" ng-click="add()" />
        <table>
            <tbody>
                <tr>
                    <td>ID</td>
                    <td>Service</td>
                    <td>Price</td>
                </tr>
                <tr ng-repeat="rate in rates">
                    <td>
                        <div ng-hide="editingData[rate.RateID]">{{rate.RateID | uppercase}}</div>
                        <div ng-show="editingData[rate.RateID]" ng-model="rate.RateID">{{rate.RateID | uppercase}}</div>
                    </td>
                    <td>
                        <div ng-hide="editingData[rate.RateID]">{{rate.Name | uppercase}}</div>
                        <div ng-show="editingData[rate.RateID]"><input type="text" ng-model="rate.Name" /></div>
                    </td>
                    <td>
                        <div ng-hide="editingData[rate.RateID]">{{rate.Value}}</div>
                        <div ng-show="editingData[rate.RateID]"><input type="text" ng-model="rate.Value" /></div>
                    </td>
                    <td>
                        <button ng-hide="editingData[rate.RateID]" ng-click="edit(rate)">Edit</button>
                        <button ng-show="editingData[rate.RateID]" ng-click="save(rate)">Save</button>
                        <button ng-show="editingData[rate.RateID]" ng-click="delete(rate)">Remove</button>
                    </td>
                </tr>
                <tr ng-show="addNew">
                    <td>
                        <input type="text" style="width:20px;" disabled="disabled" ng-model="RateID" />
                    </td>
                    <td>
                        <input type="text" style="width:94px;" ng-model="rate.Name" />
                    </td>


                    <td>
                        <input type="text" style="width:94px;" ng-model="rate.Value" />
                    </td>

                    <td colspan="2">
                        <input type="button" value="Add new" ng-click="save(rate)" />
                    </td>
                </tr>
            </tbody>
        </table>
    </div>


If you run application, you should see the table as in picture below:



NOTE: I have added all html and angulajs code at ones to save the time for entry code.

·         Open Service.js file and add the following code snippet
    //Save (Update) 
    this.update = function (rate) {
        var response = $http({
            method: "post",
            url: "/Home/UpdateRates",
            data: JSON.stringify(rate),
            dataType: "json"
        });
        return response;
    }

·         Open Controller.js file and add the following code snippet

Function $scope.edit converts div eelemnts into the input texboxe with values

$scope.edit = function (rate) {
        $scope.editingData[rate.RateID] = true;
        $scope.RateID = rate.RateID;
        $scope.Name = rate.Name;
        $scope.Value = rate.Value;
        $scope.Operation = "Update";
        $scope.divRateModification = true;
}

Function $scope.save creates an javacript object with values in row you want yo edit

var Rate = {
            RateID: rate.RateID,
            Name: rate.Name,
            Value: rate.Value
        };

and pass it to Service.js function called angularService.update(Rate). Action method in HomeConstoller.cs file is invoked. The method gets the record by id, modifies the values of an existiong object and saves the changes in the table of database.

$scope.save = function (rate) {
      
        var Rate = {
            RateID: rate.RateID,
            Name: rate.Name,
            Value: rate.Value
        };
        var Operation = $scope.Operation;

        if (Operation == "Update") {
            alert('dd');
            var getMSG = angularService.update(Rate);
            getMSG.then(function (messagefromController) {
                GetAllRates();
                alert(messagefromController.data);
                $scope.divRateModification = false;
            }, function () {
                alert('Update Error');
            });
        }
        else {
//Add method
        }
        $scope.editingData[rate.RateID] = false;
    }

·         Open HomeController.cs file in Controllers folder and add the following code according Listing 1-10

Listing 1-10 HomeController.cs add UpdateRates action method

public string UpdateRates(Rate rate) {

            if (rate != null)
            {
                var rateRecord = unitOfWork.RateRepository.GetByID(rate.RateID);
                if (rateRecord != null)
                {
                    rateRecord.Name = rate.Name;
                    rateRecord.Value = rate.Value;
                    unitOfWork.RateRepository.Update(rateRecord);
                    unitOfWork.Save();

                    return "Record has been Updated";
                }
                else
                    return "Update error";
            }
            else
            {
                return "Record has Not been Updated";
            }
        }

·         Now if you click on “Edit” button the row cells should be changed to input elements as it shown in the picture


·         Change the price for first record and click button Save. You should see the Dialog box with message “Record has been Updated” and the cell value should have a new value





·         Now we will add Delete function to our solution
·         Open Service.js file and add the following code snippet
    //Delete
    this.Delete = function (rateID) {
        var response = $http({
            method: "post",
            url: "/Home/Delete",
            params: {
                id: rateID
            }
        });
        return response;
    }
·         Open HomeController.cs file in Controllers folder and add the following code for delete method according Listing 1-11

Listing 1-11 HomeController.cs

   public string Delete(int id)
        {
            try
            {
                if (id != 0)
                {
                    unitOfWork.RateRepository.Delete(id);
                    unitOfWork.Save();

                    return "Rate has Been Deleted";
                }
                else
                {
                    return "Rate has not Been Deleted";
                }
            }
            catch
            {
                return "Rate has not Been Deleted";
            }
        }

·         Open Controller.js file and add the following code snippet
      

  $scope.delete = function (rate) {
        alert(rate.RateID);
        var getMSG = angularService.Delete(rate.RateID);
        getMSG.then(function (messagefromController) {
            GetAllRates();
            alert(messagefromController.data);
        }, function () {
            alert('Delete Error');
        });
    }

·         Run the project, click Edit button and then click Remove. 

The record “Opening an account” should be deleted.


·         The last action we will add to this demo example is adding a new record.
·         Open Controller.js file, replace  comments   //Add method with the following code snippet

    var getMSG = angularService.Add(Rate);
            getMSG.then(function (messagefromController) {
                GetAllRates();
                alert(messagefromController.data);
                $scope.divRateModification = false;
  $scope.addNew = false;
            }, function () {
                alert('Insert Error');
            });
        }
·         Open Service.js file and add the following code snippet
   
    //Add
    this.Add = function (rate) {
        var response = $http({
            method: "post",
            url: "/Home/Add",
            data: JSON.stringify(rate),
            dataType: "json"

        });
        return response;
}
·         Open HomeController.cs file in Controllers folder and add the following code for add method according Listing 1-12

      public string Add(Rate rate)
        {
            try
            {
                if (rate != null)
                {
                    unitOfWork.RateRepository.Insert(rate);
                    unitOfWork.Save();
                    return "Record has been Added";
                }
                else
                {
                    return "Record has Not been Verified";
                }
            }

            catch
            {
                return "Record has Not been Added";
            }
        }

A new record „NEW SERVICE” with price „10 EUR” was added to database


In this step by step tutorail I have tired to show you how to make edit in place table to add, update and delete records from database using AngularJS framework in ASP.MVC project. The project programming code you can find going to the following link on GitHub.

Demo project on GitHub:

https://github.com/aperepjolkin/demo

I hope you enjoy the demo and it will help you. If you find any inaccuracies in the article, please let me know.

Have a good day! J
//Best regards, Aleks