TRUNGTQ

Think Big, Act Small, Fail Fast and Learn Rapidly

NAVIGATION - SEARCH

Using AngularJs, ASP.NET MVC, Web API and EntityFramework to build NLayered Single Page Web Applications

Building an NLayered, localized, well-structured Single-Page Web Application using AngularJs, ASP.NET MVC, Web API, EntityFramework and ASP.NET Boilerplate.

Simple Task System Screenshot
A screenshot of the sample application.

Contents

  • Introduction
  • Create the application from boilerplate template
  • Create entities
  • Create DbContext
  • Create Database Migrations
  • Define repositories
  • Implement repositories
  • Build application services
  • Build Web API services
  • Develop the SPA
  • Localization
  • Unit testing
  • Summary
  • Article history
  • References

Introduction

In this article, I'll show you how to develop a Single-Page Web Application (SPA) from ground to up using the following tools:

  • ASP.NET MVC and ASP.NET Web API as web framework.
  • Angularjs as SPA framework.
  • EntityFramework as ORM (Object-Relational Mapping) framework
  • Castle Windsor as Dependency Injection framework.
  • Twitter Bootstrap as HTML/CSS framework.
  • Log4Net for logging, AutoMapper for object-to-object mapping.
  • And ASP.NET Boilerplate as startup template and application framework.

ASP.NET Boilerplate [1] is an open source application framework that combines all of these frameworks and libraries to make you start easily to develop your application. It provides us an infrastructure to develop applications in best practices. It naturally supports Dependency Injection, Domain Driven Designand Layered Architecture. The sample application also implements validation, exception handling, localization andresponsive design.

Create the application from boilerplate template

ASP.NET Boilerplate saves our time while starting a new application by providing templates that combinesandconfiguresbest tools to build enterprise level web applications.

Let's go to aspnetboilerplate.com/Templates to build our application from template...

Create template by ASP.NET Boilerplate

Here, I selected SPA (Single Page Application) with AngularJs and EntityFramework. Also entered SimpleTaskSystem for my project name. It created and downloaded my solution.

There are 5 projects included in the solution. Core project for domain (business) layer, Application project for application layer, WebApi project to implement Web Api controllers, Web project for presentation layer and finally EntityFramework project for EntityFramework implementation.

Note: If you download sample solution for this acticle, you will see 7 projects in the solution. I improved template to support NHibernate and Durandal also for same application. If you don't interest in NHibernate or Durandal, just ignore these 2 projects.

Create entities

I'm creating a simple application to create tasks and assing these tasks to people. So, I need Task and Person entities.

Task entity simply defines a Description, CreationTime and a State for a Task. It also has an optional reference to a Person (AssignedPerson):

public class Task : Entity<long>
{
    [ForeignKey("AssignedPersonId")]
    public virtual Person AssignedPerson { get; set; }

    public virtual int? AssignedPersonId { get; set; }

    public virtual string Description { get; set; }

    public virtual DateTime CreationTime { get; set; }

    public virtual TaskState State { get; set; }

    public Task()
    {
        CreationTime = DateTime.Now;
        State = TaskState.Active;
    }
}


Person entity is simpler and just defines Name of the person:

public class Person : Entity
{
    public virtual string Name { get; set; }
}


ASP.NET Boilerplate provides Entity class that defines Id poperty. I derived entities from this Entity class. Task class has an Id of type long since I derived from Entity<long>. Person class has an Id of type int. Since int is the default primary key type, I did not specified it.

I defined entities in the Core project since Entities are parts of domain/business layer.

Create DbContext

As you know, EntityFramework works with DbContext class. We should first define it. ASP.NET Boilerplate template creates a DbContext template for us. I just added IDbSets for Task and Person. This is my DbContext class:

public class SimpleTaskSystemDbContext : AbpDbContext
{
    public virtual IDbSet<Task> Tasks { get; set; }

    public virtual IDbSet<Person> People { get; set; }

    public SimpleTaskSystemDbContext()
        : base("Default")
    {

    }

    public SimpleTaskSystemDbContext(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {
            
    }
}


It uses Default connection string in web.config. It's defined as shown below:

<add name="Default" connectionString="Server=localhost; Database=SimpleTaskSystem; Trusted_Connection=True;" providerName="System.Data.SqlClient" />

Create Database Migrations

We'll use EntityFramework's Code First Migrations to create and maintain the database schema. ASP.NET Boilerplate template has enabled migrations by default and added a Configurationclass as shown below:

internalinternal sealed class Configuration : DbMigrationsConfiguration<SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    }

    protected override void Seed(SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext context)
    {
        context.People.AddOrUpdate(
            p => p.Name,
            new Person {Name = "Isaac Asimov"},
            new Person {Name = "Thomas More"},
            new Person {Name = "George Orwell"},
            new Person {Name = "Douglas Adams"}
            );
    }
}


In the Seed method, I added four people for initial data. Now, I'll create the initial migration. I opened Package Manager Console and typed the following command:

Visual studio Package manager console

Add-Migration "InitialCreate" command creates a class named InitialCreate as shown below:

public partial class InitialCreate : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.StsPeople",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    Name = c.String(),
                })
            .PrimaryKey(t => t.Id);
            
        CreateTable(
            "dbo.StsTasks",
            c => new
                {
                    Id = c.Long(nullable: false, identity: true),
                    AssignedPersonId = c.Int(),
                    Description = c.String(),
                    CreationTime = c.DateTime(nullable: false),
                    State = c.Byte(nullable: false),
                })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.StsPeople", t => t.AssignedPersonId)
            .Index(t => t.AssignedPersonId);            
    }
        
    public override void Down()
    {
        DropForeignKey("dbo.StsTasks", "AssignedPersonId", "dbo.StsPeople");
        DropIndex("dbo.StsTasks", new[] { "AssignedPersonId" });
        DropTable("dbo.StsTasks");
        DropTable("dbo.StsPeople");
    }
}


We did create needed classes to create the database, but not created the database yet. To do it, I'll run the following command:

PM> Update-Database

This command runs migrations, creates the database and populates the initial data for us:

Database created by EntityFramework Migrations

When we change Entitiy classes, we can easily create new migration classes using Add-Migration command and update the database with Update-Database command. To learn more about database migrations, see entity framework's documentation.

Define repositories

In domain driven design, repositories used to implement database-specific codes. ASP.NET Boilerplate creates an automatic repository for each entity using generic IRepository interface. IRepository defines common methods for select, insert, update, delete and a few more:

IRepository interface

We can extend these repository upon our needs. I will extend it to create a Task repository. As I want to seperate interface from implementation, I declare interfaces for repositories first. Here, is the Task repository interface:

