1. Introduction
Cet article explique comment appliquer AngularJs à des projets réels. Cet article utilisera AngularJS pour créer un système simple de gestion des autorisations. Pas grand chose à dire ci-dessous, passons directement au sujet.
2. Introduction à la conception globale de l'architecture
Tout d'abord, jetez un œil au dessin de conception architecturale de l'ensemble du projet :
À partir de l'image ci-dessus, vous pouvez voir la structure globale de l'ensemble du projet. Ensuite, je présenterai la structure globale du projet en détail :
Utilisez l'API Web Asp.net pour implémenter les services REST. Cette méthode de mise en œuvre a permis un déploiement commun et séparé et une meilleure expansion des services back-end. La couche Web s'appuie sur l'interface de service d'application et utilise Castle Windsor pour implémenter l'injection de dépendances.
Couche d'affichage (interface utilisateur)
La couche d'affichage utilise la page SPA implémentée par AngularJS. Toutes les données de la page sont chargées de manière asynchrone et partiellement actualisées. Cette implémentation offrira une meilleure expérience utilisateur.
Service d'application
AngularJS demande l'API Web via le service HTTP pour obtenir des données, et la mise en œuvre de l'API Web appelle la couche application pour demander des données.
Couche d'infrastructure
La couche infrastructure comprend la mise en œuvre de l'entreposage et la mise en œuvre de certaines méthodes publiques.
La couche d'entrepôt est implémentée à l'aide d'EF Code First et la méthode EF Migration est utilisée pour créer et mettre à jour la base de données.
La couche LH.Common implémente certaines méthodes courantes, telles que l'implémentation de classes d'aide aux journaux, d'extensions d'arbre d'expression et d'autres classes.
Couche de domaine
La couche de domaine implémente principalement tous les modèles de domaine du projet, y compris la mise en œuvre des modèles de domaine et la définition des interfaces d'entreposage.
En plus de présenter la structure complète, la mise en œuvre du service back-end et la mise en œuvre du front-end Web du projet seront présentées séparément.
3. Implémentation du service back-end
Les services back-end utilisent principalement l'API Web Asp.net pour implémenter les services back-end, et Castle Windsor est utilisé pour terminer l'injection de dépendances.
Ici, nous abordons la gestion des utilisateurs dans la gestion des autorisations pour présenter la mise en œuvre du service Rest Web API.
Mise en place du service REST qui fournit les données utilisateur :
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); } }
Comme le montre l'implémentation du code ci-dessus, le service User REST s'appuie sur l'interface IUserService et ne met pas toute la logique métier dans l'implémentation de l'API Web de la manière traditionnelle, mais encapsule certaines implémentations métier spécifiques dans le In correspondant la couche application, Rest API est uniquement responsable de l'appel des services dans la couche application correspondante. Les avantages de cette conception sont :
Le département du service REST s'appuie sur l'interface de la couche application, qui sépare les responsabilités et laisse l'instanciation des services de la couche application à un conteneur d'injection de dépendances distinct, tandis que le service REST est uniquement responsable d'appeler les méthodes des services d'application correspondants pour obtenir des données. L'utilisation d'interfaces dépendantes plutôt que l'implémentation de classes spécifiques entraîne un faible couplage entre les classes. Le service REST n'inclut pas d'implémentation de logique métier spécifique. Ce type de conception permet de mieux séparer les services.Si vous souhaitez ultérieurement utiliser WCF pour implémenter les services REST, vous n'avez pas besoin d'écrire à plusieurs reprises la logique dans l'API Web dans la classe de service REST de WCF.C'est tout à fait correct. du service d'application pour implémenter le service WCF REST. Par conséquent, l'implémentation de la logique métier est extraite de la couche de service d'application. Cette conception rendra la responsabilité du service REST plus unique et l'implémentation du service REST plus facile à étendre.
Mise en place des services applicatifs utilisateurs :
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; } }
La couche de service d'application ici peut en fait être optimisée davantage pour réaliser la séparation de la lecture et de l'écriture au niveau du code, définir l'interface IReadOnlyService et l'interface IWriteServie et résumer l'opération d'écriture dans BaseService à l'aide d'une méthode générique. De telles opérations d'ajout, de suppression et de modification sont partagées. La raison pour laquelle ces opérations peuvent être partagées est que ces opérations sont très similaires, mais les entités des opérations sont différentes. En fait, ce type d'implémentation a été utilisé dans un autre de mes projets open source : OnlineStore Vous pouvez vous y référer pour l'implémenter vous-même.
Mise en place de la couche entrepôt :
Les services d'application utilisateur ne dépendent pas directement de classes de stockage spécifiques, mais s'appuient également sur leurs interfaces. L'implémentation de la classe de stockage utilisateur correspondante est la suivante :
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. Implémentation frontale d'AngularJS
Le front-end Web est implémenté à l'aide d'AngularJS et adopte un modèle de développement modulaire. La structure de code spécifique du front-end Web est illustrée dans la figure ci-dessous :
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打造权限管理系统的方法,希望对大家有所帮助!