Single Page Application (SPA) for Enterprise App (Angular2 & WebApi) – Part 2 – Add new Permission

0
42








Other Articles in the Series

  1. Overview
  2. Project structure
  3. Multi-Languages (i18n)
  4. DI & IoC – Why and Why not?
  5. RESTful & WebApi
  6. Manage Application Lifecycle
  7. Build & Deploy Application
  8. New version of TinyERP with Angular 2 (typescript)
  9. CQRS: Avoiding performance issues in enterprise app (part 1)
  10. Multiple data-stores: Scale your repository (Part 1)

Introduction

The first thing I would like to share with you is “We do not learn technologies, we learn how to use technologies for our business”.

Note: In this code, we use Relase Candidate version of Angular 2

In this part, we will to through the list of steps to know how to use my code for implementing the add Role page as photo below:

https://www.codeproject.com/KB/applications/1144122/add-role1.png

How to Get the Code

Checkout the code at https://github.com/techcoaching/TinyERP.git.

Analyze the Page

As a matter of habit, I analyze the page before implementing, this will help us:

  • List out the steps we need to do to complete the page.
  • Figure out the missing information, so we can ask for help immediately.
  • Think about the flow of logic from the client to server side, from UI to repository. So the written code will be nicer. For this, many of my co-workers try to write the code first, and debug it later. When something goes wrong, it changes our behavior, and we try to change the code to make it work. This may break the logic flow and the code does not follow the convention. The architecture was used in the app. This will raise some new potential issues in the future and the code is hard for maintenance.

After analyzing the page, we figure out the list of things that need to be completed as below:

On Client

  • Register route for “Add Role” page (Component in angular2)
  • Create component files (html file for UI, ts file for logic handling and ts for view model of that component).
  • Implement the UI of “Add Role” component
  • Implement logic of “Add Role” component.
  • Implement service for this page (service will make the call to REST API for creating/ updating data on server).

On API

  • Add new method into RolesController for handling create request related to Role
  • Add new method into Role service and Role repository for creating Role

Implement the Client

In this section, <root> folder is the folder of client code (the path to “client” folder).

  1. Register route for “Add Role” page (Component in angular2):

    Go to “<root>/app/modules/secutiry/_share/config” and update addRoutes method to:


    import {IModule, Module, MenuItem} from "../../../../common/models/layout";
    import {Roles} from "../../role/roles";
    import {AddRole} from "../../role/addRole";
    import {AuthenticationMode} from "../../../../common/enum";
    import route from "./route";
    let module: IModule = createModule();
    export default module;
    function createModule() {
 let module = new Module("app/modules/secutiry", "secutiry");
 module.menus.push(
 new MenuItem(
 "Security", route.role.roles.name, "fa fa-user-md",
 new MenuItem("Roles", route.role.roles.name, ""),
 )
 );
 module.addRoutes([
 { path: route.role.roles.path, name: route.role.roles.name, component: Roles, 
 data: { authentication: AuthenticationMode.Require }, useAsDefault: true },
 { path: route.role.addRole.path, name: route.role.addRole.name, component: AddRole, 
 data: { authentication: AuthenticationMode.Require } }
 ]);
 return module;
    }


    The content of route.ts will be:


    let route = {
 role: {
 roles: { name: "Roles", path: "/roles" },
 addRole: { name: "Add Role", path: "/addRole" }
 }
    };
    export default route;

  2. Create component files (HTML file for UI, ts file for logic handling and ts for view model of that component).

  3. Implement the UI of “Add or Edit Role” component:
    <page>
 <page-header>
 {{i18n.security.addOrUpdateRole.title}}
 </page-header>
 <page-content>
 <form-default>
 <form-text-input [placeHolderText]=i18n.security.addOrUpdateRole.inputName 
                  [labelText]=i18n.security.addOrUpdateRole.name
                    [validation]="['security.addOrUpdateRole.validation.nameIsRequire',
 'security.addOrUpdateRole.validation.keyAlreadyExisted']"
                    [(model)]=model.name>
 </form-text-input>
 <form-textarea [placeHolderText]=i18n.security.addOrUpdateRole.inputDesc 
                 [labelText]=i18n.security.addOrUpdateRole.desc [(model)]=model.description>
 </form-textarea>
    
 <form-permission-select [(values)]=model.permissions 
                 [placeHolderText]=i18n.security.addOrUpdateRole.inputPermissions 
                 [labelText]=i18n.security.addOrUpdateRole.permission
                    [(model)]=model.permissions>
 </form-permission-select>
    
 <form-footer>
 <button id="save" 
                    (click)="onSaveClicked($event)" type="button" 
                     class="btn btn-primary">{{i18n.common.form.save}}</button>
 <button id="cancel" 
                    (click)="onCancelClicked($event)" type="button" 
                     class="btn btn-default">{{i18n.common.form.cancel}}</button>
 </form-footer>
 </form-default>
 </page-content>
    </page>


    The output to browser as photo below:


    https://www.codeproject.com/KB/applications/1144122/add-role–ui-on-browser.png


    In this file:


    • We did not use traditional HTML for creating the UI of the form, there are a number of directives for this purpose, such as: form, form-textarea, ….
    • The page was structured as:
      • page-header: This contains the header text of the page
      • page-content: This is the main content region of the page
  4. Implement logic of “Add Role” component.
    import {BasePage} from "../../../common/models/ui";
    import {Router, RouteParams} from "angular2/router";
    import {Component} from "angular2/core";
    import {AddRoleModel} from "./addRoleModel";
    import {Page, SelectPermission, Form, FormTextInput, FormFooter, 
 FormTextArea, FormPermissionSelect} from "../../../common/directive";
    import {ValidationDirective} from "../../../common/directive";
    import roleService from "../_share/services/roleService";
    import {FormMode} from "../../../common/enum";
    import route from "../_share/config/route";
    @Component({
 templateUrl: "app/modules/security/role/addRole.html",
 directives: [Page, Form, FormTextInput, FormFooter, FormTextArea, FormPermissionSelect]
    })
    export class AddRole extends BasePage {
 public model: AddRoleModel = new AddRoleModel();
 private router: Router;
 constructor(router: Router, routeParams: RouteParams) {
 super();
 let self: AddRole = this;
 self.router = router;
 }
 public onSaveClicked(event: any): void {
 let self: AddRole = this;
 roleService.create(this.model).then(function () {
 self.router.navigate([route.role.roles.name]);
 });
 }
 public onCancelClicked(event: any): void {
 let self: AddRole = this;
 self.router.navigate([route.role.roles.name]);
 }
    }


    In this file, we:


    • Get the id of selected role by routeParams.get("id")
    • Navigate between page using router.navigate method
  5. Implement service for this page (service will make the call to REST API for creating data on server).
    import configHelper from "../../../../common/helpers/configHelper";
    import {Promise} from "../../../../common/models/promise";
    let roleServices = {
 getRoles: getRoles,
 create: create
    };
    export default roleServices;
    function getRoles(): Promise {
 let connector = window.ioc.resolve("IConnector");
 let url = String.format("{0}roles", configHelper.getAppConfig().api.baseUrl);
 return connector.get(url);
    }
    
    function create(role: any): Promise {
 let connector = window.ioc.resolve("IConnector");
 let url = String.format("{0}roles", configHelper.getAppConfig().api.baseUrl);
 return connector.post(url, role);
    }

