How to create a SPA MVC CRUD application using WebAPI, Angular JS, bootstrap & Entity framework


Tarun Kumar Chatterjee
.Net – Technology Specialist
Published On :   15 Nov 2015
Visit Count
Today :  5    Total :   6702
Plan, Migrate, Secure, Report
SharePoint & Office 365 Tool. Simple & Easy to Use. 15-Day Trial!

Sharegate: Kick-Ass Tool
Think Your SharePoint & Office 365 Are Secure ? Find Out Now!


In this artifact let me explain you the steps to create a SPA MVC client application which will pull the data from WebAPI using Get or Post methods. Also, I have incorporated Angular JS, now a day it is most commonly used JavaScript framework. I am trying to cover Angular functionality called as ng-view to load partial views which is similar like usercontrol we have used in Asp.net application, also have tried to resolve the issues I faced at the time of implementing.

In traditional web applications, the client (browser) communicates with server by requesting a page. The server then processes the request and sends the HTML page to the client. In subsequent interactions with the page, for example the user navigates to a link or submits a form with data, a new request is sent to the server and the flow starts again. The server processes the request and sends a new page to the browser in response to the new action requested by the client.
But in Single-Page Applications (SPAs) the entire page is loaded into the browser after the initial request, subsequent interactions take place using Ajax requests. There is no need to reload the entire page. The SPA approach reduces the time taken by the application to respond to user actions, resulting in a more fluid experience.
clip_image002
Now we will learn this by creating a sample application. In this SPA example I will do CRUD operations on the Student table using the Web API and MVC.

The below DB script will create a table with Data needs to be executed in SQL Server database.

 USE [EmpDb]
 GO
 /****** Object:  Table [dbo].[Employees]    Script Date: 10/31/2015 23:24:53 ******/
 SET ANSI_NULLS ON
 GO
 SET QUOTED_IDENTIFIER ON
 GO
 CREATE TABLE [dbo].[Employees](
 	[EmployeeId] [int] IDENTITY(1,1) NOT NULL,
 	[FirstName] [nvarchar](20) NOT NULL,
 	[LastName] [nvarchar](20) NOT NULL,
 	[Description] [nvarchar](100) NOT NULL,
 	[Salary] [real] NOT NULL,
 	[Country] [nvarchar](50) NOT NULL,
 	[State] [nvarchar](50) NOT NULL,
 	[DateofBirth] [datetime] NOT NULL,
 	[IsActive] [bit] NOT NULL,
  CONSTRAINT [PK_dbo.Employees] PRIMARY KEY CLUSTERED 
 (
 	[EmployeeId] ASC
 )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
 ) ON [PRIMARY]
 GO
 
 SET IDENTITY_INSERT [dbo].[Employees] ON
 INSERT [dbo].[Employees] ([EmployeeId], [FirstName], [LastName], [Description], [Salary], [Country], [State], [DateofBirth], [IsActive]) VALUES (1, N'Tarun1', N'Chatterjee1', N'Tarun1 Chatterjee1', 99999, N'IN', N'WB', CAST(0x00008403017F260F AS DateTime), 0)
 INSERT [dbo].[Employees] ([EmployeeId], [FirstName], [LastName], [Description], [Salary], [Country], [State], [DateofBirth], [IsActive]) VALUES (2, N'Tarun2', N'Chatterjee2', N'Tarun2 Chatterjee2', 49999.28, N'IN', N'WB', CAST(0x00008128017F2610 AS DateTime), 1)
 INSERT [dbo].[Employees] ([EmployeeId], [FirstName], [LastName], [Description], [Salary], [Country], [State], [DateofBirth], [IsActive]) VALUES (3, N'Tarun3', N'Chatterjee3', N'Tarun3 Chatterjee3', 234234240, N'IN', N'WB', CAST(0x0000A4CE0130DEE0 AS DateTime), 0)
 INSERT [dbo].[Employees] ([EmployeeId], [FirstName], [LastName], [Description], [Salary], [Country], [State], [DateofBirth], [IsActive]) VALUES (6, N'Tarun4', N'Chatterjee4', N'Tarun4 Chatterjee4', 889999, N'IN', N'WB', CAST(0x0000A4CF00000000 AS DateTime), 1)
 SET IDENTITY_INSERT [dbo].[Employees] OFF
 
 