public interface ITaskRepository : IRepository<Task, long>
{
    List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state);
}


It extends generic IRepository interface of ASP.NET Boilerplate. So, ITaskRepository inherently defines all these methods as default. It can also add it's own methods as I defined GetAllWithPeople(...).

No need to create a repository for Person since default methods are enough for me. ASP.NET Boilerplate provides a way of injecting generic repositories without creating a repository class. We will see it in TaskAppService class in 'Build application services' section..

I defined repository interfaces in the Core project since they are parts of domain/business layer.

Implement repositories

We should implement the ITaskRepository interface defined above. I'm implementing repositories in EntityFramework project. Thus, domain layer becomes completely independent from EntityFramework.

When we created the project template, ASP.NET Boilerplate defined a generic base class for repositories in our project: SimpleTaskSystemRepositoryBase. It's a good practice to have such a base class since we can later add some common methods for our repositories. You can see definition of this class in the code. I just derive from it for TaskRepository implementation:

public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository
{
    public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state)
    {
        //In repository methods, we do not deal with create/dispose DB connections, DbContexes and transactions. ABP handles it.
            
        var query = GetAll(); //GetAll() returns IQueryable<T>, so we can query over it.
        //var query = Context.Tasks.AsQueryable(); //Alternatively, we can directly use EF's DbContext object.
        //var query = Table.AsQueryable(); //Another alternative: We can directly use 'Table' property instead of 'Context.Tasks', they are identical.
            
        //Add some Where conditions...

        if (assignedPersonId.HasValue)
        {
            query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value);
        }

        if (state.HasValue)
        {
            query = query.Where(task => task.State == state);
        }

        return query
            .OrderByDescending(task => task.CreationTime)
            .Include(task => task.AssignedPerson) //Include assigned person in a single query
            .ToList();
    }
}


TaskRepository is derived from SimpleTaskSystemRepositoryBase and implements ITaskRepository we defined above.

GetAllWithPeople is our specific method to get tasks where AssignedPerson included (pre-fetched) and optionally filtered by some conditions. We can freely use Context (EF's DBContext) object and database in repositories. ASP.NET Boilerplate manages database connection, transaction, creating and disposing the DbContext for us (See documentation for more information)

Build application services

Application services is used to seperate presentation layer from domain layer by providing façade style methods. I defined application services in the Application assembly in the project. First, I define interface for task application service:

public interface ITaskAppService : IApplicationService
{
    GetTasksOutput GetTasks(GetTasksInput input);
    void UpdateTask(UpdateTaskInput input);
    void CreateTask(CreateTaskInput input);
}


ITaskAppService extends IApplicationService. Thus, ASP.NET Boilerplate automatically provides some features for this class (like dependency injection and validation). Now, let's implement ITaskAppService:

public class TaskAppService : ApplicationService, ITaskAppService
{
    //These members set in constructor using constructor injection.
        
    private readonly ITaskRepository _taskRepository;
    private readonly IRepository<Person> _personRepository;
        
    /// <summary>
    ///In constructor, we can get needed classes/interfaces.
    ///They are sent here by dependency injection system automatically.
    /// </summary>
    public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository)
    {
        _taskRepository = taskRepository;
        _personRepository = personRepository;
    }
        
    public GetTasksOutput GetTasks(GetTasksInput input)
    {
        //Called specific GetAllWithPeople method of task repository.
        var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);

        //Used AutoMapper to automatically convert List<Task> to List<TaskDto>.
        return new GetTasksOutput
                {
                    Tasks = Mapper.Map<List<TaskDto>>(tasks)
                };
    }
        
    public void UpdateTask(UpdateTaskInput input)
    {
        //We can use Logger, it's defined in ApplicationService base class.
        Logger.Info("Updating a task for input: " + input);

        //Retrieving a task entity with given id using standard Get method of repositories.
        var task = _taskRepository.Get(input.TaskId);

        //Updating changed properties of the retrieved task entity.

        if (input.State.HasValue)
        {
            task.State = input.State.Value;
        }

        if (input.AssignedPersonId.HasValue)
        {
            task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
        }

        //We even do not call Update method of the repository.
        //Because an application service method is a 'unit of work' scope as default.
        //ABP automatically saves all changes when a 'unit of work' scope ends (without any exception).
    }

    public void CreateTask(CreateTaskInput input)
    {
        //We can use Logger, it's defined in ApplicationService class.
        Logger.Info("Creating a task for input: " + input);

        //Creating a new Task entity with given input's properties
        var task = new Task { Description = input.Description };

        if (input.AssignedPersonId.HasValue)
        {
            task.AssignedPersonId = input.AssignedPersonId.Value;
        }

        //Saving entity with standard Insert method of repositories.
        _taskRepository.Insert(task);
    }
}

TaskAppService
uses repositories for database operations. It gets references in it's constructor via constructor injection pattern. ASP.NET Boilerplate naturally implements dependency injection, so we can use constructor injection or property injection freely (See more on dependency injection in ASP.NET Boilerplate documentation).

Notice that we're using PersonRepository by injecting IRepository<Person>. ASP.NET Boilerplate automatically creates repositories for our entities. If default methods of IRepository enough for us, we don't have to create repository classes.

Application service methods works with Data Transfer Objects (DTOs). It's a best practice and I definitely suggest to use this pattern. But you don't have to do it as long as you can deal with problems of exposing Entities to presentation layer.

In the GetTasks method, I used the GetAllWithPeople method that I implemented before. It returns a List<Task> but I need to return a List<TaskDto> to presentation layer. AutoMapper helps us here to automatically convert Task objects to TaskDto objects. GetTasksInput and GetTasksOutput are special DTOs defined for GetTasks method.

