Friday, 25 April 2014

Passing Objects in JSON through ViewBag

Below is an example of how to pass a collection of objects in JSON format through ViewBag.
List<Student> studentsList = new List<Student>();
studentsList = GetStudents();
ViewBag.Students = System.Web.Helpers.Json.Encode(studentsList);
We use System.Web.Helpers.Json.Encode() function to do the formatting and on the view, we just need to render the passed content like this:
@Html.Raw((String)ViewBag.Students)

A different way to do this is by passing the collection directly through ViewBag
ViewBag.Students = studentsList;
then we do the formatting on the view
@Html.Raw(Json.Encode((IList<Student>)ViewBag.Students))
However this way is less efficient than the earlier one.

Wednesday, 23 April 2014

Building Single Page Application with AngularJS and Web API - Part 2

On this post, we will continue building our single page application with AngularJS and Web API. For the first part of this topic, please see my previous post.

Update Functionality
Now we want to add update user functionality. First, we add a new route in our AngularJS routing configuration:
. . .
.when('/edit/:userId', {
 templateUrl: 'Partials/create-edit.html',
 controller: 'UserCtrl'
}).
. . .
Note that we use a parameter ':userId' in the path. This is used to get a value from the url and will be stored into $routeParams object. A new property called 'userId' will be created in $routeParams object (i.e. $routeParams.userId).

Other types of parameter can be used as well in a routing path:
- ':name*' is used to store all values from the path of the parameter up to the next matching string path. For example; if we set a route like '/edit/:user*/end' then when we have a url '/edit/1/type/admin/end', :user will have value '1/type/admin'
- 'name?', we can use '?' to specify that this is an optional parameter

Then add logic for updating user in our WebAPI controller:
// PUT api/UserRegistration/5
public async Task<IHttpActionResult> PutUser(int id, User user)
{
 if (!ModelState.IsValid)
 {
  return BadRequest(ModelState);
 }

 if (id != user.UserId)
 {
  return BadRequest();
 }

 db.Entry(user).State = EntityState.Modified;

 try
 {
  await db.SaveChangesAsync();
 }
 catch (DbUpdateConcurrencyException)
 {
  if (!UserExists(id))
  {
   return NotFound();
  }
  else
  {
   throw;
  }
 }

 return StatusCode(HttpStatusCode.NoContent);
}

Also add this in our AngularJS user service:
. . .
getById: function (id) {
 //return $resource('/api/UserRegistration/' + id).get();
 return $resource('/api/UserRegistration/:userId', { userId: '@userId' }).get({ userId: id });   // this is another way to build the string to be passed
},
updateUser: function (user) {
 return $resource('/api/UserRegistration/' + user.UserId, {},
  {
  customUpdate: { method: 'PUT', isArray:false }
  }
 ).customUpdate(user);
},
. . .
Notice that in updateUser function, we need to specify a custom action to invoke our Web API method. By default AngularJS $resource service has these default methods:
{ 'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'} };
Each method will invoke a call with the specified HTTP method. We need to specify an additional one with HTTP PUT method to invoke the Web API method. isArray is true if we expect the call to return an array of objects.

Then in our AngularJS user controller:
. . .
userService.getById($routeParams.userId).$promise.then(
//success
function (data) {
 $scope.user = data;
},
//error
function (response) {
 //console.log(response.status)
 $scope.error = true;
})
. . .
userService.updateUser(user).$promise.then(
 //success
 function () { $location.url('/'); },
 //error
 function () { $scope.error = true }
);
. . .

Finally, we add this link to the view:
<td><a data-ng-href="#edit/{{user.UserId}}">Edit</a></td>


Delete Functionality

Our delete function does not require a routing because it does not have its on view. The delete function will be executed on the main view.

We specify our Web API method:
// DELETE api/UserRegistration/5
[ResponseType(typeof(User))]
public async Task<IHttpActionResult> DeleteUser(int id)
{
 User user = await db.Users.FindAsync(id);
 if (user == null)
 {
  return NotFound();
 }

 db.Users.Remove(user);
 await db.SaveChangesAsync();

 return Ok(user);
}

Then in our AngularJS user service, we invoke the Web API method with $resource delete method.
. . .
removeUser: function (userId) {
 return $resource('/api/UserRegistration/' + userId).delete();
}
. . .

We add these codes as well to our controller:
. . .
$scope.remove = function (index) {
 if (confirm('Are you sure to delete this user?')) {
  //console.log(index);
  var userId = $scope.users[index].UserId;
  //console.log(userId);
  userService.removeUser(userId).$promise.then(
    //success
    function () { $scope.users.splice(index, 1); },
    //error
    function () { $scope.error = true }
   );
 }
}
. . .

Finally add this delete link to the view:
<td><a href="" data-ng-click="remove($index)">Delete</a></td>
Notice that we use $index property of ng-repeat in the view and pass it to the controller function to be able to know which particular record is being clicked. Then we can get any property's value of the record.

The complete source codes can be downloaded from here.