Our database is ready. Now we will have to pull the data into the WebAPI with help of Entity Framework. Let see how it would be:

First, we will open Visual Studio 2013 & then click on New Project.

clip_image004

Select Web -> ASP.NET Web Application then provide a name & then click on OK.clip_image005

Right-click on the Models Folder & then select Add -- > ADO.NET Entity Data Model.

clip_image007

Give the Item name as “ManageEmployee” & then Ok

clip_image009

clip_image011

Next, select table Name & then Finish

Now we will have to take Angular JS references. So, go to Package manager console and execute the below command to install Angular JS package to your solution.

PM> Install-Package angularjs

In RouteConfig add the following code

routes.MapRoute(

name: "Default",

url: "{controller}/{action}/{id}",

defaults: new { controller = "Employee", action = "Index", id = UrlParameter.Optional }

);

Then we will have to add a Web API Controller, so right-click on the controller folder & then select Add -- > Controller.

clip_image013

clip_image015

Here is the generated EmployeeAPIController class

 using System;
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Entity;
 using System.Data.Entity.Infrastructure;
 using System.Linq;
 using System.Net;
 using System.Net.Http;
 using System.Web.Http;
 using System.Web.Http.Description;
 using SpaWebApiMVC.Models;
 
 namespace SpaWebApiMVC.Controllers
 {
     public class EmployeeAPIController : ApiController
     {
         private EmpDbEntities db = new EmpDbEntities();
 
         // GET api/EmployeeAPI
         public IQueryable<Employee> GetEmployee()
         {
             return db.Employees;
         }
 
         // GET api/EmployeeAPI/5
         [ResponseType(typeof(Employee))]
         public IHttpActionResult GetEmployee(int id)
         {
             Employee employee = db.Employees.Find(id);
             if (employee == null)
             {
                 return NotFound();
             }
 
             return Ok(employee);
         }
 
         // PUT api/EmployeeAPI/5
         public IHttpActionResult PutEmployee(int id, Employee employee)
         {
             if (!ModelState.IsValid)
             {
                 return BadRequest(ModelState);
             }
 
             if (id != employee.EmployeeId)
             {
                 return BadRequest();
             }
 
             db.Entry(employee).State = EntityState.Modified;
 
             try
             {
                 db.SaveChanges();
             }
             catch (DbUpdateConcurrencyException)
             {
                 if (!EmployeeExists(id))
                 {
                     return NotFound();
                 }
                 else
                 {
                     throw;
                 }
             }
 
             return StatusCode(HttpStatusCode.NoContent);
         }
 
         // POST api/EmployeeAPI
         [ResponseType(typeof(Employee))]
         public IHttpActionResult PostEmployee(Employee employee)
         {
             if (!ModelState.IsValid)
             {
                 return BadRequest(ModelState);
             }
 
             db.Employees.Add(employee);
             db.SaveChanges();
 
             return CreatedAtRoute("DefaultApi", new { id = employee.EmployeeId }, employee);
         }
 
         // DELETE api/EmployeeAPI/5
         [ResponseType(typeof(Employee))]
         public IHttpActionResult DeleteEmployee(int id)
         {
             Employee employee = db.Employees.Find(id);
             if (employee == null)
             {
                 return NotFound();
             }
 
             db.Employees.Remove(employee);
             db.SaveChanges();
 
             return Ok(employee);
         }
 
         protected override void Dispose(bool disposing)
         {
             if (disposing)
             {
                 db.Dispose();
             }
             base.Dispose(disposing);
         }
 
         private bool EmployeeExists(int id)
         {
             return db.Employees.Count(e => e.EmployeeId == id) > 0;
         }
     }
 }
 
 

Again, right-click on the Controller folder & then select Add -- > Controller.

Select MVC Controller Empty and then ADD

Give the controller name as “EmployeeController”

Now, we are ready to add a view.

clip_image017

