It has been almost a year that I have not posted anything to the blog so thanks to those life coach blog articles about “how to overcome laziness”! I have read bunch of them and then decided to write this out finally.
Anyway lets talk about web api 2, first of all this project was a weekend project and It is a very good example in order to learn web api 2 and basics of angular.
What does that project do? Basically I tried to create a micro accounting app has an stock support where the app user can perform Create/Read/Update/Delete (CRUD) operations for suppliers, products and link products to suppliers. After suppliers and products are created, app user can perform CRUD operations for purchase orders and link it with the suppliers but on the other hand there are customers which the app user can create sales orders for them. So stock will be the difference of those purchase and sales orders for the products.
On the back-end since it is a .net MVC project;
My models are
- Supplier
- Product
- PurchaseOrder
- PurchaseOrderLine
- User
- SalesOrder
- SalesOrderLine
I think the name of models are very clear. After creating the models I also tried to implement Repository Pattern for this project so I will not need to deal with different database types. There is good explanation at MSDN about the repository pattern :
” For example, if a client calls a catalog repository to retrieve some product data, it only needs to use the catalog repository interface. For example, the client does not need to know if the product information is retrieved with SQL queries to a database or Collaborative Application Markup Language (CAML) queries to a SharePoint list. Isolating these types of dependencies provides flexibility to evolve implementations. “
I did not want to engage with database in this project so I used List data type for seeding some test data into my project. You can see them in the TestDataHelper.cs file.
For the Repository pattern first of all I have created an interface that describes what my SupplierRepository class will do:
1 2 3 4 5 6 7 8 9 10 11 12 |
public interface ISupplierRepository { IEnumerable AllSuppliers(); Supplier GetById(int Id); void Add(Supplier supplier); void Update(Supplier supplier); bool Remove(int Id); } |
And then I implemented that Repository interface in the actual SupplierRepository class. As you will see below I load the table content in the constructor and do all the dirty stuff here for the data layer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public class SupplierRepository : ISupplierRepository { private List _supplierTable; public SupplierRepository() { _supplierTable = TestDataHelper.GetMySuppliers(); } public void Add(Supplier supplier) { supplier.Id = _supplierTable.LastOrDefault().Id + 1; _supplierTable.Add(supplier); } public IEnumerable AllSuppliers() { return _supplierTable; } public Supplier GetById(int Id) { var supplier = (from s in _supplierTable where s.Id == Id select s).SingleOrDefault(); return supplier; } public bool Remove(int Id) { var supplier = GetById(Id); if (supplier != null) _supplierTable.Remove(supplier); return GetById(Id) == null; } public void Update(Supplier supplier) { var oldSupplier = GetById(supplier.Id); oldSupplier.Name = supplier.Name; oldSupplier.Products = supplier.Products; } } |
After doing so it comes to the controller. Under the controller folder there is only HomeController which extends the actual Controller class because I used that homecontroller for only calling appropriate view in the front-end but of course that could have done with a better way with separating each controller on its own.
The other controllers are only extends the APIController which lets us handle HTTP calls and create the logic. On the controller layer we have nothing left to do with domain model layer. It is time to create our app’s logic. Controllers initialize a SupplierRepository instance as a private variable in order to has the abilities of its repository class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
public class SupplierController : ApiController { private static readonly ISupplierRepository _supplierRepository = new SupplierRepository(); public IEnumerable<Supplier> Get() { var suppliers = _supplierRepository.AllSuppliers(); return suppliers; } [HttpGet] public IHttpActionResult Get(string Id) { var supplier = _supplierRepository.GetById(Convert.ToInt32(Id)); if (supplier == null) { var resp = new HttpResponseMessage(HttpStatusCode.NotFound) { Content = new StringContent(string.Format("No supplier with ID = {0}", Id)), ReasonPhrase = "Supplier ID Not Found" }; throw new System.Web.Http.HttpResponseException(resp); } return Ok(supplier); } [HttpPost] public void Post(Supplier supplier) { _supplierRepository.Add(supplier); } [HttpPut] public void Put(Supplier supplier) { _supplierRepository.Update(supplier); } public void Delete(string Id) { if (!_supplierRepository.Remove(Convert.ToInt32(Id))) { var resp = new HttpResponseMessage(HttpStatusCode.NotFound) { Content = new StringContent(string.Format("No supplier with ID = {0}", Id)), ReasonPhrase = "Supplier ID Not Found" }; throw new HttpResponseException(resp); } } } |
Well I think it is enough for the back-end lets talk about how things go on the front-end.
I used Bootstrap for the designing the interface and AngularJs 1.5.5 for performing CRUD operations on the API. It is easy to install these packages with nuGet. You can install them with
1 2 |
PM> Install-Package bootstrap PM> Install-Package AngularJS.Core |
As I mentioned above in the HomeController I send all the actions to related view. So you can see my View files under the ~/Views/Home directory. On the index you will see some buttons with the api’s endpoints you can reach the raw json data from here. I did not setup any authentication method because this is test project but you can reach a good tutorial of securing the web api from here you can also find a lot of tutorial on the google about securing the web api.
So lets continue with the angularJS you can see my angularJS controllers under ~/scripts/app folder. I have created the module, controller and the factory to use http service in angular. As you can see below we must initialize the Angular on the page with ng-app and then we need to specify our angular controller in the ng-controller attribute and then we can use angular freely in that code blocks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
@section Scripts{ <script src="~/scripts/app/SupplierClient.js"></script> } <div class="container" ng-app="MyApp"> <div ng-controller="SupplierController"> <h2>{{pageTitle}}</h2> <table class="table table-bordered table-striped table-responsive"> <tr> <th>ID</th> <th>Name</th> <th> </th> </tr> <tr ng-repeat="supp in suppliers"> <td>{{supp.Id}}</td> <td>{{supp.Name}}</td> <td> <a class="text-primary" href="#" ng-click="edit(supp.Id)"><i class="glyphicon glyphicon-edit"></i> Edit</a> <a class="text-danger" href="#" ng-click="delete(supp.Id)"><i class="glyphicon glyphicon-remove"></i> Delete</a> </td> </tr> </table> <h2>{{formTitle}}</h2> <form name="supplierForm" ng-submit="submitForm(Supplier)"> <div class="row"> <div class="form-group col-md-4"> <label for="ID">ID</label> <input type="text" id="ID" class="form-control" placeholder="Supplier ID" ng-model="Supplier.Id" disabled> </div> </div> <div class="row"> <div class="form-group col-md-4"> <label for="Name">Name</label> <input type="text" id="Name" class="form-control" placeholder="Supplier Name" ng-model="Supplier.Name"> </div> </div> <button type="submit" class="btn btn-default">Add/Update</button> <button type="reset" class="btn btn-default">Clear</button> </form> </div> </div> |
AngularJS is very easy to write and understand. Its modular structure makes you write good code. Let’s see some example for the suppliers page controller for example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
var MyModule = angular.module('MyApp', []) MyModule.controller('SupplierController', ['$scope','SupplierService', function ($scope, SupplierService) { $scope.pageTitle = "Supplier List"; $scope.formTitle = "Supplier Form"; getAll(); function getAll() { SupplierService.getAll() .success(function (supps) { $scope.suppliers = supps; }) .error(function (error) { $scope.status = 'Unable to load data: ' + error.message; console.log($scope.status); }); } $scope.edit = function getSingle(Id) { SupplierService.getSingle(Id) .success(function (supp) { $scope.Supplier = {}; $scope.Supplier.Id = supp.Id; $scope.Supplier.Name = supp.Name; }) .error(function (error) { $scope.status = 'Unable to load data: ' + error.message; console.log($scope.status); }); } $scope.delete = function deleteRecord(Id) { SupplierService.delete(Id) .success(function () { alert("Deleted successfully!!"); getAll(); }) .error(function (error) { $scope.status = 'Unable to load data: ' + error.message; console.log($scope.status); }); } $scope.submitForm = function submitForm(Supplier) { if (!Supplier.Id) { SupplierService.add(Supplier) .success(function () { alert("Added successfully!!"); getAll(); resetForm(); }) .error(function (error) { $scope.status = 'Unable to load data: ' + error.message; console.log($scope.status); }); } else { SupplierService.update(Supplier) .success(function () { alert("Updated successfully!!"); getAll(); resetForm(); }) .error(function (error) { $scope.status = 'Unable to load data: ' + error.message; console.log($scope.status); }); } } function resetForm() { $scope.Supplier = {}; $scope.Supplier.Id = ''; $scope.Supplier.Name = ''; } }]); MyModule.factory('SupplierService', ['$http', function ($http) { var SupplierService = {}; SupplierService.getAll = function () { return $http.get('/api/Supplier'); } SupplierService.getSingle = function (Id) { return $http.get('/api/Supplier/' + Id); } SupplierService.add = function (Supplier) { return $http.post('/api/Supplier', Supplier); } SupplierService.update = function (Supplier) { return $http.put('/api/Supplier', Supplier); } SupplierService.delete = function (Id) { return $http.delete('/api/Supplier/' + Id); } return SupplierService; }]); |
On the controller side $scope represents our view’s attached page. We can add functions into scope to use for the actions. On the factory layer we isolated the api call’s from the logic. It uses the $http service of AngularJS in order to making calls to api back-end. You can find detailed AngularJS service here.
If you want to check out the code and run the application then you can find the code here on github. Feel free to contribute.
https://github.com/unicod3/WebApiExcercise
I also deployed the project on the free tier of azure server. I do not know how long it will last but the link to reach the project is below :
http://webapiexcercise.azurewebsites.net/