一、引言
上一篇博文已经向大家介绍了AngularJS核心的一些知识点,在这篇博文将介绍如何把AngularJs应用到实际项目中。本篇博文将使用AngularJS来打造一个简易的权限管理系统。下面不多说,直接进入主题。
二、整体架构设计介绍
首先看下整个项目的架构设计图:
从上图可以看出整个项目的一个整体结构,接下来,我来详细介绍了项目的整体架构:
采用Asp.net Web API来实现REST 服务。这样的实现方式,已达到后端服务的公用、分别部署和更好地扩展。Web层依赖应用服务接口,并且使用Castle Windsor实现依赖注入。
显示层采用了AngularJS来实现的SPA页面。所有的页面数据都是异步加载和局部刷新,这样的实现将会有更好的用户体验。
2、应用层(Application Service)
AngularJS通过Http服务去请求Web API来获得数据,而Web API的实现则是调用应用层来请求数据。
3、基础架构层
仓储层的实现采用EF Code First的方式来实现的,并使用EF Migration的方式来创建数据库和更新数据库。
LH.Common层实现了一些公用的方法,如日志帮助类、表达式树扩展等类的实现。
4、领域层
领域层主要实现了该项目的所有领域模型,其中包括领域模型的实现和仓储接口的定义。
介绍完整体结构外,接下来将分别介绍该项目的后端服务实现和Web前端的实现。
三、后端服务实现
后端服务主要采用Asp.net Web API来实现后端服务,并且采用Castle Windsor来完成依赖注入。
这里拿权限管理中的用户管理来介绍Rest Web API服务的实现。
提供用户数据的REST服务的实现:
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只负责调用对应的应用层中的服务。这样设计好处有:
1.REST 服务部依赖与应用层接口,使得职责分离,将应用层服务的实例化交给单独的依赖注入容器去完成,而REST服务只负责调用对应应用服务的方法来获取数据。采用依赖接口而不依赖与具体类的实现,使得类与类之间低耦合。
2.REST服务内不包括具体的业务逻辑实现。这样的设计可以使得服务更好地分离,如果你后期想用WCF来实现REST服务的,这样就不需要重复在WCF的REST服务类中重复写一篇Web API中的逻辑了,这时候完全可以调用应用服务的接口方法来实现WCF REST服务。所以将业务逻辑实现抽到应用服务层去实现,这样的设计将使得REST 服务职责更加单一,REST服务实现更容易扩展。
用户应用服务的实现:
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("当前编辑的<a href="https://www.jb51.cc/tag/yonghu/" target="_blank" class="keywords">用户</a>“{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中去实现。这样一些增删改操作实现公用,之所以可以将这里操作实现公用,是因为这些操作都是非常类似的,无非是操作的实体不一样罢了。
仓储层的实现:
用户应用服务也没有直接依赖与具体的仓储类,同样也是依赖其接口。对应的用户仓储类的实现如下:
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
{
}
四、AngularJS前端实现
Web前端的实现就是采用AngularJS来实现,并且采用模块化开发模式。具体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类的配置:
首页 Index.cshtml