Index.chtml Code will be like:

 @{
     ViewBag.Title = "SPA";
     Layout = "~/Views/Shared/_Layout.cshtml";
 }
 
 <link href="~/Content/bootstrap.min.css" rel="stylesheet" />
 <body data-ng-app="ApplicationModule">
     <div>
         <div>
             <div>
                 <table cellpadding="5" cellspacing="6" width="100%" style="background-color:none; border:solid 4px black;">
                     <tr>
                         <td style="border: solid 1px ; width:170px; text-align:center;" class="glyphicon glyphicon-user"><a href="#/showEmployee"> Show All</a></td>
                         <td style="border: solid 1px ; width:170px; text-align:center;" class="glyphicon glyphicon-plus"><a href="#/addEmployee"> Add New</a></td>
                         <td></td>
                     </tr>
                 </table>
             </div>
             <div>
                 <div data-ng-view></div>
             </div>
         </div>
     </div>
 
 </body>
 
 @section scripts{
     <script type="text/javascript" src="@Url.Content("~/Scripts/angular.js")"></script>
     <script type="text/javascript" src="@Url.Content("~/Scripts/angular-route.min.js")"></script>
     <script type="text/javascript" src="@Url.Content("~/CustomScripts/Module.js")"></script>
     <script type="text/javascript" src="@Url.Content("~/CustomScripts/Services.js")" ></script>
     <script type="text/javascript" src="@Url.Content("~/CustomScripts/ShowEmployeeController.js")"></script>
     <script type="text/javascript" src="@Url.Content("~/CustomScripts/AddEmployeeController.js")"></script>
     <script type="text/javascript" src="@Url.Content("~/CustomScripts/EditEmployeeController.js")"></script>
     <script type="text/javascript" src="@Url.Content("~/CustomScripts/DeleteEmployeeController.js")"></script>
 } 
 
 

Here, we will add a partial view for all the modules. So right-click on Views then selects the Employee folder.

clip_image019

ShowEmployee.chtml code will be like:

<table cellpadding="4" cellspacing="4" width="100%" style="background-color:none; border:solid 2px ; padding-top:10px;">

<thead>

<tr style="background-color:none">

<th>Employee ID</th>

<th>FirstName</th>

<th>LastName</th>

<th>Description</th>

<th>Salary</th>

<th>Country</th>

<th>State</th>

<th>DateofBirth</th>

<th>IsActive</th>

</tr>

</thead>

<tbody>

<tr data-ng-repeat="emp in Employee">

<td>{{emp.employeeId}}</td>

<td>{{emp.firstName}}</td>

<td>{{emp.lastName}}</td>

<td>{{emp.description}}</td>

<td>{{emp.Salary}}</td>

<td>{{emp.country}}</td>

<td>{{emp.state}}</td>

<td>{{emp.dateofBirth}}</td>

<td>{{emp.isActive}}</td>

<td><a ng-href="#/editEmployee/{{emp.employeeId}}" class="glyphicon glyphicon-edit">Edit</a></td>

<td><a ng-href="#/deleteEmployee/{{emp.employeeId}}" class="glyphicon glyphicon-trash">Delete</a></td>

</tr>

</tbody>

</table>

Similarly, AddEmployee.chtml code will be like:

@{

ViewBag.Title = "Add New Employee";

}

<table><tr><td height="10px"></td></tr></table>

<table cellpadding="4" cellspacing="4" width="70%" align="center" style="background-color:none;

border:solid 2px ; padding-top:20px;">

<tr>

<td colspan="3" style="background-color:gray; font-size:18pt;

font-weight:bold; height:30px; text-align:center;">

Add New Employee

</td>

</tr>

<tr>

<td style="text-align:right;">Employee ID</td>

<td><input type="text" ng-model="EmployeeId" /> </td>

</tr>

<tr>

<td style="text-align:right;">FirstName</td>

<td><input type="text" ng-model="FirstName" /> </td>

</tr>

<tr>

<td style="text-align:right;">LastName</td>

<td><input type="text" ng-model="LastName" /> </td>

</tr>

<tr>

<td style="text-align:right;">Description</td>

<td><input type="text" ng-model="Description" /> </td>

</tr>

<tr>

<td style="text-align:right;">Salary</td>

<td><input type="text" ng-model="Salary" /> </td>

</tr>

<tr>

<td style="text-align:right;">Country</td>

<td><input type="text" ng-model="Country" /> </td>

</tr>

<tr>

<td style="text-align:right;">State</td>

<td><input type="text" ng-model="State" /> </td>

</tr>

<tr>

<td style="text-align:right;">DateofBirth</td>

<td><input type="text" ng-model="DateofBirth" /> </td>

</tr>

<tr>

<td style="text-align:right;">IsActive</td>

<td><input type="text" ng-model="IsActive" /> </td>

</tr>

<tr>

<td></td>

<td>

<input type="button" value="Save" data-ng-click="save()" />

</td>

</tr>

</table>

EditEployee.chtml code will be like:

@{

ViewBag.Title = "Edit Employee";

}

<table><tr><td height="10px"></td></tr></table>

<table cellpadding="4" cellspacing="4" width="70%" align="center" style="background-color: none; border:solid 2px black; padding-top:20px;">

<tr><td colspan="3" style="background-color:none; font-size:18pt; font-weight:bold; height:30px; text-align:center;">Edit Employee</td></tr>

<tr>

<td style="text-align:right;">Employee ID</td>

<td><input type="text" ng-model="Employee.employeeId" /> </td>

</tr>

<tr>

<td style="text-align:right;">FirstName</td>

<td><input type="text" ng-model="Employee.firstName" /> </td>

</tr>

<tr>

<td style="text-align:right;">LastName</td>

<td><input type="text" ng-model="Employee.lastName" /> </td>

</tr>

<tr>

<td style="text-align:right;">Description</td>

<td><input type="text" ng-model="Employee.description" /> </td>

</tr>

<tr>

<td style="text-align:right;">Salary</td>

<td><input type="text" ng-model="Employee.salary" /> </td>

</tr>

<tr>

<td style="text-align:right;">Country</td>

<td><input type="text" ng-model="Employee.country" /> </td>

</tr>

<tr>

<td style="text-align:right;">State</td>

<td><input type="text" ng-model="Employee.state" /> </td>

</tr>

<tr>

<td style="text-align:right;">DateofBirth</td>

<td><input type="text" ng-model="Employee.dateofBirth" /> </td>

</tr>

<tr>

<td style="text-align:right;">IsActive</td>

<td><input type="text" ng-model="Employee.isActive" /> </td>

</tr>

<tr>

<td></td>

<td>

<input type="button" value="Save" ng-click="save()" />

<br />

<div>{{error}}</div>

</td>

</tr>

</table>

DeleteEmployee.chtml code will be like:

@{

ViewBag.Title = "Delete Employee";

}

<table><tr><td height="10px"></td></tr></table>

<table cellpadding="4" cellspacing="4" width="70%" align="center" style="background-color: none; border:solid 2px black; padding-top:20px;">

<tr><td colspan="3" style="background-color:none; font-size:18pt; font-weight:bold; height:30px; text-align:center;">Delete Employee</td></tr>

<tr>

<td style="text-align:right;">Employee ID</td>

<td>{{Employee.employeeId}} </td>

</tr>

<tr>

<td style="text-align:right;">FirstName</td>

<td> {{Employee.firstName}} </td>

</tr>

<tr>

<td style="text-align:right;">LastName</td>

<td>{{Employee.lastName}} </td>

</tr>

<tr>

<td style="text-align:right;">Description</td>

<td>{{Employee.description}} </td>

</tr>

<tr>

<td style="text-align:right;">Salary</td>

<td>{{Employee.salary}} </td>

</tr>

<tr>

<td style="text-align:right;">Country</td>

<td>{{Employee.country}} </td>

</tr>

<tr>

<td style="text-align:right;">State</td>

<td>{{Employee.state}} </td>

</tr>

<tr>

<td style="text-align:right;">DateofBirth</td>

<td>{{Employee.dateofBirth}} </td>

</tr>

<tr>

<td style="text-align:right;">IsActive</td>

<td>{{Employee.isActive}} </td>

</tr>

<tr>

<td></td>

<td>

<input type="button" value="Delete" ng-click="delete()" />

</td>

</tr>

</table>

Next, we will have to add the following code in EmployeeController.cs

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.Mvc;

namespace SpaWebApiMVC.Controllers

{

public class EmployeeController : Controller

{

//

// GET: /Employee/

public ActionResult Index()

{

return View();

}

public ActionResult AddEmployee()

{

return PartialView("AddEmployee");

}

public ActionResult ShowEmployee()

{

return PartialView("ShowEmployee");

}

public ActionResult EditEmployee()

{

return PartialView("EditEmployee");

}

public ActionResult DeleteEmployee()

{

return PartialView("DeleteEmployee");

}

}

}

