4.15.2017

AngularJS CRUD, Entity FrameWork & Web API Step-By-Step Part Two

Introduction

This is Part Two of my Angular Step-By-Step tutorial.

Add BundleConfig.cs

  1. Select the App_Start folder. Right-Click and choose the Add from the context-menu.
  2. Add a new Class name it BundleConfig.cs.
  3. This class will use the following namespace using System.Web.Optimization;.
  4. Add the following static method public static void RegisterBundles(BundleCollection bundles)
  5. You may need to install the Microsoft.AspNet.Web.Optimization via NuGet - without it the BundleCollection will not instantiate.
  6. Now to configure this class to load the bundles. We will use the .IncludeDirectory method. Type the following code in the RegisterBundles method:
           bundles.Add(new StyleBundle("~/Content/VendorCss")
                .Include("~/Content/bootstrap.css")
                .Include("~/Content/bootstrap-theme.css")
                .Include("~/Content/font-awesome.css")
                .Include("~/Content/toastr.css"));

            bundles.Add(new ScriptBundle("~/Scripts/VendorJavaScript")
                .Include("~/Scripts/jquery-3.1.1.js")
                .Include("~/Scripts/bootstrap.min.js")
                .Include("~/Scripts/angular.js")
                .Include("~/Scripts/angular-route.js")
                .Include("~/Scripts/toastr.js")
                .Include("~/Scripts/lodash.js"));

Modify the Global.asax.cs File

  1. In the Solution Explorer scroll to the top, select Solution WorldPopulation.
  2. Select the Project WorldPopulation
  3. Expand the file named Global.asax.
  4. Open the file Global.asax.cs
  5. Add the following code to the Application_Start method
BundleConfig.RegisterBundles(BundleTable.Bundles);

NOTE You will need to add a reference to using System.Web.Optimization;.

the Global.asax.cs should now look something like the following:

    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            // Code that runs on application startup
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }

Adding a RAZOR Index.cshtml page

Open the HomeController that was added in Step #7.

  1. The View() will be RED because there is no Index View (remember Model-View-Controller). Right-click on the word Index and select Add View from the popup.
  2. The next dialog will have several options, pick the above Layout Page you just added then click Add.
  3. Press Add.

Now this will insert a basic HTML page, and we need to modify it so the CSS and JavaScript bundles get loaded, and the Single Page Application will be setup.

Modify the Index.cshtml file to look like the following

 @{
    ViewBag.Title = "Index";
    Layout = "~/Views/_LayoutPage.cshtml";
}

<!DOCTYPE html>

@using System.Web.Optimization
<html ng-app="worldPopApp">
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>

    @Styles.Render("~/Content/VendorCss")

    @Scripts.Render("~/Scripts/VendorJavaScript")

    @Scripts.Render("~/bundles/Application")
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">

        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">World Population</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                @*<li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>*@
            </ul>
        </div>

    </div>
    <hr /><hr />
    <div class="main container-fluid" ng-view>

    </div>
</body>
</html>

NOTE There is a bundle reference in this code to @Scripts.Render("~/bundles/Application"). This does not exist in the BundleConfig. We will set this up, and it will not cause the code to crash. It will throw an exception to the browser console.

Angular Directive ng-app

In the Index.cshtml there is a line of code that tells this web application it is an AngularJS application

<html ng-app="worldPopApp">

Use this directive to auto-bootstrap an AngularJS application. The ngApp directive designates the root element of the application and is typically placed near the root element of the page - e.g. on the or tags. There are a few things to keep in mind when using ngApp:

  • only one AngularJS application can be auto-bootstrapped per HTML document. The first ngApp found in the document will be used to define the root element to auto-bootstrap as an application. To run multiple applications in an HTML document you must manually bootstrap them using angular.bootstrap instead.
  • AngularJS applications cannot be nested within each other.
  • Do not use a directive that uses transclusion on the same element as ngApp. This includes directives such as ngIf, ngInclude and ngView. Doing this misplaces the app $rootElement and the app's injector, causing animations to stop working and making the injector inaccessible from outside the app.
You can specify an AngularJS module to be used as the root module for the application. This module will be loaded into the $injector when the application is bootstrapped. It should contain the application code needed or have dependencies on other modules that will contain the code. See angular.module for more information.

Angular Directive ng-view

In the Index.cshtml there is a line of code that injects web pages into Index.cshtml between the div.

Understanding SPA and ng-view can be found here: Single Page Apps with AngularJS Routing and Templating


    <div class="main container-fluid" ng-view>

    </div>

The basic startup view is setup, now we need to setup the AngularJS folder structure, app and routing.

AngularJS Folder Structure

As noted in AngularJS CRUD, Entity FrameWork & Web API Step-By-Step Part One the folder structure we will use is what John Papa recommended years ago, as defined here John Papa project structure.

Steps to Creating the Folder Structure

  1. Select the WorldPopulation Project.
  2. Right-Click the WorldPopulation Project, select New Folder from the context menu.
  3. Name the folder app (Case is critical)
  4. Select the newly created folder app, Right-click it and select New Folder from the context menu.
  5. Name the new folder controllers.
  6. Select the newly created folder app, Right-click it and select New Folder from the context menu.
  7. Name the new folder services.
  8. Select the newly created folder app, Right-click it and select New Folder from the context menu.
  9. Name the new folder views.

Now the basic folder structure for the AngularJS components is completed.

AngularJS Setup app.js

  1. Select the folder app. Right-click it and select Add from the context menu. Select JavaScript File.
  2. Type app in the popup dialog. Press OK