In the UpdateTask method, I retrived the Task from database (using IRepository's Get method) and changed peoperties of the Task. Notice that I did not even called Update method of the repository. ASP.NET Boilerplate implements UnitOfWork pattern. So, all changes in an application service method are a unit of work (atomic) and applied to database at the end of the method automatically.

In the CreateTask method, I simply created a new Task and inserted to database using the IRepository's Insert method.

ASP.NET Boilerplate's ApplicationService class has some properties to make developing application services easier. For example, it defines a Logger property for logging. So, we derived TaskAppService from ApplicationService and used it's Logger property here. It's optional to derive from this class but required to implement IApplicationService (notice that ITaskAppService extends IApplicationService).

Validation

ASP.NET Boilerplate automatically validates inputs of application service methods. CreateTask method gets CreateTaskInput as parameter:

public class CreateTaskInput
{
    public int? AssignedPersonId { get; set; }

    [Required]
    public string Description { get; set; }
}


Here, Description is marked as Required. You can use any Data Annotation attributes here. If you want to make some custom validation, you can implement ICustomValidate as I implemented in UpdateTaskInput:

public class UpdateTaskInput : ICustomValidate
{
    [Range(1, long.MaxValue)]
    public long TaskId { get; set; }

    public int? AssignedPersonId { get; set; }

    public TaskState? State { get; set; }

    public void AddValidationErrors(List<ValidationResult> results)
    {
        if (AssignedPersonId == null && State == null)
        {
            results.Add(new ValidationResult("Both of AssignedPersonId and State can not be null in order to update a Task!", new[] { "AssignedPersonId", "State" }));
        }
    }

    public override string ToString()
    {
        return string.Format("[UpdateTask > TaskId = {0}, AssignedPersonId = {1}, State = {2}]", TaskId, AssignedPersonId, State);
    }
}


AddValidationErrors method is the place you can write your custom validation code.

Handling exceptions

Note that we did not handled any exception. ASP.NET Boilerplate automatically handles exceptions, logs and returns an appropriate error message to the client. Also, in client side, handles these error messages and show to the user. Actually, this is true for ASP.NET MVC and Web API Controller actions. Since we will expose the TaskAppService using Web API, we don't need to handle exceptions. See exception handling document for details.

Build Web API services

I want to expose my application services to remote clients. Thus, my AngularJs application can easily call these service methods using AJAX.

ASP.NET Boilerplate provides an automatic way of exposing application service methods as ASP.NET Web API. I just use DynamicApiControllerBuilder as shown below:

DynamicApiControllerBuilder
    .ForAll<IApplicationService>(Assembly.GetAssembly(typeof (SimpleTaskSystemApplicationModule)), "tasksystem")
    .Build();


For this example, ASP.NET Boilerplate finds all interfaces inherits IApplicationService in Application layer assembly and creates a web api controller for each application service class. There are alternative syntaxes for fine control. We'll see how to call these services via AJAX.

Develop the SPA

I'll implement a Single-Page Web Application as user interface of my project. AngularJs (by Google) is one (propably the top one) of the most used SPA frameworks.

ASP.NET Boilerplate provides a template that makes easy to start with AngularJs. The template has two pages (Home and About) with smooth transition between pages. Uses Twitter Bootstrap as HTML/CSS framework (thus, it's responsive). It's also localized into English and Turkish with ASP.NET Boilerplate's localization system (You can easly add other languages or remove one of them).

We first change route of the template. ASP.NET Boilerplate template uses AngularUI-Router, the de-facto standard router of AngularJs. It provides state based routing modal. We will have two views: task list and new task. So, we will change route definition in app.js as shown below:

app.config([
    '$stateProvider', '$urlRouterProvider',
    function ($stateProvider, $urlRouterProvider) {
        $urlRouterProvider.otherwise('/');
        $stateProvider
            .state('tasklist', {
                url: '/',
                templateUrl: '/App/Main/views/task/list.cshtml',
                menu: 'TaskList' //Matches to name of 'TaskList' menu in SimpleTaskSystemNavigationProvider
            })
            .state('newtask', {
                url: '/new',
                templateUrl: '/App/Main/views/task/new.cshtml',
                menu: 'NewTask' //Matches to name of 'NewTask' menu in SimpleTaskSystemNavigationProvider
            });
    }
]);


app.js is the main javascript file to configure and start our SPA. Notice that we're using cshtml files as views! Normally, html files are used as views in AngularJs. ASP.NET Boilerplate makes it possible to use cshtml files. Thus we will have the power of razor engine to generate HTML.

ASP.NET Boilerplate provides an infrastructure to create and show menus in an application. It allows to define menu in C# and use same menu both in C# and javascript. See SimpleTaskSystemNavigationProvider class for creating menu and see header.js/header.cshtml for showing menu in the angular way.

First, I'm creating an Angular controller for the task list view:

(function() {
    var app = angular.module('app');

    var controllerId = 'sts.views.task.list';
    app.controller(controllerId, [
        '$scope', 'abp.services.tasksystem.task',
        function($scope, taskService) {
            var vm = this;

            vm.localize = abp.localization.getSource('SimpleTaskSystem');

            vm.tasks = [];

            $scope.selectedTaskState = 0;

            $scope.$watch('selectedTaskState', function(value) {
                vm.refreshTasks();
            });

            vm.refreshTasks = function() {
                abp.ui.setBusy( //Set whole page busy until getTasks complete
                    null,
                    taskService.getTasks({ //Call application service method directly from javascript
                        state: $scope.selectedTaskState > 0 ? $scope.selectedTaskState : null
                    }).success(function(data) {
                        vm.tasks = data.tasks;
                    })
                );
            };

            vm.changeTaskState = function(task) {
                var newState;
                if (task.state == 1) {
                    newState = 2; //Completed
                } else {
                    newState = 1; //Active
                }

                taskService.updateTask({
                    taskId: task.id,
                    state: newState
                }).success(function() {
                    task.state = newState;
                    abp.notify.info(vm.localize('TaskUpdatedMessage'));
                });
            };

            vm.getTaskCountText = function() {
                return abp.utils.formatString(vm.localize('Xtasks'), vm.tasks.length);
            };
        }
    ]);
})();


I defined name of the controller as 'sts.views.task.list'. This my convention (for scalable code-base) but you can simply name it as 'ListController'. AngularJs also uses dependency injection. We're injecting '$scope' and 'abp.services.tasksystem.task' here. First one is Angular's scope variable, second one is the automatically created javascript service proxy for ITaskAppService (we built it before in 'Build Web API services' section).

ASP.NET Boilerplate provides infrastructure to use same localization texts both in server and client (see it's documentation for details). 

vm.taks is the list of tasks that will be shown in the view. vm.refreshTasks method fills this array by getting tasks using taskService. It's called when selectedTaskState changes (observed using $scope.$watch).

As you see, calling an application service method is very easy and straightforward! This is a feature of ASP.NET Boilerplate. It generates Web API layer and Javascript proxy layer that talks with this Web API layer. Thus, we are calling the application service method as calling a simple javascript method. It is completely integrated with AngularJs (uses Angular's $http service).

Let's see the view side of task list:

<div class="panel panel-default" ng-controller="sts.views.task.list as vm">

    <div class="panel-heading" style="position: relative;">
        <div class="row">
            
            <!-- Title -->
            <h3 class="panel-title col-xs-6">
                @L("TaskList") - <span>{{vm.getTaskCountText()}}</span>
            </h3>
            
            <!-- Task state combobox -->
            <div class="col-xs-6 text-right">
                <select ng-model="selectedTaskState">
                    <option value="0">@L("AllTasks")</option>
                    <option value="1">@L("ActiveTasks")</option>
                    <option value="2">@L("CompletedTasks")</option>
                </select>
            </div>
        </div>
    </div>

    <!-- Task list -->
    <ul class="list-group" ng-repeat="task in vm.tasks">
        <div class="list-group-item">
            <span class="task-state-icon glyphicon" ng-click="vm.changeTaskState(task)" ng-class="{'glyphicon-minus': task.state == 1, 'glyphicon-ok': task.state == 2}"></span>
            <span ng-class="{'task-description-active': task.state == 1, 'task-description-completed': task.state == 2 }">{{task.description}}</span>
            <br />
            <span ng-show="task.assignedPersonId > 0">
                <span class="task-assignedto">{{task.assignedPersonName}}</span>
            </span>
            <span class="task-creationtime">{{task.creationTime}}</span>
        </div>
    </ul>

</div>


ng-controller attribute (in the first line) binds the controller to the view. @L("TaskList") gets localized text for "task list" (works on server while rendering HTML). It's possible since this is a cshtml file.

ng-model binds combobox and the javascript variable. When the variable changes, the combobox updated. When the combobox changes, the valiable is updated. This is two-way binding of AngularJs.

ng-repeat is another 'directive' of Angular that is used to render same HTML for each value in an array. When the array changes (an item is added for example), it's automatically reflected to the view. This is another powerful feature of AngularJs.

Note: When you add a javascript file (for example, for the 'task list' controller), you should add it to your page. This can be done by adding it to Home\Index.cshtml in the template.

Localization

ASP.NET Boilerplate provides a flexible and strong localization system. You can use XML files or Resource files as localization source. You can also define custom localization sources. See documentation for more. In this sample application, I used XML files (it's under Localization folder in web application):

<?xml version="1.0" encoding="utf-8" ?>
<localizationDictionary culture="en">
  <texts>
    <text name="TaskSystem" value="Task System" />
    <text name="TaskList" value="Task List" />
    <text name="NewTask" value="New Task" />
    <text name="Xtasks" value="{0} tasks" />
    <text name="AllTasks" value="All tasks" />
    <text name="ActiveTasks" value="Active tasks" />
    <text name="CompletedTasks" value="Completed tasks" />
    <text name="TaskDescription" value="Task description" />
    <text name="EnterDescriptionHere" value="Task description" />
    <text name="AssignTo" value="Assign to" />
    <text name="SelectPerson" value="Select person" />
    <text name="CreateTheTask" value="Create the task" />
    <text name="TaskUpdatedMessage" value="Task has been successfully updated." />
    <text name="TaskCreatedMessage" value="Task {0} has been created successfully." />
  </texts>
</localizationDictionary>
 

Unit testing

ASP.NET Boilerplate is designed to be testable. I authored an article to show unit and integration testing for ABP based projects. See the article: Unit testing in C# using xUnit, Entity Framework, Effort and ASP.NET Boilerplate.

Summary

In this article, I demonstrated how to develop an NLayered ASP.NET MVC web application with a SPA and responsive user interface. I used ASP.NET Boilerplate since it makes easy to develop such applications using best practices and saves our time. Use these links for moe:

LINK: https://www.codeproject.com/Articles/791740/Using-AngularJs-ASP-NET-MVC-Web-API-and-EntityFram

Single Page Application (SPA) for Enterprise App (Angular2 & WebApi) - Part 1 - Overview

Một tác giả người Việt, bài viết khá chất lượng, có thể tham khảo.
 
How to build the SPA for enterprise application using Angular2 and WebApi (RESTful)

Other Articles in the series

  1. Overview
  2. Add new Permission
  3. Project structure
  4. Multi-Languages (i18n)
  5. DI & IoC - Why and Why not?
  6. RESTful & WebApi
  7. Manage Application Lifecycle
  8. Build & Deploy Application

Introduction

please visit my blog at www.tranthanhtu.vn for reading more articles about coding

Note: In my series of this article, I use free template at https://colorlib.com/polygon/gentelella

The first thing, I would like to share to you is "We did not learn technogies, we learn how to use technologies for our business".

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

Nowadays, with growing of web development, more and more desktop applications (such as HRM, CRM, Payroll, ...)  were migrated to web application.

So the web application can utilize much benefit from web environment. This is easy to search the pros and cons between web application and desktop application on the internet, so we will not discuss here.

Purpose of series of articles will introduce you how did I organise the project for those types of application.

Some highlight technologies:

  • Angular2 (typescript)
  • WebApi (RESTful)
  • Entity Framework
  • Bootstrap
  • IoC
  • Multi-Layer architecture
  • Modular your Application
  • Multi Languages
  • Design Pattern (Unit Of Work, Repository, …)
  • SOLID principle
  • Gulp
  • NodeJs

There are many mistake and improvements need to be done so far.

How to get the code

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

How to run the code

The code was organized in 2 folders:

  • Client: this is client code written by typescript using angular2 framework.
  • Api: this is server side code where handles request from client.

Run client side:

  1. To run the client side code, please check and install if missing:
  1. Run the app

- Open command prompt and go to the root folder of client:

- Type “npm install” for installing missing nodejs packages.

- Use “npm start” to run the app:

Output in console:

Output on the browser:

To this point, we can run the client successfully

Run API

- For the first run, We need to update the connectionstring in app.common\configurations\configuration.(debug|release).config

- Open Api project in visual studio (2015).

- Press f5 to run the Api on IIS Express, we will see the output on browser:

Config the default route, will remove this error page.

The First Look At The App

- We need to login before continueing:

- Login with tu.tran@yahoo.com/123456, we will see the default page as below (this can be changed in application configuration):

 

List of Roles Page

In this section, we will go through how we implement new page using my code.

Analyse

As my habit, I analyse the page before implement, this will help us recognise:

  • Listing out The steps we need to do to complete the page.
  • Figure out the missing information, so we can ask for help immediatedly.
  • 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, they try to write the code first, and debug it after that. When something wrong, it changes your behavior, and we try to change the code to make it works. this may break the logic flow and the code is not follow the convention, architecture was used in the app. This will raise some new potential issues in the future and the code is hard for manteance.

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

On client:

+ Create new module (called security), I will discusss more pros and cons on how to modular the application.

+ Register route for Roles component (page in angular was called component) and appropriated menu item information (sub menu on th eleft panel)

+ Import and register this setting module with application config.

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

+ Implemnet the UI of Roles component

+ Implement logic of Roles component (also calling to server and getting data)

+ Implement service for this page (service willmake the call to REST api for getting the list of permission).

On Api:

+ Add new controller called RolesController for handling request related to Role.

+ Add RoleService and RoleRepository for getting the list of Roles (I use multi-layers architecture in this case)

+ Add entity into appropriated DbContext.

Ok, Now we will go through step by step in detail.

Implement client

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

  1. Create new module (called security). as the convention of the app, we create new module.ts file in "<root>/app/modules/secutiry/_share/config" as photo below:

and route.ts file

In this file, we will provide the setting information for security module, such as: list of sub menu items, routing in module, ...

  1. Register route for Roles component (page in angular was called component) and appropriated menu item information (sub menu on th eleft panel). Add this line into the module.ts file created in above step.

  1. Import and register this setting module with application config. This will register the security module in the application, show the module and its sub menu items will be displayed on the left panel.

The sytem will auto register routes in security module for us automaticly.

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

  1. Implemnet the UI of Roles component, there are some directives already created, we use in this article. I will explain more detail in other article how to implement those directives in other articles.

In this html, we use:

  • "grid" directive, for displaying the list of roles. there are some events we can passnig the handlers and  columns of grid in mode.options property.
  • "page-action" directive, this will dispaly the "Add Role" on the list of Roles as photo below:

  • page, page-header, page-content directives, this is the structure of the page in my framework.
  1. Implement logic of Roles component (also calling to server and getting data)

For declare new componnet we need to:

  • Specify the template file for the component (line 9).
  • Declare the number of directives were used in component html file (line 11). Is the directive was not declare here, it will not be redered in roles.html file and angular will consider its tag (<page-action/>, <grid />) as html tag
  • Declare new Roles class inherit  from BasePage.
  • Declare the model for the component. each component should have its own view model, this help us reducing the complexity of roles.ts file.
  • Getting data from api by calling appropriated method of roleService. We move this to service and let the roles.ts focus on the behavior of the page.

Note: we pass i18nHelper into constructor of RolesModel. this will be used for resolving the lable of grid columns base on the current languages (multi-language supported).

  1. Implement service for this page (service willmake the call to REST api for getting the list of permission).

This is just a simple call to REST api for getting the list of roles. For more information about IConnection, see <root>/app/common/connectors/RESTConnector.ts

Implement Api

In this section, <root> folder is the folder of api code (the path to "api" folder).

  1. Api project structure:

  • App.Common: this contains the common code can be used everywhere in the app
  • App.Api: this is public REST api that client side can call.
  • App.Service: this contains all service interfaces and its appropriated DTOs.
  • App.Service.Impl: this only contains implementation of appropriated interface in App.Service.
  • App.Repository: this contains all repository interfaces. Each repository only operates on 1 entity in database.
  • App.Repository.Impl: this only contains implementation of appropriated interface in App.Resitory.
  • App.Entity: This contains all entities of the app.
  • App.Context: Currently, this containts the all DbContexts in the app, as we use EF framework. I intend to merge this with the App.Entity in the future.

Note: The communication between the layer (such as: App.Api -> App.Service => App.Repository), we use the interface only.

 

  1. Add new controller called RolesController for handling request related to Role.

As the convention of the app, we create new RolesController in "App.Api/Features/Security"

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;
        }
    }
}
 

I think the code is rather simple. From controller we call "GetRoles" of the appropriated service interface and get the list of roles (dto).

IoC will return the concrete instance of that service.

In App.Service.Impl, we have boostrap.cs file, this is the place for registering the service interface and its concrete implementation.

namespace App.Service.Impl
{
    public class Bootstrap : App.Common.Tasks.BaseTask<IBaseContainer>, IBootstrapper
    {
        public Bootstrap():base(App.Common.ApplicationType.All)
        {
        }
        public void Execute(IBaseContainer context)
        {
            context.RegisterSingleton<App.Service.Security.IRoleService, App.Service.Impl.Security.RoleService>();
        }
    }
}
 
  1. Add IRoleService and RoleService for getting the list of Roles (I use multi-layers architecture in this case)
namespace App.Service.Security
{
    public interface IRoleService
    {
        System.Collections.Generic.IList<RoleListItemSummary> GetRoles();
    }
}

namespace App.Service.Impl.Security
{
    internal class RoleService : IRoleService
    {
        public IList<RoleListItemSummary> GetRoles()
        {
            IRoleRepository repository = IoC.Container.Resolve<IRoleRepository>();
            return repository.GetItems<RoleListItemSummary>();
        }
    }
}


Note:

  • We should define appropriate DTOs for each action, as most cases, we just get some properties of entity and this will help the code easier for mantaince in future.
  • The RoleService class was delared as internal, not a public class. So outside of this dll can not create instance of this class

 

  1. Add IRoleRepository and RoleRepository for getting the list of Roles
namespace App.Repository.Secutiry
{
    public interface IRoleRepository: App.Common.Data.IBaseContentRepository<Role>
    {
    }
}

namespace App.Repository.Impl.Security
{
    internal 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)
        {
        }
    }
}