Again, we will have to add a new folder called “CustomScripts”, to the solution which will have the custom controllers for all the individual modules.

So, we need to add the following JavaScript files within “CustomScripts” folder.

  • Module.js
  • Services.js
  • ShowEmployeeController.js
  • AddEmployeeController.js
  • EditEmployeeController.js
  • DeleteEmployeeController.js

Module.JS code will be like:

var app = angular.module("ApplicationModule", ["ngRoute"]);

app.factory("ShareData", function () {

return { value: 0 }

});

//Showing Routing

app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {

$routeProvider.when('/showEmployee',

{

templateUrl: 'Employee/ShowEmployee',

controller: 'ShowEmployeeController'

});

$routeProvider.when('/addEmployee',

{

templateUrl: 'Employee/AddEmployee',

controller: 'AddEmployeeController'

});

$routeProvider.when("/editEmployee/:id",

{

templateUrl: 'Employee/EditEmployee',

controller: 'EditEmployeeController'

});

$routeProvider.when('/deleteEmployee/:id',

{

templateUrl: 'Employee/DeleteEmployee',

controller: 'DeleteEmployeeController'

});

$routeProvider.otherwise(

{

redirectTo: '/showEmployee'

});

//This can be used to remove # from URL

//$locationProvider.html5Mode(true).hashPrefix('!')

}]);

Services.js code will be like:

app.service("SPACRUDService", function ($http) {

//Read all Employee

this.getEmployees = function () {

return $http.get("/api/EmployeeAPI");

};

//Fundction to Read Employee by Employee ID

this.getEmployee = function (id) {

return $http.get("/api/EmployeeAPI/" + id);

};

//Function to create new Employee

this.post = function (Employee) {

var request = $http({

method: "post",

url: "/api/EmployeeAPI",

data: Employee

});

return request;

};

//Edit Employee By Employee ID

this.put = function (id, Employee) {

var request = $http({

method: "put",

url: "/api/EmployeeAPI/" + id,

data: Employee

});

return request;

};

//Delete Employee By Employee ID

this.delete = function (id) {

var request = $http({

method: "delete",

url: "/api/EmployeeAPI/" + id

});

return request;

};

});

ShowEmployeeController.js code will be like:

app.controller('ShowEmployeeController', function ($scope, $location, $routeParams, SPACRUDService, ShareData) {

loadEmployee();

function loadEmployee() {

var emp = SPACRUDService.getEmployees();

emp.then(function (pl) {

$scope.Employee = pl.data;

console.log(JSON.stringify($scope.Employee));

},

function (errorPl) {

$scope.error = errorPl;

});

}

});

AddEmployeeController.js code will be like:

app.controller('AddEmployeeController', function ($scope, SPACRUDService) {

$scope.EmployeeId = 0;

$scope.save = function () {

var emp = {

EmployeeId: $scope.EmployeeId,

FirstName: $scope.FirstName,

LastName: $scope.LastName,

Description: $scope.Description,

Salary: $scope.Salary,

Country: $scope.Country,

State: $scope.State,

DateofBirth: $scope.DateofBirth,

IsActive: $scope.IsActive

};

var val = SPACRUDService.post(emp);

val.then(function (pl) {

alert("Employee Saved Successfully.");

},

function (errorPl) {

$scope.error = 'failure loading Employee', errorPl;

});

};

});

EditEmployeeController.js code will be like:

app.controller("EditEmployeeController", function ($scope, $location, $routeParams,ShareData, SPACRUDService) {

getEmployee();

function getEmployee() {

ShareData.value = $routeParams.id;

var emp = SPACRUDService.getEmployee(ShareData.value);

emp.then(function (pl) {

$scope.Employee = pl.data;

debugger;

},

function (errorPl) {

$scope.error = 'failure loading Employee', errorPl;

});

}

$scope.save = function () {

var emp = {

EmployeeId: $scope.Employee.employeeId,

FirstName: $scope.Employee.firstName,

LastName: $scope.Employee.lastName,

Description: $scope.Employee.description,

Salary: $scope.Employee.salary,

Country: $scope.Employee.country,

State: $scope.Employee.state,

DateofBirth: $scope.Employee.dateofBirth,

IsActive: $scope.Employee.isActive

};

var val = SPACRUDService.put($scope.Employee.employeeId, emp);

val.then(function (pl) {

$location.path("/showEmployee");

},

function (errorPl) {

$scope.error = 'failure loading Employee', errorPl;

});

};

});

