对一个前端AngularJS,后端OData,ASP.NET Web API案例的理解
依然chsakell,他写了一篇前端AngularJS,后端OData,ASP.NET Web API的Demo,关于OData在ASP.NET Web API中的正删改查没有什么特别之处,但在前端调用API时,把各种调用使用$resouce封装在一个服务中的写法颇有借鉴意义。
文章:http://chsakell.com/2015/04/04/asp-net-web-api-feat-odata/
源码:https://github.com/chsakell/odatawebapi
首先是领域模型。
public class Employee { public int ID{get;set;} ... public int AddressID { get; set; } public virtual Address Address { get; set; } public int CompanyID { get; set; } public virtual Company Company { get; set; } } public class Address { public int ID{get;set;} ... } public class Company { public int ID{get;set;} .. public virtual List<Employee> Employees{get;set;} public Compay() { Employees = new List<Employee>(); } }
使用EF Fuent API对领域进行配置,继承EntityTypeConfiguration<T>,比如:
public class CompanyConfiguration: EntityTypeConfiguration<Company> { }
上下文继承DbContext。
public class EntitiesContext : DbContext { }
种子数据继承DropCreateDatabaseIfModelChanges.
public class EntitiesInitializer : DropCreateDatabaseIfModelChanges<EntitiesContext> { }
配置项目连接字符串。
<connectionStrings> <add name="EntitiesContext" providerName="System.Data.SqlClient" connectionString="Server=(localdb)\v11.0; Database=CompanyDB; Trusted_Connection=true; MultipleActiveResultSets=true" /> </connectionStrings>
在项目全局文件中启用种子数据的配置。
protected void Application_Start() { GlobalConfiguration.Configure(WebApiConfig.Register); // Init the database Database.SetInitializer(new EntitiesInitializer()); }
在NuGet中输入odata,安装V4.0版本。
关于ODataController的增删改查,在"ASP.NET Web API基于OData的增删改查,以及处理实体间关系"比较详细的描述,这里略去,把重点放在前端的调用上。
先来看界面:
这里有个主视图,如下:
<html ng-app="mainApp"> <head> <link href="Content/styles/toastr.css" rel="stylesheet" /> <link href="Content/styles/loading-bar.css" rel="stylesheet" /> <script src="Content/scripts/jquery-2.1.1.js"></script> <script src="Content/scripts/bootstrap.js"></script> <script src="Content/scripts/angular.js"></script> <script src="Content/scripts/angular-resource.js"></script> <script src="Content/scripts/toastr.js"></script> <script src="Content/scripts/loading-bar.js"></script> <script src="Content/scripts/main.js"></script> <script src="app/services.js"></script> <script src="app/controllers.js"></script> </head> <body ng-controller="appCtrl" ng-init="getTop10Employees()"> <tbody ng-repeat="emp in employees"> <tr ng-click="setEmployee(emp)"> <td>{{emp.ID}}</td> <td>{{emp.FirstName}}</td> <td>{{emp.Surname}}</td> <td>{{emp.Email}}</td> </tr> </tbody> <!--更新或删除--> <form> <input type="text" id="id" ng-model="currentEmployee.ID" disabled> <input type="text" id="firstName" ng-model="currentEmployee.FirstName"> <input type="text"id="surname" ng-model="currentEmployee.Surname"> <input type="email" id="inputEmail" ng-model="currentEmployee.Email"> <input type="text" id="city" ng-model="currentEmployee.City" disabled> <input type="text" id="country" ng-model="currentEmployee.Country" disabled> <input type="text" id="state" ng-model="currentEmployee.State" disabled> <input type="text" id="company" ng-model="currentEmployee.Company" disabled> <button type="button" ng-click="updateEmployee()">Update</button> <button type="button" ng-click="deleteEmployee()">Delete</button> </form> <!--添加--> <form role="form"> <input type="text" name="firstname" ng-model="newEmployee.FirstName" /> <input type="text" name="surname" ng-model="newEmployee.Surname" /> <input type="text" name="email" ng-model="newEmployee.Email" /> <button type="button" ng-click="addEmployee()">Add</button> </form> <script type="text/javascript"> $(function () { toastr.options = { "positionClass": "toast-bottom-right", "preventDuplicates": true, "progressBar": true, "timeOut": "3000", } }); </script> </body> </html>
一般来说,前端针对某个领域的操作有多个,chsakell的一种写法特别值得推荐,那就是把针对某个领域的操作,在AngularJS中,用$resource封装到一个服务中去。如下:
angular.module('mainApp') .factory('employeeService', function ($resource) { var odataUrl = '/odata/Employees'; return $resource('', {}, { 'getAll': { method: 'GET', url: odataUrl }, 'getTop10': { method: 'GET', url: odataUrl + '?$top=10' }, 'create': { method: 'POST', url: odataUrl }, 'patch': { method: 'PATCH', params: { key: '@key' }, url: odataUrl + '(:key)' }, 'getEmployee': { method: 'GET', params: { key: '@key' }, url: odataUrl + '(:key)' }, 'getEmployeeAdderss': { method: 'GET', params: { key: '@key' }, url: odataUrl + '(:key)' + '/Address' }, 'getEmployeeCompany': { method: 'GET', params: { key: '@key' }, url: odataUrl + '(:key)' + '/Company' }, 'deleteEmployee': { method: 'DELETE', params: { key: '@key' }, url: odataUrl + '(:key)' }, 'addEmployee': { method: 'POST', url: odataUrl } }); }).factory('notificationFactory', function () { return { success: function (text) { toastr.success(text, "Success"); }, error: function (text) { toastr.error(text, "Error"); } }; })
然后针对Employee,在mainApp中增减一个controller用来针对Employee的各种操作。
angular.module('mainApp') .controller('appCtrl', function ($scope, employeeService, notificationFactory) { //存储当前用户 $scope.currentEmployee = {}; // Get Top 10 Employees $scope.getTop10Employees = function () { (new employeeService()).$getTop10() .then(function (data) { //存储所有用户 $scope.employees = data.value; $scope.currentEmployee = $scope.employees[0]; //相当于设置Empoyee的导航属性 $scope.setCurrentEmployeeAddress(); $scope.setCurrentEmployeeCompany(); //通知 notificationFactory.success('Employeess loaded.'); }); }; // Set active employee for patch update $scope.setEmployee = function (employee) { $scope.currentEmployee = employee; $scope.setCurrentEmployeeAddress(); $scope.setCurrentEmployeeCompany(); }; //设置当前Employee的地址 $scope.setCurrentEmployeeAddress = function () { //获取当前Employee var currentEmployee = $scope.currentEmployee; return (new employeeService({ "ID": currentEmployee.ID, })).$getEmployeeAdderss({ key: currentEmployee.ID }) .then(function (data) { $scope.currentEmployee.City = data.City; $scope.currentEmployee.Country = data.Country; $scope.currentEmployee.State = data.State; }); } //设置当前Employee的Company $scope.setCurrentEmployeeCompany = function () { var currentEmployee = $scope.currentEmployee; return (new employeeService({ "ID": currentEmployee.ID, })).$getEmployeeCompany({ key: currentEmployee.ID }) .then(function (data) { $scope.currentEmployee.Company = data.Name; }); } // Update Selected Employee $scope.updateEmployee = function () { var currentEmployee = $scope.currentEmployee; console.log(currentEmployee.Email); if (currentEmployee) { return (new employeeService({ "ID": currentEmployee.ID, "FirstName": currentEmployee.FirstName, "Surname": currentEmployee.Surname, "Email": currentEmployee.Email })).$patch({ key: currentEmployee.ID }) .then(function (data) { notificationFactory.success('Employee with ID ' + currentEmployee.ID + ' updated.') }); } } $scope.deleteEmployee = function () { var currentEmployee = $scope.currentEmployee; return (new employeeService({ "ID": currentEmployee.ID, })).$deleteEmployee({ key: currentEmployee.ID }) .then(function (data) { notificationFactory.success('Employee with ID ' + currentEmployee.ID + ' removed.'); $scope.getTop10Employees(); }); } $scope.addEmployee = function () { var newEmployee = $scope.newEmployee; return (new employeeService({ "FirstName": newEmployee.FirstName, "Surname": newEmployee.Surname, "Email": newEmployee.Email, "AddressID": 1, // normally obtained from UI "CompanyID": 3 // normally obtained from UI })).$addEmployee() .then(function (data) { notificationFactory.success('Employee ' + newEmployee.FirstName + ' ' + newEmployee.Surname + ' added successfully'); $scope.newEmployee = {}; }); } });