Other Articles in the Series
- Overview
- Add new Permission
- 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 (basic)
- Multiple data-stores: Scale your repository (Part 1)
- Multiple data-stores: Scale your repository (Part 2)
Introduction
In my code, we use angular2 (typescript) handles client logic and performs business logic on server side (c#).
The client communicates with server side through RESTful web service written in WepApi.
How to Get the Code
Please check out the code at https://github.com/techcoaching/TinyERP.
Calling from Client
In the page, we call to appropriated service:
@Component({
selector: "roles",
templateUrl: "app/modules/security/permission/permissions.html",
directives: [Grid, PageActions, Page]
})
export class Permissions extends BasePage {
private router: Router;
public model: PermissionsModel;
constructor(router: Router) {
super();
let self: Permissions = this;
self.router = router;
self.model = new PermissionsModel(self.i18nHelper);
self.loadPermissions();
this.model.addPageAction(new PageAction("btnAddPer", "security.permissions.addPermissionAction", () => self.onAddNewPermissionClicked()));
}
private loadPermissions() {
let self: Permissions = this;
permissionService.getPermissions().then(function (items: Array<any>) {
self.model.importPermissions(items);
});
}
}
In permissionService, it was actually use the IConnector (IConnector in order using Http from angular) and send request to server side.
import configHelper from "../../../../common/helpers/configHelper";
import {Promise} from "../../../../common/models/promise";
import {IoCNames} from "../../../../common/enum";
import {IConnector} from "../../../../common/connectors/iconnector";
let permissionService = {
getPermissions: getPermissions
};
export default permissionService;
function getPermissions(): Promise {
let connector: IConnector = window.ioc.resolve(IoCNames.IConnector);
let url = String.format("{0}permissions", configHelper.getAppConfig().api.baseUrl);
return connector.get(url);
}
In IConnector, we use Http service:
export class RESTConnector implements IConnector {
private static http: Http;
private static eventManager: EventManager;
constructor() {
let http: Http = window.appState.getInjector().get(Http);
this.setHttp(http);
}
public get(url: string): Promise {
RESTConnector.eventManager.publish(LoadingIndicatorEvent.Show);
let def = PromiseFactory.create();
let headers = new JsonHeaders();
RESTConnector.http.get(url, { headers: headers })
.map((response: any) => response.json())
.subscribe(
(data: any) => this.handleResponse(def, data),
(exception: any) => this.handleException(def, exception)
);
return def;
}
}
Handling on API (Server side)
We have the controller, we receive the request:
Hide Shrink Copy Code
namespace App.Api.Features.Security
{
[RoutePrefix("api/permissions")]
public class PermissionsController : ApiController
{
[HttpGet]
[Route()]
public IResponseData<IList<PermissionAsKeyNamePair>> GetPermissions()
{
IResponseData<IList<PermissionAsKeyNamePair>> response = new ResponseData<IList<PermissionAsKeyNamePair>>();
try
{
IPermissionService permissionService = IoC.Container.Resolve<IPermissionService>();
IList<PermissionAsKeyNamePair> pers = permissionService.GetPermissions();
response.SetData(pers);
}
catch (ValidationException ex)
{
response.SetErrors(ex.Errors);
response.SetStatus(System.Net.HttpStatusCode.PreconditionFailed);
}
return response;
}
}
}
If we have parameters sent to server along with request. they should be mapped to DTO (Data Transfer Object).
In RESTful, we know which Http Verb should be used for specified action (Get, Create, Update, Delete) and appropriated URI. So I think we will not discuss here again.
There are some different, I just want to clarify:
There are 2 types of errors/ exception, we returned to client: Infrastructure Error and Business Error
Infrastructure Error
we use this for some errors related to network (not found, time out, ....), configuration on IIS. it means our business code was not executed.
Business Error
we use this for the case, data was sent from clien for doing specified business logic is invalid.
Such as: user name or pwd does not match. For this case, we should not return 500 HttpCode, as client may not understand what was happended on server side.
So we need to return the client side the code for that error.
Hide Copy Code
IResponseData<IList<PermissionAsKeyNamePair>> response = new ResponseData<IList<PermissionAsKeyNamePair>>();
try
{
}
catch (ValidationException ex)
{
response.SetErrors(ex.Errors);
response.SetStatus(System.Net.HttpStatusCode.PreconditionFailed);
}
return response;
The output from REST client as below:
We can see, on the API can send multiple invalid validation back to client.
On the client we can show them on the UI. rather than common error message as photo below:
In the appropriated service, we validate the request and throw exception if invalid like this:
Hide Copy Code
private void ValidateUserLoginRequest(UserSignInRequest request)
{
ValidationException exception = new ValidationException();
if (request == null)
{
exception.Add(new ValidationError("common.invalidRequest"));
}
if (String.IsNullOrWhiteSpace(request.Email))
{
exception.Add(new ValidationError("registration.signin.validation.emailRequired"));
}
if (String.IsNullOrWhiteSpace(request.Pwd))
{
exception.Add(new ValidationError("registration.signin.validation.pwdRequired"));
}
IUserRepository userRepository = IoC.Container.Resolve<IUserRepository>();
User userProfile = userRepository.GetByEmail(request.Email);
if (userProfile == null || EncodeHelper.EncodePassword(request.Pwd) != userProfile.Password)
{
exception.Add(new ValidationError("registration.signin.validation.invalidEmailOrPwd"));
}
exception.ThrowIfError();
}
Summary
In some case, the response from the API success (status code 200) but we have the business logic exception.
Some of my friends told me that this may a little confuse.
If you have other better solution, Please let me know. I appriciate
For more information about: