
从上图可以看出整个项目的一个整体结构,接下来,我来详细介绍了项目的整体架构:
采用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);} }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前端实现
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服务。
public class BundleConfig {// For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862public 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打造权限管理系统的方法,希望对大家有所帮助!