Note: The same as RoleService, RoleRepository class also was delared as internal. So only the interface can be used outside of this project.

  1. Add entity into appropriated DbContext:
namespace App.Entity.Security
{
    public class Role:BaseContent
    {
        public IList<Permission> Permissions { get; set; }
        public Role():base()
        {
            this.Permissions = new List<Permission>();
        }
        public Role(string name, string desc, IList<Permission> permissions): this() {
            this.Name = name;
            this.Key = App.Common.Helpers.UtilHelper.ToKey(name);
            this.Description = desc;
            if (permissions == null) { return; }
            this.Permissions = permissions;
        }
    }
}

namespace App.Context
{
    public class AppDbContext : App.Common.Data.MSSQL.MSSQLDbContext
    {
        public AppDbContext(IOMode mode = IOMode.Read) : base(new App.Common.Data.MSSQL.MSSQLConnectionString(), mode)
        {
        }
        public System.Data.Entity.DbSet<Role> Roles { get; set; }
    }
}
 
For more information about Angular, Please have a look at Angular2 - Overview

Summary

In this article, I provider you an overview about how to get the list of roles and display them on the ui.

In next article, we continue with how to create and update roles, and go  into more detail of framework (such as: project structure, directives, ....).

For more information about:

 

 

 

ASP.NET 5 và AngularJS Phần 2, Sử dụng MVC 6 Web API