Implement API

  1. Add new method into RolesController for handling create request related to Role.

    using App.Common.DI;
    using App.Common.Http;
    using App.Common.Validation;
    using App.Service.Security;
    using System;
    using System.Collections.Generic;
    using System.Web.Http;
    
    namespace App.Api.Features.Security
    {
 [RoutePrefix("api/roles")]
 public class RolesController : ApiController
 {
 [HttpGet]
 [Route("")]
 public IResponseData<IList<RoleListItemSummary>> GetRoles()
 {
 IResponseData<IList<RoleListItemSummary>> response = 
 new ResponseData<IList<RoleListItemSummary>>();
 try
 {
 IRoleService roleService = IoC.Container.Resolve<IRoleService>();
 IList<RoleListItemSummary> roles=roleService.GetRoles();
 response.SetData(roles);
 }
 catch (ValidationException ex)
 {
 response.SetErrors(ex.Errors);
 response.SetStatus(System.Net.HttpStatusCode.PreconditionFailed);
 }
 return response;
 }
    
 [HttpPost]
 [Route("")]
 public IResponseData<CreateRoleResponse> CreateRole(CreateRoleRequest request)
 {
 IResponseData<CreateRoleResponse> response = new ResponseData<CreateRoleResponse>();
 try
 {
 IRoleService roleService = IoC.Container.Resolve<IRoleService>();
 CreateRoleResponse role = roleService.Create(request);
 response.SetData(role);
 }
 catch (ValidationException ex)
 {
 response.SetErrors(ex.Errors);
 response.SetStatus(System.Net.HttpStatusCode.PreconditionFailed);
 }
 return response;
 }
 }
    }

  2. Add new method into IRoleService and RoleService for creating Role.
    • IRoleService.cs
    namespace App.Service.Security
    {
 public interface IRoleService
 {
 System.Collections.Generic.IList<RoleListItemSummary> GetRoles();
 CreateRoleResponse Create(CreateRoleRequest request);
 }
    }


    • RoleService.cs
    using System.Collections.Generic;
    using App.Service.Security;
    using App.Common.DI;
    using App.Repository.Secutiry;
    using App.Entity.Security;
    using App.Common;
    namespace App.Service.Impl.Security
    {
 public class RoleService : IRoleService
 {
 public CreateRoleResponse Create(CreateRoleRequest request)
 {
 ValidateCreateRequest(request);
 using (App.Common.Data.IUnitOfWork uow = 
 new App.Common.Data.UnitOfWork(new App.Context.AppDbContext(IOMode.Write)))
 {
 IRoleRepository roleRepository = 
 IoC.Container.Resolve<IRoleRepository>(uow);
 IPermissionRepository permissionRepo = 
 IoC.Container.Resolve<IPermissionRepository>(uow);
 IList<Permission> permissions = 
 permissionRepo.GetPermissions(request.Permissions);
 Role role = new Role(request.Name, request.Description, permissions);
 roleRepository.Add(role);
 uow.Commit();
 }
 return new CreateRoleResponse();
 }
 private void ValidateCreateRequest(CreateRoleRequest request)
 {
 if (string.IsNullOrWhiteSpace(request.Name))
 {
 throw new App.Common.Validation.ValidationException
 ("security.addOrUpdateRole.validation.nameIsRequire");
 }
 }
 public IList<RoleListItemSummary> GetRoles()
 {
 IRoleRepository repository = IoC.Container.Resolve<IRoleRepository>();
 return repository.GetItems<RoleListItemSummary>();
 }
 }
    }


    In Role service:


    • User creates new Role calling Create method of Role repository.
    • In the UnitOfWork, we specify explicitly the mode we want to use for this UnitOfWork. This avoids unexpected change from being saved to database.
    • security.addOrUpdateRole.validation.nameIsRequire“, this will be resolved on client base on current language and display the error as the tooltip on each form component. In my code, the service should only return client the key of error, and client will display in appropriated language itself.
  3. IRoleRepository and RoleRepository for creating Role.

    We do not need to change this repository as it inherited appropriate method from IBaseContentRepository and BaseContentRepository.


    namespace App.Repository.Secutiry
    {
 public interface IRoleRepository: App.Common.Data.IBaseContentRepository<Role>
 {
 }
    }
    
    using App.Common.Data.MSSQL;
    using App.Entity.Security;
    using App.Repository.Secutiry;
    
    namespace App.Repository.Impl.Security
    {
 public class RoleRepository: BaseContentRepository<Role>, IRoleRepository
 {
 public RoleRepository() : base(new App.Context.AppDbContext(App.Common.IOMode.Read))
 {
 }
    
 public RoleRepository(IUnitOfWork uow) : base(uow.Context as IMSSQLDbContext)
 {
 }
 }
    }

For more information about Angular2, Please have a look at “Angular2 – Overview“.

Summary

Until this point, we can create new Role using the code.

For more information about:

  • Angular2, For:
  • REST:


License


Share



About the Author



I have more than 8 years in web development for multiple types of applications (ERP, Education System, …).
I usually organize training/ coaching on specified topic (such as: RESTful/ WebApi, Angular2, BEM, LESS, SASS, EF, NodeJs ….). Please contact me on Skype (tranthanhtu83) or email (contact@tranthanhtu.vn) if need.
For more information about me, Please visit http://www.tranthanhtu.vn/page/about-me




Comments and Discussions







General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

LEAVE A REPLY