一、引言
本文將介紹如何把AngularJs應用到實際項目中。本篇文章將使用AngularJS來打造一個簡易的許可權管理系統。下面不多說,直接進入主題。
二、整體架構設計介紹
首先看下整個項目的架構設計圖:
從上圖可以看出整個項目的一個整體結構,接下來,我來詳細介紹了項目的整體架構:
採用Asp.net Web API來實現REST 服務。這樣的實現方式,已達到後端服務的公用、分別部署和更好地擴充。Web層依賴應用服務介面,並且使用Castle Windsor實現依賴注入。
顯示層(使用者UI)
顯示層採用了AngularJS來實現的SPA頁面。所有的頁面資料都是非同步載入和局部重新整理,這樣的實現將會有更好的使用者體驗。
應用程式層(Application Service)
AngularJS通過Http服務去請求Web API來獲得資料,而Web API的實現則是調用應用程式層來請求資料。
基礎架構層
基礎架構層包括倉儲的實現和一些公用方法的實現。
倉儲層的實現採用EF Code First的方式來實現的,並使用EF Migration的方式來建立資料庫和更新資料庫。
LH.Common層實現了一些公用的方法,如日誌協助類、運算式樹狀架構擴充等類的實現。
領域層
領域層主要實現了該項目的所有領域模型,其中包括領域模型的實現和倉儲介面的定義。
介紹完整體結構外,接下來將分別介紹該項目的後端服務實現和Web前端的實現。
三、後端服務實現
後端服務主要採用Asp.net Web API來實現後端服務,並且採用Castle Windsor來完成依賴注入。
這裡拿許可權管理中的使用者管理來介紹Rest Web API服務的實現。
提供使用者資料的REST服務的實現:
public class UserController : ApiController { private readonly IUserService _userService; public UserController(IUserService userService) { _userService = userService; } [HttpGet] [Route("api/user/GetUsers")] public OutputBase GetUsers([FromUri]PageInput input) { return _userService.GetUsers(input); } [HttpGet] [Route("api/user/UserInfo")] public OutputBase GetUserInfo(int id) { return _userService.GetUser(id); } [HttpPost] [Route("api/user/AddUser")] public OutputBase CreateUser([FromBody] UserDto userDto) { return _userService.AddUser(userDto); } [HttpPost] [Route("api/user/UpdateUser")] public OutputBase UpdateUser([FromBody] UserDto userDto) { return _userService.UpdateUser(userDto); } [HttpPost] [Route("api/user/UpdateRoles")] public OutputBase UpdateRoles([FromBody] UserDto userDto) { return _userService.UpdateRoles(userDto); } [HttpPost] [Route("api/user/DeleteUser/{id}")] public OutputBase DeleteUser(int id) { return _userService.DeleteUser(id); } [HttpPost] [Route("api/user/DeleteRole/{id}/{roleId}")] public OutputBase DeleteRole(int id, int roleId) { return _userService.DeleteRole(id, roleId); } }
從上面代碼實現可以看出,User REST 服務依賴與IUserService介面,並且也沒有像傳統的方式將所有的商務邏輯放在Web API實現中,而是將具體的一些業務實現封裝到對應的應用程式層中,Rest API只負責調用對應的應用程式層中的服務。這樣設計好處有:
REST 服務部依賴與應用程式層介面,使得職責分離,將應用程式層服務的執行個體化交給單獨的依賴注入容器去完成,而REST服務只負責調用對應應用服務的方法來擷取資料。採用依賴介面而不依賴與具體類的實現,使得類與類之間低耦合。REST服務內不包括具體的商務邏輯實現。這樣的設計可以使得服務更好地分離,如果你後期想用WCF來實現REST服務的,這樣就不需要重複在WCF的REST服務類中重複寫一篇Web API中的邏輯了,這時候完全可以調用應用服務的介面方法來實現WCF REST服務。所以將商務邏輯實現抽到應用服務層去實現,這樣的設計將使得REST 服務職責更加單一,REST服務實現更容易擴充。
使用者應用服務的實現:
public class UserService : BaseService, IUserService { private readonly IUserRepository _userRepository; private readonly IUserRoleRepository _userRoleRepository; public UserService(IUserRepository userRepository, IUserRoleRepository userRoleRepository) { _userRepository = userRepository; _userRoleRepository = userRoleRepository; } public GetResults<UserDto> GetUsers(PageInput input) { var result = GetDefault<GetResults<UserDto>>(); var filterExp = BuildExpression(input); var query = _userRepository.Find(filterExp, user => user.Id, SortOrder.Descending, input.Current, input.Size); result.Total = _userRepository.Find(filterExp).Count(); result.Data = query.Select(user => new UserDto() { Id = user.Id, CreateTime = user.CreationTime, Email = user.Email, State = user.State, Name = user.Name, RealName = user.RealName, Password = "*******", Roles = user.UserRoles.Take(4).Select(z => new BaseEntityDto() { Id = z.Role.Id, Name = z.Role.RoleName }).ToList(), TotalRole = user.UserRoles.Count() }).ToList(); return result; } public UpdateResult UpdateUser(UserDto user) { var result = GetDefault<UpdateResult>(); var existUser = _userRepository.FindSingle(u => u.Id == user.Id); if (existUser == null) { result.Message = "USER_NOT_EXIST"; result.StateCode = 0x00303; return result; } if (IsHasSameName(existUser.Name, existUser.Id)) { result.Message = "USER_NAME_HAS_EXIST"; result.StateCode = 0x00302; return result; } existUser.RealName = user.RealName; existUser.Name = user.Name; existUser.State = user.State; existUser.Email = user.Email; _userRepository.Update(existUser); _userRepository.Commit(); result.IsSaved = true; return result; } public CreateResult<int> AddUser(UserDto userDto) { var result = GetDefault<CreateResult<int>>(); if (IsHasSameName(userDto.Name, userDto.Id)) { result.Message = "USER_NAME_HAS_EXIST"; result.StateCode = 0x00302; return result; } var user = new User() { CreationTime = DateTime.Now, Password = "", Email = userDto.Email, State = userDto.State, RealName = userDto.RealName, Name = userDto.Name }; _userRepository.Add(user); _userRepository.Commit(); result.Id = user.Id; result.IsCreated = true; return result; } public DeleteResult DeleteUser(int userId) { var result = GetDefault<DeleteResult>(); var user = _userRepository.FindSingle(x => x.Id == userId); if (user != null) { _userRepository.Delete(user); _userRepository.Commit(); } result.IsDeleted = true; return result; } public UpdateResult UpdatePwd(UserDto user) { var result = GetDefault<UpdateResult>(); var userEntity =_userRepository.FindSingle(x => x.Id == user.Id); if (userEntity == null) { result.Message = string.Format("當前編輯的使用者“{0}”已經不存在", user.Name); return result; } userEntity.Password = user.Password; _userRepository.Commit(); result.IsSaved = true; return result; } public GetResult<UserDto> GetUser(int userId) { var result = GetDefault<GetResult<UserDto>>(); var model = _userRepository.FindSingle(x => x.Id == userId); if (model == null) { result.Message = "USE_NOT_EXIST"; result.StateCode = 0x00402; return result; } result.Data = new UserDto() { CreateTime = model.CreationTime, Email = model.Email, Id = model.Id, RealName = model.RealName, State = model.State, Name = model.Name, Password = "*******" }; return result; } public UpdateResult UpdateRoles(UserDto user) { var result = GetDefault<UpdateResult>(); var model = _userRepository.FindSingle(x => x.Id == user.Id); if (model == null) { result.Message = "USE_NOT_EXIST"; result.StateCode = 0x00402; return result; } var list = model.UserRoles.ToList(); if (user.Roles != null) { foreach (var item in user.Roles) { if (!list.Exists(x => x.Role.Id == item.Id)) { _userRoleRepository.Add(new UserRole { RoleId = item.Id, UserId = model.Id }); } } foreach (var item in list) { if (!user.Roles.Exists(x => x.Id == item.Id)) { _userRoleRepository.Delete(item); } } _userRoleRepository.Commit(); _userRepository.Commit(); } result.IsSaved = true; return result; } public DeleteResult DeleteRole(int userId, int roleId) { var result = GetDefault<DeleteResult>(); var model = _userRoleRepository.FindSingle(x => x.UserId == userId && x.RoleId == roleId); if (model != null) { _userRoleRepository.Delete(model); _userRoleRepository.Commit(); } result.IsDeleted = true; return result; } public bool Exist(string username, string password) { return _userRepository.FindSingle(u => u.Name == username && u.Password == password) != null; } private bool IsHasSameName(string name, int userId) { return !string.IsNullOrWhiteSpace(name) && _userRepository.Find(u=>u.Name ==name && u.Id != userId).Any(); } private Expression<Func<User, bool>> BuildExpression(PageInput pageInput) { Expression<Func<User, bool>> filterExp = user => true; if (string.IsNullOrWhiteSpace(pageInput.Name)) return filterExp; switch (pageInput.Type) { case 0: filterExp = user => user.Name.Contains(pageInput.Name) || user.Email.Contains(pageInput.Name); break; case 1: filterExp = user => user.Name.Contains(pageInput.Name); break; case 2: filterExp = user => user.Email.Contains(pageInput.Name); break; } return filterExp; } }
這裡應用服務層其實還可以進一步的最佳化,實現代碼層級的讀寫分離,定義IReadOnlyService介面和IWriteServie介面,並且把寫操作可以採用泛型方法的方式抽象到BaseService中去實現。這樣一些增刪改操作實現公用,之所以可以將這裡操作實現公用,是因為這些操作都是非常類似的,無非是操作的實體不一樣罷了。其實這樣的實現在我另一個開源項目中已經用到:OnlineStore.大家可以參考這個自行去實現。
倉儲層的實現:
使用者應用服務也沒有直接依賴與具體的倉儲類,同樣也是依賴其介面。對應的使用者倉儲類的實現如下:
public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity :class , IEntity { private readonly ThreadLocal<UserManagerDBContext> _localCtx = new ThreadLocal<UserManagerDBContext>(() => new UserManagerDBContext()); public UserManagerDBContext DbContext { get { return _localCtx.Value; } } public TEntity FindSingle(Expression<Func<TEntity, bool>> exp = null) { return DbContext.Set<TEntity>().AsNoTracking().FirstOrDefault(exp); } public IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> exp = null) { return Filter(exp); } public IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, dynamic>> sortPredicate, SortOrder sortOrder, int pageNumber, int pageSize) { if (pageNumber <= 0) throw new ArgumentOutOfRangeException("pageNumber", pageNumber, "pageNumber must great than or equal to 1."); if (pageSize <= 0) throw new ArgumentOutOfRangeException("pageSize", pageSize, "pageSize must great than or equal to 1."); var query = DbContext.Set<TEntity>().Where(expression); var skip = (pageNumber - 1) * pageSize; var take = pageSize; if (sortPredicate == null) throw new InvalidOperationException("Based on the paging query must specify sorting fields and sort order."); switch (sortOrder) { case SortOrder.Ascending: var pagedAscending = query.SortBy(sortPredicate).Skip(skip).Take(take); return pagedAscending; case SortOrder.Descending: var pagedDescending = query.SortByDescending(sortPredicate).Skip(skip).Take(take); return pagedDescending; } throw new InvalidOperationException("Based on the paging query must specify sorting fields and sort order."); } public int GetCount(Expression<Func<TEntity, bool>> exp = null) { return Filter(exp).Count(); } public void Add(TEntity entity) { DbContext.Set<TEntity>().Add(entity); } public void Update(TEntity entity) { DbContext.Entry(entity).State = EntityState.Modified; } public void Delete(TEntity entity) { DbContext.Entry(entity).State = EntityState.Deleted; DbContext.Set<TEntity>().Remove(entity); } public void Delete(ICollection<TEntity> entityCollection) { if(entityCollection.Count ==0) return; DbContext.Set<TEntity>().Attach(entityCollection.First()); DbContext.Set<TEntity>().RemoveRange(entityCollection); } private IQueryable<TEntity> Filter(Expression<Func<TEntity, bool>> exp) { var dbSet = DbContext.Set<TEntity>().AsQueryable(); if (exp != null) dbSet = dbSet.Where(exp); return dbSet; } public void Commit() { DbContext.SaveChanges(); } }public class UserRepository :BaseRepository<User>, IUserRepository { }
四、AngularJS前端實現
Web前端的實現就是採用AngularJS來實現,並且採用模組化開發模式。具體Web前端的代碼結構如下圖所示:
App/images // 存放Web前端使用的圖片資源App/Styles // 存放樣式檔案App/scripts // 整個Web前端用到的指令檔 / Controllers // angularJS控制器模組存放目錄 / directives // angularJs指令模組存放目錄 / filters // 過濾器模組存放目錄 / services // 服務模組存放目錄 / app.js // Web前端程式配置模組(路由配置)App/Modules // 項目依賴庫,angular、Bootstrap、Jquery庫App/Views // AngularJs視圖模板存放目錄
使用AngularJS開發的Web應用程式的代碼之間的調用層次和後端基本一致,也是視圖頁面——》控制器模組——》服務模組——》Web API服務。
並且Web前端CSS和JS資源的載入採用了Bundle的方式來減少請求資源的次數,從而加快頁面載入時間。具體Bundle類的配置:
public class BundleConfig { // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862 public static void RegisterBundles(BundleCollection bundles) { //類庫依賴檔案 bundles.Add(new ScriptBundle("~/js/base/lib").Include( "~/app/modules/jquery-1.11.2.min.js", "~/app/modules/angular/angular.min.js", "~/app/modules/angular/angular-route.min.js", "~/app/modules/bootstrap/js/ui-bootstrap-tpls-0.13.0.min.js", "~/app/modules/bootstrap-notify/bootstrap-notify.min.js" )); //angularjs 專案檔 bundles.Add(new ScriptBundle("~/js/angularjs/app").Include( "~/app/scripts/services/*.js", "~/app/scripts/controllers/*.js", "~/app/scripts/directives/*.js", "~/app/scripts/filters/*.js", "~/app/scripts/app.js")); //樣式 bundles.Add(new StyleBundle("~/js/base/style").Include( "~/app/modules/bootstrap/css/bootstrap.min.css", "~/app/styles/dashboard.css", "~/app/styles/console.css" )); } }
首頁 Index.cshtml
<!DOCTYPE html><html ng-app="LH"><head> <meta name="viewport" content="width=device-width" /> <title>簡易許可權管理系統Demo</title> @Styles.Render("~/js/base/style") @Scripts.Render("~/js/base/lib")</head><body ng-controller="navigation"> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/">簡易許可權管理系統Demo</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-left"> <li class="{{item.isActive?'active':''}}" ng-repeat="item in ls"> <a href="#{{item.urls[0].link}}">{{item.name}}</a> </li> </ul> <div class="navbar-form navbar-right"> <a href="@Url.Action("UnLogin", "Home", null)" class="btn btn-danger"> {{lang.exit}} </a> </div> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="{{item.isActive?'active':''}}" ng-repeat="item in urls"><a href="#{{item.link}}">{{item.title}}</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div ng-view></div> </div> </div> </div> @Scripts.Render("~/js/angularjs/app")</body></html>
五、運行效果
介紹完前後端的實現之後,接下來讓我們看下整個項目的運行效果:
六、總結
到此,本文的所有內容都介紹完了,儘管本文的AngularJS的應用項目還有很多完善的地方,例如沒有緩衝的支援、沒有實現讀寫分離,沒有對一些API進行壓力測試等。但AngularJS在實際項目中的應用基本是這樣的,大家如果在項目中有需要用到AngularJS,正好你們公司的後台又是.NET的話,相信本文的分享可以是一個很好的參考。另外,關於架構的設計也可以參考我的另一個開源項目:OnlineStore和FastWorks。
以上所述是小編給大家介紹的使用AngularJs打造許可權管理系統的方法,希望對大家有所協助!