Other Articles in the Series
- Overview
- Project structure
- Multi-Languages (i18n)
- DI & IoC - Why and Why not?
- RESTful & WebApi
- Manage Application Lifecycle
- Build & Deploy Application
- New version of TinyERP with Angular 2 (typescript)
- CQRS: Avoiding performance issues in enterprise app (part 1)
- Multiple data-stores: Scale your repository (Part 1)
- Multiple data-stores: Scale your repository (Part 2)
- Basic authentication (user name/ password) with OWIN
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:
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).
- 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;
- 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 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:
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
- 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
- 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
- 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;
}
}
}
- Add new method into
IRoleService
and RoleService
for creating Role
.
namespace App.Service.Security
{
public interface IRoleService
{
System.Collections.Generic.IList<RoleListItemSummary> GetRoles();
CreateRoleResponse Create(CreateRoleRequest request);
}
}
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.
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: