26

janvier
2014

Par Guillaume Nurdin le 26/01/2014 à 23:21

Mots clés: , , ,

Un exemple de CRUD avec AngularJS et ASP.NET MVC et Web API

AngularJSJavaScript est devenu un langage incontournable sur le web, popularisé par sa facilité d’utilisation malgré ses (nombreux) inconvénients. Si l’utilisation de JavaScript peut aujourd’hui s’envisager sur toute la stack d’une application web grâce par exemple à Node.js, il n’en reste pas moins que l’intégrer massivement dans une application web classique (par exemple écrite en Asp.Net) pour manipuler le DOM et faire de l’Ajax conduit souvent à un code difficilement testable et maintenable. L’utilisation de jQuery et l’écriture de Javascript non intrusif améliorent les choses mais laissent le code Javascript fortement lié au DOM et difficilement lisible.

Pour apporter une réponse à cette problématique, et faciliter l’écriture d’application riche avec Javascript (par exemple une Single Page Application) plusieurs frameworks  MV* (ou MVWhatever), tels que Knockout.js ou Backbone.js, ont fait leur apparition. Parmis eux, AngularJS semble être le plus prometteur, et celui qui rencontre le plus grand succès, peut-être (et même sûrement) parce qu’il est développé par Google.

Dans cet article nous allons voir comment l’utiliser pour faire du CRUD avec ASP.NET Web API côté serveur sous Visual Studio 2013. Une application web qui gère le CRUD d’une entité User. L’application est sur une page, elle affiche la liste des utilisateurs, un moyen de les éditer directement dans la liste, ainsi que d’en ajouter de nouveaux. Rien de révolutionnaire donc, mais une demande fonctionnelle récurrente.

L’implémentation côté serveur

Nous partons d'un projet Visual Studio d'application Web ASP.NET MVC classique. Nous ajoutons un contrôleur Web Api avec les méthodes de Read/Write, que nous modifions  pour obtenir :

 
    public class UserController : ApiController
    {
        private readonly IUserRepository userRepository;
        public UserController(IUserRepository userService) { this.userRepository = userRepository; }

        // GET api/user
        public IEnumerable Get() { return userRepository.LoadAll(); }

        // GET api/user/5
        public UserModel Get(int id) { return userRepository.Load(id); }

        // POST api/user
        public UserModel Post(UserModel value) { return userRepository.Save(value); }

        // PUT api/user/
        public UserModel Put(UserModel value) { return userRepository.Save(value); }

        // DELETE api/user/5
        public void Delete(int id) { userRepository.Delete(id); }
    }   

 

Il sera en charge  des donnés et effectuera le CRUD dans notre base de données. ASP.NET Web API est une façon simple d’exposer de données en REST.

Pour l’injection du repository, nous installons le package Nuget “Unity bootstrapper for ASP.NET Web API” et un repository de demo. Une Remarque au passage, nous avons modifié le Put pour ne pas avoir besoin de passer l’id en argument car nous le stockons dans l’Entité.

N'oublions non plus cette ligne dans le global.asax :

GlobalConfiguration.Configure(WebApiConfig.Register);

La page web Asp.Net MVC

Il nous faut ensuite une page, pour cela nous créons un contrôleur classique et une vue (qui aurait pu aussi être simplement du html). Nous  avons une première partie pour la création:

 
	<div class="form-horizontal">
            <h4>User CRUD</h4>
            <hr />
            <div class="form-group">
                @Html.LabelFor(model => model.FirstName, new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.TextBoxFor(model => model.FirstName, new { @ng_model = "Model.NewUser.FirstName" })
                </div>
            </div>
            <div class="form-group">
                @Html.LabelFor(model => model.LastName, new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.TextBoxFor(model => model.LastName, new { @ng_model = "Model.NewUser.LastName" })
                </div>
            </div>
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <button class="btn btn-default" ng-click="Save()">Save</button>
                </div>
            </div>
        </div>

Notez les @ng_model qui deviendront ng-model lors du rendu de la page ainsi que l'absence de <form>.

Une partie Liste qui permet aussi l’édition et la suppression:

 
	<table class="table">
            <tr>
                <th></th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.LastName)
                </th>
                <th></th>
            </tr>
            <tr ng-repeat="user in Model.Users">
                <td>
                    {{user.Id}}
                </td>
                <td>
                    <input ng-show="user.Edit" ng-model="user.FirstName" type="text" />
                    <span ng-hide="user.Edit">{{user.FirstName}}</span>
                </td>
                <td>
                    <input ng-show="user.Edit" ng-model="user.LastName" type="text" />
                    <span ng-hide="user.Edit"> {{user.LastName}}</span>
                </td>
                <td>
                    <button ng-hide="user.Edit" ng-click="Edit(user)" class="btn btn-default">Edit</button>
                    <button ng-show="user.Edit" ng-click="Update(user)" class="btn btn-default">Update</button>
                    <button ng-show="user.Edit" ng-click="Cancel(user)" class="btn btn-default">Cancel</button>
                    <button ng-click="Delete(user,$index)" class="btn btn-default">Delete</button>
                </td>
            </tr>
        </table>

Le tout est encapsulé dans deux “div” qui déclarent notre application et notre contrôleur AngularJS :

 
<div ng-app="UserApp">
    <div ng-controller="UserController">
.....
    </div>
</div>

L’implémentation côté client

Commençons par installer le package Nuget AngularJS. Ajoutons ensuite dans notre page l’appel au fichier .js d’Angular ainsi qu’à un fichier UserController.js. Notre application AngularJS s’appelle UserApp, nous ne sommes pas toujours obligés de lui donner un nom mais nous verrons plus loin que cela est nécessaire dans notre cas, dans notre fichier nous commençons donc par :

 
var userApp = angular.module('UserApp',[])

 

Nous devons maintenant créer notre contrôleur Angular qui est l’élément essentiel de notre code JavaScript. Il ajoute dans le $scope un objet Model qui contient une propriété “NewUser” représentant le nouvel utilisateur et “Users” contenant la liste des utilisateurs déjà créés. Il ajoute également au $scope trois méthodes, pour la création, la mise à jour et la suppression. Il charge également la liste des utilisateurs déjà créés lors de l’affichage de la page. Il définit également deux méthodes pour passer en mode édition d’une ligne et le quitter.

 

Première version

Une première version de notre contrôleur pourra donc ressembler à :

 
function UserController($scope, $http) {
    $scope.Model = {}

    //query list
    $http({
        url: '/api/user/', method: "GET",
    }).success(function (response) {
        $scope.Model.Users = response;
    });

    //Create Method
    $scope.Save = function () {
        //TODO SAVE $scope.Model.NewUser
        // AND add response to the list $scope.Model.Users.push(response);
    };

    $scope.Update = function (user) {
        //TODO Upadate
        user.Edit = false;
    };

    $scope.Delete = function (user, index) {
       //TODO Delete
       $scope.Model.Users.splice(index, 1);
    };

    $scope.Edit = function (user) {
        user.Edit = true;
    };
    $scope.Cancel = function (user) {
        user.Edit = false;
    };
};
  

L’implémentation du “save’'” pourrait être :

 
    $scope.Save = function () {
        $http({
            url: '/api/user/', method: "POST",
            data: $scope.Model.NewUser
        }).success(function (response) {
            $scope.Model.Users.push(response);
        });

    };

 

Cela fonctionne et vous trouverez dans le code source joint une implémentation de ce type dans le fichier SimpleUserController.js. Mais cette version n’est pas optimale. En effet le contrôleur a trop de responsabilité, ce n’est pas à lui de communiquer avec le serveur, il est trop couplé à $http ce qui le rend difficilement testable (Eh oui AngularJS permet aussi de faire des tests unitaires de notre code JavaScript).

Seconde version: Création d’un service

Faisons évoluer ce code. La bonne pratique est de créer un service qui s’occupera de communiquer avec le serveur, il pourrait ressembler en partie à cela:

 

userApp.factory('UserService', ['$http', function($http) {
    return {
        save : function(user){
           //TODO
        },
        update: function (user) {
            //TODO
        }
        //TODO
    };
}]);

Puis la définition de notre contrôleur devient:

 
function UserController($scope, UserService) {
    $scope.Model = {}

    $scope.Model.Users = UserService.query();
    $scope.Save = function () {
        var u = UserService.save($scope.Model.NewUser);
        $scope.Model.Users.push(u);
    };
....

 

Notez que l’injection de dépendance est intégrée à Angular.

Cette version est déjà un peu meilleure, mais elle reste fastidieuse. Et les concepteurs d’Angular ont pensé, à juste titre, que communiquer avec un serveur, pour récupérer des données et les mettre à jour, serait une tâche récurrente. Nous allons donc faire évoluer notre service.

Troisième version: utilisation de $resource

Commençons par modifier la première ligne de notre fichier pour charger le module ngResource:

 
var userApp = angular.module('UserApp', ['ngResource']);

 

Il faut aussi appeler le fichier angular-resource.js dans notre page.

$resource propose l'ensemble les méthodes de CRUD, mais l’update est réalisé avec un save(id,object) qui fait un POST alors que dans notre contrôleur Web API nous avons choisi un PUT qui ne prend que l’objet en paramètre. Nous allons donc ajouter cette méthode. Notre service devient alors:

userApp.factory('UserService', ['$resource', function ($resource) {
    return $resource('/api/user', null,
        { update: { method: 'PUT' } });
}]); 

 

et notre contrôleur:

function UserController($scope, UserService) {
    $scope.Model = {}

    $scope.Model.Users = UserService.query();
    $scope.Save = function () {
        var u = UserService.save($scope.Model.NewUser);
        $scope.Model.Users.push(u);
    };

    $scope.Update = function (user) {
        UserService.update(user);
        user.Edit = false;
    };

    $scope.Delete = function (user, index) {
        UserService.delete({ id: user.Id });
        $scope.Model.Users.splice(index, 1);
    };

    $scope.Edit = function (user) {
        user.Edit = true;
    };

    $scope.Cancel = function (user) {
        user.Edit = false;
    };
};

 

Et voilà nous avons maintenant notre CRUD avec 30 lignes de Javascript plutôt lisibles, et une implémentation de nos services REST côté serveur facile à mettre en place.

Nous avons donc vu un aperçu des possibilités d’AngularJS, avec l’utilisation de ng-model, ng-repeat, ng-show/hide, ng-click ainsi que l’utilisation de $resource. Nous n’avons eu à écrire aucun appel Ajax ni gérer aucun call back... ou presque, en fait il y en a un implicite lors de l'appel du save(). Si vous avez besoin d'un call back vous pouvez écrire quelque chose du genre:

$scope.Update = function (user) {
        UserService.update(user, function (response) {
            //call back
        });
        user.Edit = false;
    };

Vous pouvez télécharger l'exemple complet ici

Bref ça donne presque envie de faire du Javascript non? Sourire 

 

Guillaume Nurdin

Commentaires

Ajouter un commentaire



 

0 Commentaires

Guillaume Nurdin

Guillaume Nurdin


.Net Developer @ SoftFluent