一、引言
本文將介紹如何把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打造權限管理系統的方法,希望對大家有幫助!