1. Introduction
This article will introduce how to apply AngularJs to actual projects. This article will use AngularJS to create a simple permission management system. I won’t say much below, just go to the topic.
2. Introduction to overall architecture design
First, let’s take a look at the architectural design diagram of the entire project:
From the above figure, we can see the overall structure of the entire project. Next, I will introduce the overall structure of the project in detail:
Use Asp.net Web API to implement REST services. This implementation method has achieved the public use, deployment and better expansion of back-end services. The web layer depends on the application service interface and uses Castle Windsor to implement dependency injection.
Display layer (user UI)
The display layer uses AngularJS to implement SPA pages. All page data is loaded asynchronously and refreshed locally, so this implementation will have a better user experience.
Application Service
AngularJS requests the Web API to obtain data through the Http service, and the implementation of the Web API is to call the application layer to request data.
Infrastructure layer
The infrastructure layer includes the implementation of warehousing and the implementation of some common methods.
The implementation of the warehousing layer is implemented in EF Code First, and the EF Migration method is used to create and update the database.
The LH.Common layer implements some common methods, such as log help classes, expression tree extensions and other classes.
Domain layer
The domain layer mainly implements all domain models of the project, including the implementation of the domain model and the definition of the warehousing interface.
In addition to introducing the complete structure, we will introduce the back-end service implementation and the implementation of the Web front-end of the project respectively.
3. Back-end service implementation
Backend services mainly use Asp.net Web API to implement backend services, and Castle Windsor is used to complete dependency injection.
Here we use user management in permission management to introduce the implementation of Rest Web API service.
Implementation of REST service that provides user data:
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); } }From the above code implementation, it can be seen that the User REST service depends on the interface with IUserService, and does not place all business logic in the Web API implementation in the traditional way, but instead encapsulate some specific business implementations into the corresponding application layer. The Rest API is only responsible for calling the services in the corresponding application layer. This design benefits include:
The REST service department relies on the interface with the application layer to separate responsibilities and hand over the instantiation of the application layer service to a separate dependency injection container for completion. The REST service is only responsible for calling the corresponding application service methods to obtain data. Using dependency interfaces instead of implementations with specific classes makes low coupling between classes. The REST service does not include specific business logic implementations. This design can make services better separate. If you want to use WCF to implement REST services in the later stage, there is no need to repeatedly write a logic in the Web API in the REST service class of WCF. At this time, you can call the application service interface method to implement WCF REST service. Therefore, the business logic implementation is extracted to the application service layer to implement it. This design will make the REST service responsibilities more single and the REST service implementation easier to expand.
Implementation of user application services:
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("The currently edited user "{0}" no longer exists", 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; } }Here, the application service layer can actually be further optimized, implement code-level read and write separation, define the IReadOnlyService interface and the IWriteServie interface, and abstract the write operations into BaseService in the form of generic methods. Such addition, deletion and modification operations are public. The reason why this operation can be publicized is that these operations are very similar, and they are nothing more than the different entities of the operations. In fact, this implementation has been used in another open source project: OnlineStore. You can refer to this to implement it yourself.
Implementation of the storage layer:
User application services do not directly rely on specific warehousing classes, but also rely on their interfaces. The corresponding user warehousing class is implemented as follows:
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 front-end implementation
The implementation of the web front-end is to use AngularJS to implement it and adopt a modular development model. The specific code structure of the web front-end is shown in the figure below:
App/images // Store image resources used by the web front-end App/Styles // Store style files App/scripts // Script files used in the entire web front-end / Controllers // angularJS controller module storage directory / directives // angularJs instruction module storage directory / filters // filter module storage directory / services // Service module storage directory / app.js // Web front-end program configuration module (routing configuration) App/Modules // Project dependency library, angular, Bootstrap, Jquery library App/Views // AngularJs view template storage directory
The calling level and backend between the codes of web applications developed using AngularJS are basically the same as the backend, and they are also the view page - Controller Module - Service Module - Web API service.
In addition, the loading of CSS and JS resources in the front-end web adopts the Bundle method to reduce the number of requested resources, thereby speeding up the page loading time. Specific Bundle class configuration:
public class BundleConfig { // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862 public static void RegisterBundles(BundleCollection bundles) { // Class library dependency file 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 project file 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")); //Style bundles.Add(new StyleBundle("~/js/base/style").Include( "~/app/modules/bootstrap/css/bootstrap.min.css", "~/app/styles/dashboard.css", "~/app/styles/console.css" )); } }Home Index.cshtml
<!DOCTYPE html><html ng-app="LH"><head> <meta name="viewport" content="width=device-width" /> <title>Simple permission management system Demo</title> @Styles.Render("~/js/base/style") @Scripts.Render("~/js/base/lib")</head><body ng-controller="navigation"> <nav> <div> <div> <button type="button" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span>Toggle navigation</span> <span></span> <span></span> <span></span> </button> <a href="/">Simple permission management systemDemo</a> </div> <div> <ul> <li ng-repeat="item in ls"> <a href="#{{item.urls[0].link}}">{{item.name}}</a> </li> </ul> <div> <a href="@Url.Action("UnLogin", "Home", null)"> {{lang.exit}} </a> </div> </div> </nav> <div> <div> <div> <ul> <li ng-repeat="item in urls"><a href="#{{item.link}}">{{item.title}}</a></li> </ul> </div> <div> <div ng-view></div> </div> </div> </div> </div> @Scripts.Render("~/js/angularjs/app")</body></html>5. Operation effect
After introducing the implementation of the front and back ends, let’s take a look at the operation effect of the entire project:
6. Summary
At this point, all the contents of this article have been introduced, although the AngularJS application project in this article still has many perfect areas, such as no buffering support, no read and write separation, no stress tests on some APIs, etc. But the application of AngularJS in actual projects is basically like this. If you need to use AngularJS in your project, and your company's backend is .NET, I believe that the sharing of this article can be a good reference. In addition, you can also refer to my other open source project: OnlineStore and FastWorks for architecture design.
The above is the method of using AngularJs to create a permission management system introduced by the editor. I hope it will be helpful to everyone!