Đây là phần thứ 2 trong phần blog của loạt bài xây dựng ASP.NET 5 (ASP.NET vNext) ứng với AngularJS. Trong loạt bài đăng trên blog, tôi sẽ cho các bạn thấy làm thế nào để có thể tạo ra một ứng dụng Movie đơn giản sử dụng ASP.NET 5, MVC 6, và AngularJS.

Bạn có thể tải về mã đã thảo luận trong bài viết blog này từ GitHub:
github.com/StephenWalther/MovieAngularJSApp

Trong bài viết trên blog này, tôi giải thích làm thế nào để expose dữ liệu từ máy chủ của bạn bằng cách sử dụng Web API MVC 6. Chúng tôi sẽ lấy một danh sách các phim từ các API Web và hiển thị danh sách các phim trong AngularJS của chúng tôi ứng dụng bằng một mẫu AngularJS.

Enabling ASP.NET MVC

Bước đầu tiên là enable MVC cho các ứng dụng. Có hai file mà chúng ta cần phải sửa đổi để cho phép MVC.
Đầu tiên, chúng ta cần phải sửa đổi project.json để nó bao gồm MVC 6:

{
    "webroot": "wwwroot",
    "version": "1.0.0-*",
    "exclude": [
        "wwwroot"
    ],
    "packExclude": [
        "**.kproj",
        "**.user",
        "**.vspscc"
    ],
    "dependencies": {
        "Microsoft.AspNet.Server.IIS": "1.0.0-beta1",
        "Microsoft.AspNet.Mvc": "6.0.0-beta1"
    },
    "frameworks" : {
        "aspnet50" : { },
        "aspnetcore50": { }
    }
}