DeleteEmployeeController.js code will be like:

app.controller(“DeleteEmployeeController”, function ($scope, $location, $routeParams, ShareData, SPACRUDService) {

getEmployee();

function getEmployee() {

ShareData.value = $routeParams.id;

var emp = SPACRUDService.getEmployee(ShareData.value);

emp.then(function (pl) {

$scope.Employee = pl.data;

},

function (errorPl) {

$scope.error = ‘failure loading Employee’, errorPl;

});

}

$scope.delete = function () {

var promiseDeleteEmployee = SPACRUDService.delete(ShareData.value);

promiseDeleteEmployee.then(function (pl) {

$location.path(“/showEmployee”);

},

function (errorPl) {

$scope.error = ‘failure loading Employee’, errorPl;

});

};

});

Now rebuild the solution and run. Following is the output of ShowEmployee page :

clip_image021

Edit page output :

clip_image023

Delete page output :

clip_image025

So, we can see here in each url is having # and to ignore # from the url we need to modify our apps config, it now has Angular's $locationProvider module as a dependency and we need to enable hashPrefix and html5mode functions. That's it.

Enable the following code in Module.js

$locationProvider.html5Mode(true).hashPrefix('!');

Our URL's look better, but hit refresh (F5) & it will throw the 404 error

clip_image027

HTML5 mode is working, but only in a very superficial way. A refresh of the page is sending the full URL to the server (as we have removed the #) which doesn't know what to do. We can fix this by reconfiguring MVC's RouteCollection properly. We need to be explicit about the route for each of our views, and then add a catch-all which sends all other URL's to our landing page, to be handled by Angular.

Update the RegisterRoutes method inside App_Start -- > RouteConfig.cs like :

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.Mvc;

using System.Web.Routing;

namespace SpaWebApiMVC

{

public class RouteConfig

{

public static void RegisterRoutes(RouteCollection routes)

{

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(

name: "ShowEmployee",

url: "Employee/ShowEmployee",

defaults: new { controller = "Employee", action = "ShowEmployee" }

);

routes.MapRoute(

name: "AddEmployee",

url: "Employee/AddEmployee",

defaults: new { controller = "Employee", action = "AddEmployee", id = UrlParameter.Optional }

);

routes.MapRoute(

name: "EditEmployee",

url: "Employee/EditEmployee/{id}",

defaults: new { controller = "Employee", action = "EditEmployee", id = UrlParameter.Optional }

);

routes.MapRoute(

name: "DeleteEmployee",

url: "Employee/DeleteEmployee/{id}",

defaults: new { controller = "Employee", action = "DeleteEmployee", id = UrlParameter.Optional }

);

routes.MapRoute(

name: "Default",

url: "{*url}",

defaults: new { controller = "Employee", action = "Index", id = UrlParameter.Optional }

);

}

}

}

Now change the below line of codes in ShowEmployee.chtml

<td><a ng-href="editEmployee/{{emp.employeeId}}" class="glyphicon glyphicon-edit">Edit</a></td>

<td><a ng-href="deleteEmployee/{{emp.employeeId}}" class="glyphicon glyphicon-trash">Delete</a></td>

And the below lines in Index.chtml

<td style="border: solid 1px ; width:170px; text-align:center;" class="glyphicon glyphicon-user"><a href="showEmployee"> Show All</a></td>

<td style="border: solid 1px ; width:170px; text-align:center;" class="glyphicon glyphicon-plus"><a href="addEmployee"> Add New</a></td>

Debug the site again, browse around, and test the back/refresh buttons, everything should be working properly now.

Hope, this will help you. If you have any question feel free to write me a note in comments, I will try to explain.

Happy Coding

Tarun Kumar Chatterjee

SharePoint Usage Reports
Usage reports, collaboration and audit for SharePoint.
Categories

SharePoint Analytics