1. Einleitung
In diesem Artikel erfahren Sie, wie Sie AngularJs auf tatsächliche Projekte anwenden. In diesem Artikel wird AngularJS verwendet, um ein einfaches Berechtigungsverwaltungssystem zu erstellen. Im Folgenden gibt es nicht viel zu sagen, kommen wir direkt zum Thema.
2. Einführung in das Gesamtarchitekturdesign
Schauen Sie sich zunächst die architektonische Entwurfszeichnung des gesamten Projekts an:
Auf dem Bild oben können Sie die Gesamtstruktur des gesamten Projekts sehen. Als nächstes werde ich die Gesamtstruktur des Projekts im Detail vorstellen:
Verwenden Sie die Asp.net-Web-API, um REST-Dienste zu implementieren. Durch diese Implementierungsmethode wurde eine gemeinsame, separate Bereitstellung und eine bessere Erweiterung der Back-End-Dienste erreicht. Die Webschicht basiert auf der Anwendungsdienstschnittstelle und verwendet Castle Windsor, um die Abhängigkeitsinjektion zu implementieren.
Anzeigeebene (Benutzeroberfläche)
Die Anzeigeebene verwendet die von AngularJS implementierte SPA-Seite. Alle Seitendaten werden asynchron geladen und teilweise aktualisiert. Diese Implementierung bietet eine bessere Benutzererfahrung.
Bewerbungsservice
AngularJS fordert die Web-API über den HTTP-Dienst an, um Daten abzurufen, und die Implementierung der Web-API ruft die Anwendungsschicht auf, um Daten anzufordern.
Infrastrukturschicht
Die Infrastrukturschicht umfasst die Implementierung der Lagerhaltung und die Implementierung einiger öffentlicher Methoden.
Die Warehouse-Schicht wird mit EF Code First implementiert und die Methode EF Migration wird zum Erstellen und Aktualisieren der Datenbank verwendet.
Die LH.Common-Schicht implementiert einige gängige Methoden, z. B. die Implementierung von Protokollhilfeklassen, Ausdrucksbaumerweiterungen und anderen Klassen.
Domänenschicht
Die Domänenschicht implementiert hauptsächlich alle Domänenmodelle des Projekts, einschließlich der Implementierung von Domänenmodellen und der Definition von Lagerschnittstellen.
Zusätzlich zur Einführung der vollständigen Struktur werden die Back-End-Service-Implementierung und die Web-Front-End-Implementierung des Projekts separat vorgestellt.
3. Back-End-Service-Implementierung
Back-End-Dienste verwenden hauptsächlich die Asp.net-Web-API, um Back-End-Dienste zu implementieren, und Castle Windsor wird verwendet, um die Abhängigkeitsinjektion abzuschließen.
Hier nehmen wir die Benutzerverwaltung in die Berechtigungsverwaltung ein, um die Implementierung des Rest Web API-Dienstes vorzustellen.
Implementierung eines REST-Dienstes, der Benutzerdaten bereitstellt:
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); } }
Wie aus der obigen Code-Implementierung ersichtlich ist, basiert der Benutzer-REST-Dienst auf der IUserService-Schnittstelle und fügt nicht auf herkömmliche Weise die gesamte Geschäftslogik in die Web-API-Implementierung ein, sondern kapselt einige spezifische Geschäftsimplementierungen in das entsprechende In In der Anwendungsschicht ist die Rest-API nur für den Aufruf von Diensten in der entsprechenden Anwendungsschicht verantwortlich. Die Vorteile dieses Designs sind:
Die REST-Dienstabteilung verlässt sich auf die Schnittstelle der Anwendungsschicht, die die Verantwortlichkeiten trennt und die Instanziierung der Dienste der Anwendungsschicht einem separaten Abhängigkeitsinjektionscontainer überlässt, während der REST-Dienst nur für den Aufruf der Methoden der entsprechenden Anwendungsdienste verantwortlich ist Daten beschaffen. Die Verwendung abhängiger Schnittstellen anstelle von Implementierungen bestimmter Klassen führt zu einer geringen Kopplung zwischen Klassen. Der REST-Dienst umfasst keine spezifische Implementierung der Geschäftslogik. Durch diese Art des Designs können Dienste besser getrennt werden. Wenn Sie später WCF zum Implementieren von REST-Diensten verwenden möchten, müssen Sie die Logik nicht wiederholt in der REST-Dienstklasse von WCF schreiben des Anwendungsdienstes zur Implementierung des WCF-REST-Dienstes. Daher wird die Geschäftslogikimplementierung in die Anwendungsdienstschicht extrahiert. Durch dieses Design wird die REST-Dienstverantwortung einheitlicher und die REST-Dienstimplementierung einfacher zu erweitern.
Implementierung von Benutzeranwendungsdiensten:
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; } }
Die Anwendungsdienstschicht kann hier tatsächlich weiter optimiert werden, um die Trennung von Lesen und Schreiben auf Codeebene zu realisieren, die IReadOnlyService-Schnittstelle und die IWriteServie-Schnittstelle zu definieren und den Schreibvorgang mithilfe einer generischen Methode in BaseService zu abstrahieren. Solche Vorgänge zum Hinzufügen, Löschen und Ändern werden gemeinsam genutzt. Der Grund, warum diese Vorgänge gemeinsam genutzt werden können, liegt darin, dass diese Vorgänge sehr ähnlich sind, die Entitäten der Vorgänge jedoch unterschiedlich sind. Tatsächlich wurde diese Art der Implementierung in einem anderen Open-Source-Projekt von mir verwendet: OnlineStore Sie können darauf zurückgreifen, um es selbst zu implementieren.
Implementierung der Lagerschicht:
Benutzeranwendungsdienste hängen nicht direkt von bestimmten Speicherklassen ab, sondern auch von deren Schnittstellen. Die Implementierung der entsprechenden Benutzerspeicherklasse lautet wie folgt:
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 { }
4. AngularJS-Frontend-Implementierung
Das Web-Frontend wird mit AngularJS implementiert und verwendet ein modulares Entwicklungsmodell. Die spezifische Codestruktur des Web-Frontends ist in der folgenden Abbildung dargestellt:
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打造权限管理系统的方法,希望对大家有所帮助!