Các tập tin project.json được sử dụng bởi quản lý gói NuGet để xác định gói theo yêu cầu của dự án. Chúng tôi đã chỉ ra rằng chúng ta cần gói MVC 6 (beta1).
Chúng ta cũng cần phải sửa đổi các tập tin Startup.cs trong thư mục gốc của dự án MovieAngularJS của. Thay đổi nội dung của tập tin Startup.cs để nó trông như thế này:

using System;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.Framework.DependencyInjection;

namespace MovieAngularJSApp
{
    public class Startup
    {

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }

    }
}



Method ConfigureServices () được sử dụng để đăng ký MVC với built-in Dependency Injection framework trong ASP.NET 5. Method Configure() được sử dụng để đăng ký MVC với OWIN.

Tạo Movie Model

Hãy tạo ra một Movie model class mà chúng ta có thể sử dụng để truyền các bộ phim từ máy chủ tới trình duyệt (từ Web API tới AngularJS). Tạo một thư mục Models trong thư mục gốc của dự án MovieAngularJS:

alt

Chú ý rằng bạn tạo các thư mục Models bên ngoài thư mục wwwroot. Source code không thuộc trong wwwroot.
Thêm các class C # tên Movie.cs vào thư mục Models:

using System;

namespace MovieAngularJSApp.Models
{
    public class Movie
    {
        public int Id { get; set; }

        public string Title { get; set; }

        public string Director { get; set; }
    }
}



Tạo Web API Controller

Không giống như các phiên bản trước đó của ASP.NET, lớp controller base được sử dụng cho cả các MVC controllers và Web API controllers. Bởi vì chúng tôi đã kéo trong gói NuGet cho MVC 6, chúng tôi đã sẵn sàng để bắt đầu tạo ra Web API controllers.
Thêm một thư mục API vào thư mục gốc của dự án MovieAngularJS của bạn:

alt

Chú ý rằng bạn không thêm các thư mục API bên trong thư mục wwroot của bạn. Tất cả các mã nguồn của bạn - bao gồm cả mã nguồn cho các controller - nên được đặt bên ngoài thư mục wwwroot.
Tiếp theo, thêm một controller Web API bằng cách nhấn phải chuột vào thư mục API và chọn Add New Item. Chọn Web API Controller Class templateu và đặt tên cho MoviesController.cs điều khiển mới:

alt

Nhập đoạn mã sau cho controller Web API:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc;
using MovieAngularJSApp.Models;

namespace MovieAngularJSApp.API.Controllers
{
    [Route("api/[controller]")]
    public class MoviesController : Controller
    {
        // GET: api/values
        [HttpGet]
        public IEnumerable<Movie> Get()
        {
            return new List<Movie> {
                new Movie {Id=1, Title="Star Wars", Director="Lucas"},
                new Movie {Id=2, Title="King Kong", Director="Jackson"},
                new Movie {Id=3, Title="Memento", Director="Nolan"}
            };
        }

    }
}

Trong đoạn mã trên, Get() action trả về một danh sách các phim. Bạn có thể kiểm tra xem các action đang làm việc bằng cách bắt đầu ứng dụng của bạn và điều hướng đến /api/movies trong trình duyệt của bạn. Trong Google Chrome, bạn sẽ nhận được một đại diện XML của phim:

alt

TẠo AngularJS App

Chúng tôi sẽ hiển thị danh sách các phim sử dụng mẫu AngularJS. Đầu tiên, chúng ta cần phải tạo ra ứng dụng AngularJS.
Nhấp chuột phải vào thư mục Scripts và chọn Add New Item. Chọn mẫu AngularJS Module và nhấn vào nút Add.

alt

Nhập đoạn mã sau cho các module AngularJS mới:

(function () {
    'use strict';

    angular.module('moviesApp', [
        'moviesServices'
    ]);
})();

Đoạn mã trên định nghĩa một AngularJS mô-đun mới có tên moviesApp. Các moviesApp có một sự phụ thuộc vào một module AngularJS tên moviesServices. Chúng tôi tạo ra các moviesServices dưới đây.

Tạo AngularJS Controller

Bước tiếp theo của chúng tôi là tạo ra một Controller AngularJS phía khách hàng. Tạo một thư mục Controller mới trong thư mục Scripts:

alt

Kích chuột phải vào thư mục Controller và chọn Add New Item. Thêm một AngularJS Controller sử dụng $scope mới có tên moviesController.js vào thư mục Controllers. Nhập nội dung sau đây cho các tập tin moviesController.js:

(function () {
    'use strict';

    angular
        .module('moviesApp')
        .controller('moviesController', moviesController);

    moviesController.$inject = ['$scope', 'Movies'];

    function moviesController($scope, Movies) {
        $scope.movies = Movies.query();
    }
})();

Controller AngularJS trên phụ thuộc vào Movies service mà cung cấp danh sách các phim. Các Movies service được chuyển tới Controller sử dụng dependency injection. Các Movies service được thông qua như là tham số thứ hai đến function moviesController() .
Method moviesController.$inject() cần thiết để cho phép các moviesController làm việc với minification. AngularJS dependency injection sẽ ko cần tên của các tham số. Trong bài blog trước, tôi thiết lập Grunt và UglifyJS rút gọn tất cả các tập tin JavaScript. Là một phần của quá trình rút gọn, tất cả các tên tham số chức năng được rút ngắn (đọc sai). phương thức $inject() cho phép dependency injection để làm việc ngay cả khi các tên tham số là đọc sai.