You are presented with an empty file. Paste in the code from below.


var app = angular.module('worldPopApp',
    [
        'ngRoute',
        'toastr'
    ]);

app.config([
    '$routeProvider', function ($routeProvider) {
        $routeProvider.when('/',
            {
                templateUrl: 'app/views/wpList.html',
                controller: 'wpListCtrl'
            }).otherwise({
                redirectTo: '/'
            });
    }
]);

NOTE I have identified an issue with Twitter Bootstrap v3.3.7 as of March 2017, that seems to cause an issue with the Angular UI-Grid - a really nice grid, not being used in this tutorial but one I use extensively over the Kendo-Grid. Just keep in mind, I had to down step my NuGet package for Bootstrap to use the UI-Grid in my personal projects, but I suspect this will be fixed soon.

Injecting the 'toastr' library may be an issue, I am debugging, so if you get any console errors simply comment it out using // and comment out the comma after the injection of the 'ngRoute'.

Now, lets add a VIEW, CONTROLLER and SERVICE. If you don't know what these are I strongly recommend you take any AngularJS 1.x tutorial. In the folder app/views insert the following code into a file named wpList.html (CASE IS IMPORTANT)


<style>
    .tablerow:hover, .tablecell:hover { background-color: #f4a460; }

    table { overflow: hidden; }

    .tablecell { position: relative; }

    .tablecell:hover::before {
        content: "";
        position: absolute;
        left: 0;
        top: -5000px;
        height: 10000px;
        width: 100%;
        z-index: -1;
        /* keep it below table content */
        background-color: #f4a460;
    }

    .tableheader { text-align: center; }
</style>
<div ng-controller="wpListCtrl as vm" ng-init="vm.init()">
    <div class="table-responsive">
        <table class="table table-striped table-bordered">
            <tr>
                <th style="width: 2%;" class="tableheader">Id</th>
                <th style="width: 90%;" class="tableheader">Country</th>
                <th style="width: 4%;"></th>
                <th style="width: 4%;"></th>
            </tr>
            <!--<pre>{{vm.worldpopulation.data | json}}</pre>-->
            <tr class="tablerow" ng-repeat="item in vm.worldpopulation.data">
                <td class="label-success">{{ item.COUNTRYID }}</td>
                <td>
                    <a class="btn btn-link" ng-click="vm.addCountry(item)" style="text-decoration: underline !important; padding: 0 !important; margin: 0 !important;"
                       href="">{{ item.COUNTRY_NAME }}</a>
                </td>
                <td>
                    <a href="#/edit/{{item.COUNTRYID}}" class="glyphicon glyphicon-edit"></a>
                </td>
                <td>
                    <a href="#/delete/{{item.COUNTRYID}}" class="glyphicon glyphicon-trash"></a>
                </td>
            </tr>
        </table>
    </div>
</div>

In the folder app/services insert the following code into a file named wpService.js (CASE IS IMPORTANT)


app.factory("wpService", ["$http", "$q",
    function ($http, $q) {
        var getWorldPopulationData = function () {
            var deferred = $q.defer();
            $http({
                method: 'GET',
                url: '/api/worldpopulation'
            }).then(function (dataResponse) {
                deferred.resolve(dataResponse);
                }, function (dataResponse) {
                    alert("error" + dataResponse);
                deferred.reject();
            });
            return deferred.promise;
        };

        var deleteWorldPopulation = function (data) {
            var deferred = $q.defer();
            $http.delete('/api/worldpopulation/' + data.COUNTRYId).then(function () {
                deferred.resolve(true);
            }, function () {
                alert("error");
                deferred.reject();
            });
            return deferred.promise;
        }

        var saveWorldPopulation = function (data) {
            var deferred = $q.defer();
            $http.post('/api/worldpopulation/', data).then(function () {
                deferred.resolve(true);
            }, function () {
                alert("error");
                deferred.reject();
            });
            return deferred.promise;
        }
        return {
            GetWorldPopulationData: getWorldPopulationData,
            DeleteWorldPopulation: deleteWorldPopulation,
            SaveWorldPopulation: saveWorldPopulation
        }
    }]);

In the folder app/controllers insert the following code into a file named wpListCtrl.js (CASE IS IMPORTANT)


(function () {
    'use strict';
    var controllerId = 'wpListCtrl';
    app.controller(controllerId, ['$scope', '$http', '$location', 'wpService', wpListCtrl]);

    function wpListCtrl($scope, $http, $location, wpNotesService) {
        var vm = this;
        vm.data = wpNotesService;
        vm.sort = 0;
        vm.currentSort = '';
        vm.moduleTitle = 'World Population Countries List';
        vm.worldpopulation = [];
        vm.isActive = false;
        vm.ViewModel = {
            StatusCode: 'A'
        }


        vm.init = function () {
            vm.loadData();

        };

        vm.loadData = function () {
            vm.data.GetWorldPopulationData().then(function (data) {
                vm.worldpopulation = data;
            });
        }

        vm.GetStatus = function (data) {
            if (data.StatusCode === 'A')
                vm.isActive = true;
            vm.isActive = false;
        };
    }
}
)();

Modify the Global.asax.cs File

We need to modify the Global.asax.cs again because our data is coming from the database in EntityFramework and the properties are not serializing the data into a format that can be used easily in the client. so below the Application_Start add the following:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
           GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

Before this modification, when running the application you may see an error such as: The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'." There are several means to address this like

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

But this solution is wrong. This is JSON and XML Serialization in ASP.NET Web API

No comments: