一、引言
本文将介绍如何把AngularJs应用到实际项目中。本篇文章将使用AngularJS来打造一个简易的权限管理系统。下面不多说,直接进入主题。
二、整体架构设计介绍
首先看下整个项目的架构设计图:
从上图可以看出整个项目的一个整体结构,接下来,我来详细介绍了项目的整体架构:
采用Asp.net Web API来实现REST 服务。这样的实现方式,已达到后端服务的公用、分别部署和更好地扩展。Web层依赖应用服务接口,并且使用Castle Windsor实现依赖注入。
显示层采用了AngularJS来实现的SPA页面。所有的页面数据都是异步加载和局部刷新,这样的实现将会有更好的用户体验。
应用层(Application Service)
AngularJS通过Http服务去请求Web API来获得数据,而Web API的实现则是调用应用层来请求数据。
基础架构层
基础架构层包括仓储的实现和一些公用方法的实现。
仓储层的实现采用EF Code First的方式来实现的,并使用的方式来创建数据库和更新数据库。
LH.Common层实现了一些公用的方法,如日志帮助类、表达式树扩展等类的实现。
领域层
领域层主要实现了该项目的所有领域模型,其中包括领域模型的实现和仓储接口的定义。
介绍完整体结构外,接下来将分别介绍该项目的后端服务实现和Web前端的实现。
三、后端服务实现
后端服务主要采用Asp.net Web API来实现后端服务,并且采用Castle Windsor来完成依赖注入。
这里拿权限管理中的用户管理来介绍Rest Web API服务的实现。
提供用户数据的REST服务的实现:
从上面代码实现可以看出,User REST 服务依赖与IUserService接口,并且也没有像传统的方式将所有的业务逻辑放在Web API实现中,而是将具体的一些业务实现封装到对应的应用层中,Rest API只负责调用对应的应用层中的服务。这样设计好处有:
REST 服务部依赖与应用层接口,使得职责分离,将应用层服务的实例化交给单独的依赖注入容器去完成,而REST服务只负责调用对应应用服务的方法来获取数据。采用依赖接口而不依赖与具体类的实现,使得类与类之间低耦合。REST服务内不包括具体的业务逻辑实现。这样的设计可以使得服务更好地分离,如果你后期想用WCF来实现REST服务的,这样就不需要重复在WCF的REST服务类中重复写一篇Web API中的逻辑了,这时候完全可以调用应用服务的接口方法来实现WCF REST服务。所以将业务逻辑实现抽到应用服务层去实现,这样的设计将使得REST 服务职责更加单一,REST服务实现更容易扩展。
用户应用服务的实现:
GetUsers(PageInput input)
{
var result = GetDefault>();
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();
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 AddUser(UserDto userDto)
{
var result = GetDefault>();
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();
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();
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 GetUser(int userId)
{
var result = GetDefault>();
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();
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();
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> BuildExpression(PageInput pageInput)
{
Expression> 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中去实现。这样一些增删改操作实现公用,之所以可以将这里操作实现公用,是因为这些操作都是非常类似的,无非是操作的实体不一样罢了。其实这样的实现在我另一个开源项目中已经用到:.大家可以参考这个自行去实现。
仓储层的实现:
用户应用服务也没有直接依赖与具体的仓储类,同样也是依赖其接口。对应的用户仓储类的实现如下:
: IRepository
where TEntity :class,IEntity
{
private readonly ThreadLocal _localCtx = new ThreadLocal(() => new UserManagerDBContext());
public UserManagerDBContext DbContext { get { return _localCtx.Value; } }
public TEntity FindSingle(Expression> exp = null)
{
return DbContext.Set().AsNoTracking().FirstOrDefault(exp);
}
public IQueryable Find(Expression> exp = null)
{
return Filter(exp);
}
public IQueryable Find(Expression> expression,Expression> 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().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> exp = null)
{
return Filter(exp).Count();
}
public void Add(TEntity entity)
{
DbContext.Set().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().Remove(entity);
}
public void Delete(ICollection entityCollection)
{
if(entityCollection.Count ==0)
return;
DbContext.Set().Attach(entityCollection.First());
DbContext.Set().RemoveRange(entityCollection);
}
private IQueryable Filter(Expression> exp)
{
var dbSet = DbContext.Set().AsQueryable();
if (exp != null)
dbSet = dbSet.Where(exp);
return dbSet;
}
public void Commit()
{
DbContext.SaveChanges();
}
}
public class UserRepository :BaseRepository,IUserRepository
{
}
四、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类的配置:
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
简易权限管理系统Demo
@Styles.Render("~/js/base/style")
@Scripts.Render("~/js/base/lib")