Tạo AngularJS Movies Service

Chúng tôi sẽ sử dụng một AngularJS Movies service để tương tác với Web API. Thêm một thư mục Services mới vào thư mục Scripts. Tiếp theo, kích chuột phải vào thư mục Services và chọn Add New Item. Thêm AngularJS Factory có tên AngularJS moviesService.js vào thư mục dịch vụ:
Nhập đoạn mã sau cho moviesService.js:

(function () {
    'use strict';

    var moviesServices = angular.module('moviesServices', ['ngResource']);

    moviesServices.factory('Movies', ['$resource',
      function ($resource) {
          return $resource('/api/movies/', {}, {
              query: { method: 'GET', params: {}, isArray: true }
          });
      }]);

})();

Các movieServices phụ thuộc vào đối tượng $resource . Các đối tượng $resource thực hiện các yêu cầu Ajax sử dụng một mẫu RESTful.
Trong đoạn mã trên, các moviesServices được kết hợp với /api/movies/ route trên máy chủ. Nói cách khác, khi bạn thực hiện một truy vấn đối với moviesServices trong mã khách hàng của bạn sau đó các Web API MoviesController được viện dẫn để trả về một danh sách các phim.

Tạo AngularJS Template

Bước cuối cùng là tạo ra các mẫu AngularJS hiển thị danh sách các phim. Kích chuột phải vào thư mục wwwroot và thêm một trang HTML mới tên là index.html.

alt

Sửa đổi các nội dung của index.html để nó trông như thế này:

<!DOCTYPE html>
<html ng-app="moviesApp">
<head>
    <meta charset="utf-8" />
    <title>Movies</title>

    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular-resource.js"></script>
    <script src="app.js"></script>
</head>
<body ng-cloak>
    <div ng-controller="moviesController">

        <table>
            <thead>
                <tr>
                    <th>Title</th>
                    <th>Director</th>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="movie in movies">
                    <td>{{movie.Title}}</td>
                    <td>{{movie.Director}}</td>
                </tr>
            </tbody>
        </table>

    </div>
</body>
</html>

Có một số điều mà bạn nên chú ý về tập tin HTML này:
Chú ý rằng các phần tử <html> bao gồm một ng-app directive. directive này liên kết các moviesApp với các tập tin HTML.
Chú ý rằng <script> được sử dụng để thêm angular và angular-resource JavaScript từ CDN của Google.
Chú ý rằng <body> bao gồm một ng-controller directive. directive này liên kết các moviesController với các nội dung của phần tử <div>.
Chú ý rằng các phim được hiển thị bằng cách sử dụng một ng-repeat directive. Tiêu đề và đạo diễn sẽ được hiển thị cho mỗi bộ phim.
Chú ý rằng <body> yếu tố bao gồm một ng-cloak directive. ng-cloak directive giấu một mẫu AngularJS cho đến khi dữ liệu đã được nạp và mẫu đã được trả lại.
Khi bạn mở trang index.html trong trình duyệt của bạn, bạn có thể xem danh sách các phim được hiển thị trong bảng HTML.

alt

Tóm lược

Trong bài viết trên blog này, chúng tôi tạo ra một ứng dụng AngularJS gọi một hành động Web API để lấy danh sách các bộ phim. Chúng tôi hiển thị danh sách các phim trong một bảng HTML bằng cách tận dụng một mẫu AngularJS.
In the next blog post, I discuss how you can take advantage of AngularJS routing to divide a client-side AngularJS app into multiple virtual pages.
Trong bài blog tiếp theo, tôi sẽ thảo luận làm thế nào bạn có thể tận dụng lợi thế của AngularJS routing để chia một client-side AngularJS app vào nhiều trang ảo.

Nguồn:https://viblo.asia/le.anh.duong/posts/AQrMJb8wM40E

AngularJs và Asp.net Mvc

Đối với những lập trình web, có lẽ chúng ta đã được nghe khá nhiều về angularjs, một
javascript framework nổi tiếng do Google phát triển. Đây là thư viện đã được sử dụng
rộng rãi trong các ứng dụng nền web. Điểm mạnh của nó là có thể phát triển theo mô
hình MVC bên phía frontend giúp cấu trúc ứng dụng tốt hơn, dễ bảo trì hơn, có thể unit
test, áp dụng dependency injection, mở rộng html syntax với directive, cơ chế two-way-
binding và rất nhiều ưu điểm khác mà các bạn đã được biết hoặc nghe và đọc rất nhiều
trên các tài liệu. Trong phạm vi bài viết này tôi mong muốn giới thiệu tới các bạn việc
sử dụng angualarjs trong web asp.net MVC. Mục đích để các bạn bắt đầu với angularjs có
thể ứng dụng nó trong một web asp.net mvc đơn giản. Tôi sẽ tạo một ứng dụng tính giá trị
biểu thức từ một chuỗi được nhập vào bởi người dùng, chúng ta sẽ lưu biểu thức và
kết quả tính toán của biểu thức đó vào trong Sql server. Nội dung bài biết giả định rằng các bạn đã có kiến thức cơ bản về asp.net mvc và angularjs.

1. Tạo project asp.net mvc 4 với visual studio 2013

create_project.jpg

<br />

2. Sử dụng Entityframework để thao tác với dữ liệu

install_EF.jpg

3. Tạo một project để thao tác với DB

namespace Exercise2.Data.Entities
{
    public class Expression
    {
        public int Id { get; set; }

        public string ExpressionText { get; set; }

        public float Result { get; set; }
    }
}
 

Ở đây tôi sẽ tạo một class Expression cái mà sẽ mapping với table trong Db.
Tạo một class Exercise context kế thừa lớp DbContext (một class trong Entity Framwork hỗ trợ việc query data) để thực hiện việc kết nối và thao tác trực tiếp với sql server

namespace Exercise2.Data
    {
        public class ExerciseContext : DbContext
        {

            public ExerciseContext()
                : base("name=ExpressionConnectString")
            {

            }

            public DbSet<Expression> Expressions { get; set; }
        }
    }

4. Tạo các action trong HomeController để truy vấn dữ liệu

private readonly ExerciseContext _exerciseContext = new ExerciseContext();
        public JsonResult GetAllExpressions()
        {
            return Json(_exerciseContext.Expressions.OrderByDescending(item => item.Id),
                JsonRequestBehavior.AllowGet);
        }

        public JsonResult CaculateExpressions(string expresstionText)
        {
            try
            {
                expresstionText = expresstionText.Trim();
                // check expression whether is valid or not
                if (Common.IsValidExpression(expresstionText))
                {
                    object result = Common.CaculateExpression(expresstionText);
                    float caculatedResult;
                    if (float.TryParse(result.ToString(), out caculatedResult))
                    {
                        return Json(caculatedResult, JsonRequestBehavior.AllowGet);
                    }
                }

                return Json(new { message = "Expression is invalid", error = 1 });
            }
            catch (Exception ex)
            {
                return Json(new { message = ex.Message, error = 1 });
            }

        }

        public JsonResult SaveExpression(string expresstionText, float result)
        {
            try
            {
                var expresstion = new Expression { ExpressionText = expresstionText, Result = result };
                _exerciseContext.Expressions.Add(expresstion);
                _exerciseContext.SaveChanges();

                return Json(new { message = "Save expression successfuly", error = 0 });
            }
            catch (Exception ex)
            {
                return Json(new { message = ex.Message, error = 1 });
            }

        }

        public JsonResult DeleteExpression(int? id)
        {
            try
            {
                var currentExpression = _exerciseContext.Expressions.Find(id);
                if (currentExpression != null)
                {
                    _exerciseContext.Expressions.Remove(currentExpression);
                    _exerciseContext.SaveChanges();
                    return Json(new { message = "Delete expression successfuly", error = 0 });
                }

                return Json(new { message = "The item does not exist", error = 1 });
            }
            catch (Exception)
            {
                return Json(new { message = "Delete error", error = 1 });
            }

        }

Như vậy chúng ta đã tạo những action cơ bản cho việc thao tác với cơ sở dữ
liệu trong controller của mvc. Bước tiếp theo chúng ta sẽ sử dụng angularjs để
gọi những action này.

5. Xây dựng view với angualar js

Trước tiên chúng ta cần import angualarjs library tới layout.cshml


<script src="~/Scripts/angularJs/angular.min.js"></script>

Trong ứng dụng này cần tới phân trang, vì vậy tôi sử dụng thêm một directive có
sẵn cho việc này đó là ui-bootstrap, chúng ta cần import chúng tới layout

<script src="~/Scripts/angular-ui/ui-bootstrap-tpls.min.js"></script>
<link href="~/Content/bootstrap.min.css" rel="stylesheet" />

Tất nhiên các bạn có thể tự viết một directive phân trang cho riêng mình, các bạn
có thể tìm hiểu sâu hơn về directive tại trang angularjs.org.
Tiếp theo để khởi tạo angular app chúng ta cần tạo một file thông thường đặt tên
app.js. Trong demo này tôi đặt tất cả code angualarjs trong thư mục
Scripts/app. Trong app.js đơn giản tôi chỉ khai báo một angualar module và inject
một module phụ thuộc là ui.boostrap

var exercise2 = angular.module("exercise2", ['ui.bootstrap']);

Tiếp đến chúng ta sẽ tạo một controller để thao tác với view

app.controller("expressionController", function ($scope, $http) {

$scope.expressions = [];

$scope.currentExpression = { expressionText: "", result: 0 };

$scope.errorMessage = "";

$scope.filteredItems = [];

$scope.currentPage = 1;

$scope.numPerPage = 10;

$scope.maxSize = 5;

function fetchData() {

    $http.get("/home/GetAllExpressions").success(function(data) {

        $scope.expressions = data;

    });
}

function clearErrorMessage() {
    $scope.errorMessage = "";
};

fetchData();

$scope.caculateExpresstion = function() {

    $http.post("/home/CaculateExpressions",

        { expresstionText: $scope.currentExpression.expressionText }

    ).success(function (data) {

        if (data.error === 1) {

            $scope.errorMessage = data.message;

        } else {

            $scope.currentExpression.result = data;

            clearErrorMessage();

        }

    }).error(function(error) {

        $scope.errorMessage = error.message;

    });

};

Ở đây tôi sẽ tạo các function để gọi các action trong home controller (code mvc).
Như các bạn đã biết $scope và $http là 2 services được xây dựng sẵn bởi
angularjs library. $Scope để quản lý các models, những object có thể truyền giữa view
và controller, còn $http giúp client có thể giao tiếp với server với đầy đủ các
http method: GET, POST, PUT và DELETE. Như hình bên dưới tôi có thể gọi api lấy tất cả
các biểu thức như sau:

$http.get("/home/GetAllExpressions").success(function(data) {
    $scope.expressions = data;
});

Trong đó, home là tên controller, GetAllExpression là action phía server. Chúng ta cũng
gọi tương tự nếu dùng Web Api của .Net. Để sử dụng những function trong angular
controller tất nhiên chúng ta cũng cần tham chiếu nó trong view

<script src="~/Scripts/app/app.js"></script>
<script src="~/Scripts/app/expressionCtr.js"></script>

Tại home.cshtml cần chỉ định controller mà nó sử dụng, ta dùng cú pháp như sau:

<div class="app-container" ng-controller="expressionController">

Dưới đây là cách binding data mà tôi đã sử dụng:

<div class="app-container" ng-controller="expressionController">
    <p class="app-heading">@ViewBag.Message</p>
    <h2 class="error-message" ng-show="errorMessage.length > 0">
        {{errorMessage}}
    </h2>
    <label>Expression:</label><input type="text" ng-model="currentExpression.expressionText"/>

    <input type="button" value="Caculate" ng-click="caculateExpresstion()" class="btn"/>
    <div class="caculate-result">
        <p>Caculated Result: {{currentExpression.result}}</p>
        <input type="button" value="Save" ng-click="saveExpression()" ng-show="currentExpression.result>0" class="btn"/>
    </div>
    <div class="expression-list">
        <table>
            <tr>
                <th>No</th>
                <th>Expression</th>
                <th>Result</th>
                <th>Action</th>
            </tr>
            <tr ng-repeat="item in filteredItems">
                <td>{{$index+1}}</td>
                <td>{{item.ExpressionText}}</td>
                <td>{{item.Result | number:2}}</td>
                <td><a href="#" ng-click="deleteExpression(item.Id)">Delete</a></td>
            </tr>
        </table>
        <pagination ng-model="currentPage"
                    total-items="expressions.length"
                    max-size="maxSize"
                    boundary-links="true">
        </pagination>

    </div>

</div>

Trên đây là những chia sẻ rất cơ bản để có thể ứng dụng angular trong web asp.net mvc.
Với mong muốn chia sẽ những hiểu biết của mình và angularjs và asp.net mvc tới các
bạn yêu thích chúng. Chúc các bạn thành công.

Link code:
https://drive.google.com/file/d/0B0YcdbjNGk5NTEQxY3hyNjFuZU0/view?usp=sharing

LINK: https://viblo.asia/quanghiepth86/posts/ZabG91ydGzY6