Search This Blog

Wednesday, January 8, 2020

Xây dựng CRUD sử dụng ASP.NET MVC kết nối với ElasticSearch chạy trên môi trường Docker

Tạo mới project ASP.NET MVC Core 3.1


Chọn loại project là Web Application (Model - View - Controller) -> Create.

Install packages:
- Elasticsearch.Net
- NEST


Tạo class ElasticSearchFactory để tạo client connector đến ElasticSearch
 namespace ElasticSearchClient  
 {  
   public class ElasticSearchFactory  
   {  
     public ElasticClient ElasticSearchClient()  
     {  
       var nodes = new Uri[]  
       {  
         new Uri("http://localhost:9200/"),  
       };  
       var connectionPool = new StaticConnectionPool(nodes);  
       var connectionSettings = new ConnectionSettings(connectionPool).DisableDirectStreaming();  
       var elasticClient = new ElasticClient(connectionSettings);  
       return elasticClient;  
     }  
   }  
 }  


Add new MVC controller with read/write actions, đặt tên là CatalogController.



Trong thư mục Models, tạo class Catalog.cs
 namespace ElasticSearchClient.Models  
 {  
   public class Catalog  
   {  
     public string Id { get; set; }  
     [Required]  
     public string Name { get; set; }  
     public string Description { get; set; }  
   }  
 }  


Trong CatalogController, khai báo ElasticSearchFactory
   public class CatalogController : Controller  
   {  
     private readonly ElasticSearchFactory _esFactory;  
     public CatalogController()  
     {  
       _esFactory = new ElasticSearchFactory();  
     }  


Viết đoạn code đoạn truy vấn đơn giản đến ElasticSearch trong action Index.
     // GET: Catalog  
     public async Task<ActionResult> Index()  
     {  
       return View(await DoSearchAsync());  
     }  
     private async Task<List<Catalog>> DoSearchAsync(string name = "")  
     {  
       var response = await (_esFactory.ElasticSearchClient().SearchAsync<Catalog>(s => s  
                   .Index("catalogs")  
                   .Size(50)  
                   .Query(q => q  
                     .Match(m => m  
                       .Field(f => f.Name)  
                       .Query(name)  
                     )  
                   )  
                 ));  
       return response.Hits.Select(s => s.Source).ToList();  
     }  

Tiến hành Add New View cho action Index như hình bên dưới, với:
- Template: List
- Model class: Catalog (tạo ở bước trước)



Kết quả view Index được tạo ra như hình bên dưới

Nhấn F5 để chạy chương trình, sau đó đi đến trang Catalog theo đường dẫn https://localhost:[your-port]/Catalog để xem kết quả.
Chú ý: ElasticSearch phải đang được chạy trước khi run F5. Nếu không sẽ bị báo lỗi ko có kết nối. Xem lại bài trước Cài đặt ElasticSearch với Docker để biết thêm chi tiết.


Viết code để tiến hành thêm mới dữ liệu vào ElasticSearch
     // GET: Catalog/Create  
     public ActionResult Create()  
     {  
       return View();  
     }  
     // POST: Catalog/Create  
     [HttpPost]  
     [ValidateAntiForgeryToken]  
     public ActionResult Create(Catalog catalog)  
     {  
       if (ModelState.IsValid)  
       {  
         try  
         {  
           catalog.Id = Guid.NewGuid().ToString();  
           var response = _esFactory.ElasticSearchClient().Index<Catalog>(catalog, i => i  
                       .Index("catalogs")  
                       .Id(catalog.Id)  
                       .Refresh(Elasticsearch.Net.Refresh.True));  
           return RedirectToAction(nameof(Index));  
         }  
         catch  
         {  
           return View(catalog);  
         }  
       }  
       return View(catalog);  
     }  

Tiến hành Add View cho action Create với
- Template: Create
- Model class: Catalog.



View Create.cshtml được sinh ra như hình

Run F5 và thử Create New
Kết quả sau khi thêm mới dữ liệu


Viết code tương tự cho Edit action
     // GET: Catalog/Edit/5  
     public async Task<ActionResult> Edit(string id)  
     {  
       return View(await GetByIdAsync(id));  
     }  
     private async Task<Catalog> GetByIdAsync(string id)  
     {  
       return (await _esFactory.ElasticSearchClient().GetAsync<Catalog>(id, i =>   
               i.Index("catalogs"))).Source;  
     }  
     // POST: Catalog/Edit/5  
     [HttpPost]  
     [ValidateAntiForgeryToken]  
     public async Task<ActionResult> Edit(int id, Catalog catalog)  
     {  
       if (ModelState.IsValid)  
       {  
         try  
         {  
           var response = await _esFactory.ElasticSearchClient().UpdateAsync<Catalog>(catalog, i => i  
                       .Index("catalogs")  
                       .Refresh(Elasticsearch.Net.Refresh.True));  
           return RedirectToAction(nameof(Index));  
         }  
         catch  
         {  
           return View(catalog);  
         }  
       }  
       return View(catalog);  
     }  

Viết code tương tự cho Delete action
     // GET: Catalog/Delete/5  
     public async Task<ActionResult> Delete(string id)  
     {  
       return View(await GetByIdAsync(id));  
     }  
     // POST: Catalog/Delete/5  
     [HttpPost]  
     [ValidateAntiForgeryToken]  
     public async Task<ActionResult> Delete(string id, Catalog catalog)  
     {  
       try  
       {  
         var response = await _esFactory.ElasticSearchClient().DeleteAsync<Catalog>(id, i => i  
                 .Index("catalogs")  
                 .Refresh(Elasticsearch.Net.Refresh.True));  
         return RedirectToAction(nameof(Index));  
       }  
       catch  
       {  
         return View(catalog);  
       }  
     }  

Như vậy là chúng ta vừa hoàn tất viết một CRUD đơn giản sử dụng ASP.NET MVC kết nối đến ElasticSearch chạy trong môi trường Docker.

Source code: https://github.com/chkien0911/blog_examples/tree/master/ElasticSearchClient

Bài tiếp theo chúng ta sẽ thực hiện query đến ElasticSearch.

Cài đặt ElasticSearch với Docker

- Chạy single node cluster với Docker:
Mở CommandLine hoặc PowerShell, copy đoạn code bên dưới để pull và run ElasticSearch image trên Docker.
docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.5.1

Note: vào trang https://www.docker.elastic.co/ để kiểm tra phiên bản mới nếu có.

Giao diện PoweShell sau khi chạy lệnh pull và run ElasticSearch docker image thành công



Để kiểm tra ElasticSearch đã được run trên Docker hay chưa, truy cập vào địa chỉ http://localhost:9200 và kiểm tra kết quả như hình bên dưới:

- Sử dụng Posman để bắt đầu kiểm tra ElasticSearch chạy trên Docker
Để index một document mới, tạo một POST request trên Posman như bên dưới:
+ Request type: POST
+ URL: localhost:9200/catalogs/_doc/1?pretty, với catalogs là index name, 1 là Id muốn tạo.
+ Body data:
{
"id": "1",
"name":"Car",
"description": "Car description"
}
Kết quả sau khi Run như hình bên dưới:


Để xem lại document vừa tạo, sử dụng Get request như bên dưới:
+ Request type: GET
+ URL: localhost:9200/catalogs/_doc/1?pretty, với catalogs là index name, 1 là Id muốn lấy dữ liệu.


Làm tương tự cho các request khác như Update, Delete...

Bài tiếp theo chúng ta sẽ ASP.NET MVC kết nối ElasticSearch chạy trên môi trường Docker

Shared to be Shared

Sunday, July 30, 2017

ASP.NET MVC với AngularJS 2. Bài 2: Tìm hiểu về cấu trúc AngularJS 2, Component và Module.


1. Tạo project ASP.NET MVC sử dụng AngularJS 2.
Tạo mới một project ASP.NET MVC có tên AngularJS4_2 và cấu hình để project của chúng ta sử dụng AngularJS 2 tương tự như bài đầu tiên.
Bài viết đầu tiên ở đây http://kienchu.blogspot.com/2017/07/aspnet-mvc-voi-angularjs-2-version-4.html

Sau đó tạo mới 1 folder app. Chúng ta có project ASP.NET MVC với AngularJS 2 như hình bên dưới.

Click phải lên file package.json, chọn Restore Packages để tải về các package cần thiết cho AngularJS 2.


2. Tìm hiểu về Component và Module trong AngularJS 2.
AngularJS 2 là một binding javascript framework, mục đích chính là để binding data giữa các object trong Javascript và các HTML elements, có thể nói nó sử dụng mô hình MVVM (Model - View - ViewModel) hay MVW (Model - View - Whatever). Hình bên dưới mô tả cái nhìn cơ bản về AngularJS 2.
Vì mục tiêu chính của AngularJS 2 là binding code, data giữa ModelView, và để làm việc này trong AngularJS 2 sử dụng khái niệm Component, trong các dự án lớn hơn thì số lượng Component sẽ rất nhiều, nên để quản lý, nhóm các Component lại thì AngularJS 2 sử dụng khái niệm Module. Hình bên dưới thể hiện mối quan hệ quan hệ giữa các thành phần trong AngularJS 2.
- Component: chứa các binding logic để bind code, data giữa Model và View.
- Module: nhóm, quản lý các Component.
OK!. Để dễ hình dung, Kiên sẽ tạo ví dụ minh họa.
Trong folder app, tạo mới 4 folder:
- Model:chứa các Model business TypeScript class.
- Component: chứa các Component dùng để binding data giữa View và Model.
- Module: chứa các Module dùng để nhóm, quản lý các Component.
- View: chứa các HTML template dùng để hiển thị giao diện người dùng (UI).


Trong thư mục Model, tạo mới một file TypeScript và đặt tên là StudentModel.ts.

Bên trong file StudentModel.ts, cập nhật code như bên dưới, đơn giản là khai báo 3 property Id, FirstName, LastName thể hiện các thuộc tính cần có của một Student.
Điểm chú ý là chúng ta cần sử dụng keyword "export" để cho các Component khác có thể nhìn thấy và sử dụng được StudentModel này. Để dễ hình dung, cứ tưởng tượng "export" giống như "public" trong OOP.
 export class StudentModel {  
   Id: string = "";  
   FirstName: string = "";  
   LastName: string = "";  
 }  
Trong thư mục Component, tạo mới một file TypeScript và đặt tên là StudentComponent.ts.
Trong StudentComponent.ts, chúng ta cần import package "@angular/core" và "StudentModel" đã tạo ra trước đó.
 import { Component } from "@angular/core";  
 import { StudentModel } from "../Model/StudentModel";  
Ở trên chúng ta nói Component dùng để binding giữa Model và View, chính vì vậy:
- B1: Chúng ta cần chỉ ra StudentComponent của chúng ta liên kết với View nào. Trong AngularJS 2 có khái niệm Component Metadata Attribute phục vụ cho việc này. Một Component Metadata Attribute khai báo bắt đầu bằng "@Component" sử dụng các Metadata như "selector", "template", "templateUrl"...để xác định View.
 @Component({  
   selector: "app",  
   templateUrl:"../../View/StudentTemplate.html"  
 })  
Đoạn khai báo binding code này mang ý nghĩa: hiển thị nội dung trong file HTML template theo đường dẫn "../../View/StudentTemplate.html" vào đoạn placeholder tag <app></app> được khai báo nơi nào đó trên View.
- B2: Khai báo Model để View có thể nhìn thấy và sử dụng: bằng cách khởi tạo StudentComponent, bên trong khai báo một Model có tên là CurrentStudent.
 export class StudentComponent {  
   CurrentStudent: StudentModel = new StudentModel();  
 }  
Chú ý thêm keyword "export" để các Module có thể nhìn thấy và sử dụng StudentComponent này.
Hình bên dưới là nội dung của StudentComponent.ts.

Tiếp theo trong thư mục View, tạo mới một file HTML template và đặt tên là StudentTemplate.html. Nội dung bên trong đơn thuần là code HTML và cơ chế binding data.
 <div class="container">  
   <div class="row>">  
     <div class="col-sm-6">  
       Student Id:<br />  
       <input type="text" [(ngModel)]="CurrentStudent.Id" /><br />  
       First Name:<br />  
       <input type="text" [(ngModel)]="CurrentStudent.FirstName" /><br />  
       Last Name:<br />  
       <input type="text" [(ngModel)]="CurrentStudent.LastName" /><br />  
     </div>  
     <div class="col-sm-6">  
       Output:<br />  
       {{CurrentStudent.Id}}<br />  
       {{CurrentStudent.FirstName}}<br />  
       {{CurrentStudent.LastName}}<br />  
     </div>  
   </div>  
 </div>  

Nói về binding data, trong StudentComponent đã khởi tạo một Model có tên CurrentStudent, chính vì vậy, trong View đã có thể truy xuất được Model của chúng ta.
AngularJS 2 sử dụng khái niệm gọi là Directives để bind data. Ví dụ [(ngModel)]="CurrentStudent.Id" giúp chúng ta binding data trong property CurrentStudent.Id đến View và ngược lại, tức là hai chiều (2 ways), tức là nếu Model có giá trị, nó sẽ hiển thị lên View, và trên View nếu chúng ta thay đổi giá trị, nó cũng sẽ được cập nhật lại cho Model. Chú ý là CurrentStudent chính là Model mà chúng ta đã khởi tạo trong StudentComponent.
Chi tiết hơn về cơ chế binding:
- (...): gửi dữ liệu từ View -> Model (1 way).
- [...]: gửi dữ liệu từ Model -> View (1 way).
- [(...)]: gửi dữ liệu từ View -> ModelModel -> View (2 ways).
Ngoài ra chúng ta có thể sử dụng {{...}} chỉ để hiển thị dữ liệu từ Model -> View.Ví dụ: {{CurrentStudent.Id}}.

Tiếp theo chúng ta cần tạo Module để nhóm các Component cũng như các Service khác. Trong thư mục Module, tạo mới một file TypeScript và đặt tên là StudentModule.ts.
Trong StudentModule.ts, cần import các package cần thiết như BrowserModule (có các component để viết các điều kiện If, For... trên View), FormsModule (cung cấp các Directives như NgModel, expression..), NgModule (giúp chúng ta định nghĩa NgModule bên trong StudentModule), đồng thời import StudentComponent mà chúng ta đã tạo ra ở bước trên.
 import { BrowserModule } from "@angular/platform-browser";  
 import { FormsModule } from "@angular/forms";  
 import { NgModule } from "@angular/core";  
 import { StudentComponent } from "../Component/StudentComponent";  
NgModule gồm có 3 thuộc tính:
- Imports: định nghĩa các module nếu chúng đang được sử dụng đâu đó trong các component của chúng ta.
- Declarations: định nghĩa các component của module này. Đối với StudentModule thì hiện tại chỉ có 1 StudentComponent.
- Bootstrap: định nghĩa Component đầu tiên sẽ chạy. Ví dụ chúng ta có nhiều Component như HomeComponent, AccountComponent...thì HomeComponent rất có thể sẽ được định nghĩa trong Bootstrap.
Bên dưới là đoạn khai báo NgModule của StudentModule.ts
 @NgModule({  
   imports: [BrowserModule, FormsModule],  
   declarations: [StudentComponent],  
   bootstrap: [StudentComponent]  
 })  
Cuối cùng là chúng ta khởi tạo và export StudentModule.
 export class StudentModule {}  
Full code của StudentModule.ts.

OK!. Vậy là chúng ta đã tạo Model, View, Component (1 hoặc nhiều) và Component này được nhúng trong Module. Và trong 1 project chúng ta vẫn có thể có nhiều Module. Nhưng chúng ta vẫn cần có một Module dùng để chạy lần đầu tiên (startup module), đó có thể là file main.ts mà chúng ta đã tạo trong bài 1.
Xem lại bài 1 ở đây: http://kienchu.blogspot.com/2017/07/aspnet-mvc-voi-angularjs-2-version-4.html

Chúng ta có thể copy lại file này hoặc tại thư mục gốc, tạo mới một file TypeScript và đặt tên là main.ts có nội dung như bên dưới
 import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";  
 import { StudentModule } from "./app/Module/StudentModule";  
 platformBrowserDynamic().bootstrapModule(StudentModule);  
Chúng ta định nghĩa là Module nào sẽ được chạy đầu tiên bằng cách sử dụng
 platformBrowserDynamic().bootstrapModule(StudentModule);  
Cuối cùng build project và kiểm tra các file .js.js.map đã được sanh ra chưa.

3. Chạy thử và test.
Mở file Views\Shared\_Layout.cshtml, nhúng các file js cần thiết trong tag <head></head>
 <head>  
   <meta charset="utf-8" />  
   <meta name="viewport" content="width=device-width, initial-scale=1.0">  
   <title>@ViewBag.Title - My ASP.NET Application</title>  
   <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />  
   <link href="~/Content/bootstrap.min.css" rel="stylesheet" type="text/css" />  
   <script src="~/Scripts/modernizr-2.6.2.js"></script>  

   <script src="~/node_modules/core-js/client/shim.min.js"></script>  
   <script src="~/node_modules/zone.js/dist/zone.js"></script>  
   <script src="~/node_modules/systemjs/dist/system.src.js"></script>  
   <script src="~/node_modules/reflect-metadata/Reflect.js"></script>  
   <script src="~/systemjs.config.js"></script>  
   <script>  
    System.import('main.js').catch(function(err){ console.error(err); });  
   </script>  
 </head>  
- shim.min.js: đảm bảo ES6 javascript có thể chạy trên các browser cũ.
- zone.js: đảm bảo các task Async hoạt động trong 1 context.
- reflect.js: apply meda-data trong các javascript class như @NgModule, @NgComponent
- system.src.js: support để load các file js sử dụng các module như commonjs, AMD, UMD.

Mở file Views\Home\Index.cshtml, bổ sung placeholder tag <app></app> đã được khai báo selector trong file StudentComponent.ts.

Cuối cùng build và chạy thử (F5).
Mỗi khi chúng ta nhập giá trị cho các fied Student Id, First Name, Last Name, thì trong phần Output sẽ hiển thị giá trị ngay lập tức.


OK!. Kết thúc Bài 2: Tìm hiểu cấu trúc AngularJS 2, Componnent, Module trong loạt bài ASP.NET MVC và AngularJS2. Các bài viết tiếp theo sẽ được cập nhật trong thời gian tới.

Source code tham khảo Ở ĐÂY.

Bài 1 - Cài đặt và cấu hình AngularJS 2 trong ASP.NET MVC

------
Share to be shared
------

Wednesday, July 26, 2017

Cải thiện Performance của site ASP.NET MVC sử dụng Async Partial View

Trong website ASP.NET MVC, thông thường chúng ta sử dụng ActionResult để trả về kết quả và hiển thị lên View. Đôi khi kết quả trả về phải xử lí qua nhiều bước. Điều này có thể ảnh hưởng đến Performance của website khi phải chờ một khoảng thời gian nhất định để hiển thị được kết quả mong muốn.
Trong bài viết này, Kiên sẽ giới thiệu một cách xử lí khá là đơn giản, có thể áp dụng ngay trước khi chúng ta nghĩ đến các giải pháp tối ưu Performance khác. Đó là áp dụng nguyên tắc CHIA ĐỂ TRỊ, tức là thay vì chúng ta xử lí tất cả cùng một lúc và hiển thị kết quả đồng thời (Synchronous), thì chúng ta chia nhỏ các xử lí ra thực hiện song song (Asynchronous) và hiển thị kết quả trả về của từng xử lí đó. Trong ASP.NET MVC, chúng ta có thể sử dụng PartialView để làm việc này.

Lấy ví dụ hãy hình dung chúng ta có trang Dashboard hiển thị hai danh sách User Course như hình bên dưới:

Hãy bắt đầu với cách xử lí trả về một kết quả duy nhất. Giả sử chúng ta có các class Model như bên dưới:
   public class User  
   {  
     public string Id { get; set; }  
     public string Name { get; set; }  
   }  
   public class Course  
   {  
     public string Id { get; set; }  
     public string Title { get; set; }  
   }  
   public class Dashboard  
   {  
     public List<User> Users { get; set; }  
     public List<Course> Courses { get; set; }  
     // something more  
   }  

Chúng ta cũng có một Action xử lí và trả kết quả lên trang Dashboard.
   public class HomeController : Controller  
   {  
     public ActionResult Index()  
     {  
       // get dashboard data model  
       var dashBoardModel = new Dashboard();  
       dashBoardModel.Users = GetUsers();  
       dashBoardModel.Courses = GetCourses();  

       // return dashboard page with data model  
       return View(dashBoardModel);  
     }  
   }  

Dữ liệu hiển thị trên trang Dashboard bao gồm danh sách User và Course được xử lí trực tiếp trong action Index.

Method GetCourses: trả về danh sách Courses, đồng thời thêm dừng một khoảng thời gian là 2s để minh họa.
     private List<Course> GetCourses()  
     {  
       System.Threading.Thread.Sleep(2000);  
       return new List<Course>()  
       {  
         new Course { Id = Guid.NewGuid().ToString(), Title = "Chemistry"},  
         new Course { Id = Guid.NewGuid().ToString(), Title = "Math"},  
       };  
     }  

Tương tự là method GetUsers cũng dừng một khoảng thời gian 2s.
     private List<User> GetUsers()  
     {  
       System.Threading.Thread.Sleep(2000);  
       return new List<User>()  
       {  
         new User { Id = Guid.NewGuid().ToString(), Name = "Chu Hong Kien" },  
         new User { Id = Guid.NewGuid().ToString(), Name = "Bill Gates" },  
       };  
     }  

Nội dung trên view Index:
 @model AsyncPartialView.Controllers.Dashboard  
 @{  
   ViewBag.Title = "Index";  
 }  
 <h2>User</h2>  
 <table class="table">  
   <tr>  
     <th>Id</th>  
     <th>Name</th>  
   </tr>  
 @foreach (var item in Model.Users) {  
   <tr>  
     <td>@item.Id</td>  
     <td>@item.Name</td>  
   </tr>  
 }  
 </table>  
 <h2>Course</h2>  
 <table class="table">  
   <tr>  
     <th>Id</th>  
     <th>Title</th>  
   </tr>  
   @foreach (var item in Model.Courses)  
   {  
     <tr>  
       <td>@item.Id</td>  
       <td>@item.Title</td>  
     </tr>  
   }  
 </table>  

Ok! Chạy thử và đây là kết quả xử lí của trang Dashboard.
Thời gian phản hồi để tải hết nội dung trang (HTML) là ~ 4s. Bởi vì chúng ta có hai xừ lí, mỗi xử lí 2s, và vì chúng xử lí đồng bộ (Synchronous) nên tổng thời gian hoàn thành để hiển thị được nội dung là 4s. Khá là bad nếu bắt người dùng ngồi chờ và không làm gì trong khoảng thời gian này. :(.

Next. Giải pháp của chúng ta là:
- Bước 1: Tách hai xử lí GetCourses & GetUsers vào trong hai Action trả về hai Partial View riêng biệt. Action Index bây giờ không có bất kì xử lí nào. Để tiện việc so sánh nên Kiên return view IndexAsync.
     public ActionResult Index()  
     {  
       // get dashboard data model  
       var dashBoardModel = new Dashboard();  
       //dashBoardModel.Users = GetUsers();  
       //dashBoardModel.Courses = GetCourses();  
       // return dashboard page with data model  
       return View("IndexAsync", dashBoardModel);  
     }  
     public ActionResult _GetCourses()  
     {  
       var courses = GetCourses();  
       return PartialView("_GetCourses", courses);  
     }  
     public ActionResult _GetUsers()  
     {  
       var courses = GetUsers();  
       return PartialView("_GetUsers", courses);  
     }  

- Bước 2: Tạo PartialView cho từng Action.
PartialView _GetUsers.cshtml:
 @model IEnumerable<AsyncPartialView.Controllers.User>  
 <table class="table">  
   <tr>  
     <th>Id</th>  
     <th>Name</th>  
   </tr>  
   @foreach (var item in Model)  
   {  
     <tr>  
       <td>@item.Id</td>  
       <td>@item.Name</td>  
     </tr>  
   }  
 </table>  

PartialView _GetCourses.cshtml:
 @model IEnumerable<AsyncPartialView.Controllers.Course>  
 <table class="table">  
   <tr>  
     <th>Id</th>  
     <th>Title</th>  
   </tr>  
   @foreach (var item in Model)  
   {  
     <tr>  
       <td>@item.Id</td>  
       <td>@item.Title</td>  
     </tr>  
   }  
 </table>  

- Bước 3: Cập nhật View IndexAsync. Chúng ta tạo 2 thẻ div, có cùng class partial-content (tiện cho việc xử lí ở bước sau), data-url sẽ là URL đến hai Action (trả về 2 Partial View) ở bước 1. Có thể thêm "Loading..." hay hình gif gì đó để tăng trải ngiệm người dùng.
 @model AsyncPartialView.Controllers.Dashboard  
 @{  
   ViewBag.Title = "Index";  
 }  
 <h2>User</h2>  
 <div class="partial-content" data-url="@Url.Action("_GetUsers", "Home")">  
   Loading...  
 </div>  
 <h2>Course</h2>  
 <div class="partial-content" data-url="@Url.Action("_GetCourses", "Home")">  
   Loading...  
 </div>  

- Bước 4: Sử dụng JQuery để call & load từng action.
 <script type="text/javascript">  
   $(document).ready(function () {  
     $(".partial-content").each(function (index, item) {  
       var url = $(item).data("url");  
       if (url && url.length > 0) {  
         $(item).load(url);  
       }  
     })  
   })  
 </script>  

- Bước 5: Kết quả
Khi mới mở trang, chúng ta sẽ thấy nội dung xử lí vẫn chưa được hiển thị mà chỉ xuất hiện trạng thái Loading...(cool)

Và sau đó là kết quả hiển thị như bình thường

Nhưng thời gian phản hồi đã giảm đi phân nửa (1/2), chỉ còn ~ 2s.

Thực chất tổng thời gian xử lí là xấp xỉ như nhau (~4s), nhưng vì chúng xử lí bất đồng bộ (Asynchronous) và song song, nên ngay khi anh nào xử lí xong, chúng sẽ hiển thị kết quả ngay mà không cần phải chờ đợi nhau nữa.

OK! Đây là một trong các phương pháp cải thiện Performance cho website ASP.NET MVC của chúng ta trong trường hợp trang của chúng ta có nhiều xử lí cùng một lúc, đồng thời cũng làm tăng trải nghiệm người dùng (UX).

Source code minh họa Ở ĐÂY.
-----
Share to be shared
-----

Sunday, July 23, 2017

ASP.NET MVC với AngularJS 2. Bài 1: Cài đặt và cấu hình AngularJS 2 trong Visual Studio 2015 Community.

1. Tạo mới project ASP.NET MVC và AngularJS 2.

Mở Visual Studio, ở đây Kiên đang sử dụng Visual Studio 2015 Community và tiến hành tạo mới project ASP.NET Web Application (.NET Framework).

Đặt tên cho project là AngularJs4.

Trong phần Select a template, chọn MVC. Sau đó click OK.

Chờ một lúc để Visual Studio khởi tạo project. Hình bên dưới là cấu trúc project ASP.NET MVC với AngularJS 2 đã được tạo ra.

2. Setup môi trường cho AngularJS 2.
- Thêm package.json (npm Configuration file). File này sử dụng npm (NodeJS) để load các packages cần thiết dùng cho AngularsJS 2.
Trong trường hợp các bạn chưa cài đặt NodeJS, vào https://nodejs.org/ để cài đặt phiên bản mới nhất.
Ngoài ra để tìm hiểu thêm về npm, đọc ở đây.

- Thêm tsconfig.json (TypeScript configuration file). File này dùng để cấu hình trong việc biên dịch các file TypeScript (ts) sang Javascript (js) để chúng có thể chạy được trên các browser.
Bạn cũng cần phải cài đặt phiên bản mới nhất của TypeScript Ở ĐÂY, hoặc nếu bạn đã cài đặt NodeJS thì có thể dùng npm và gõ lệnh:
npm install -g TypeScript.

- Thêm systemjs.config.js. File này dùng để cấu hình client-side load các packages lên browser.

Để cấu hình nhanh và chính xác, cách tốt nhất là vào trang AngularJS 2 Tutorial, download phần example và tiến hành copy các files đã nói ở trên vào project của chúng ta.
Hình bên dưới là cấu trúc thư mục của phần example đã download và giải nén.

Copy các files:
package.json
tslink.json
bsconfig.json
src/tsconfig.json
src/systemjs.config.js
src/systemjs-angular-loader.js
vào project ASP.NET MVC của chúng ta.

Trong file package.json, tìm và xóa các đoạn text "src" vì thư mục gốc không phải là src như trong phần example. Nội dụng file sau khi cập nhật như bên dưới:
 {  
  "name": "angular-io-example",  
  "version": "1.0.0",  
  "private": true,  
  "description": "Example project from an angular.io guide.",  
  "scripts": {  
   "test:once": "karma start karma.conf.js --single-run",  
   "build": "tsc -p /",  
   "serve": "lite-server -c=bs-config.json",  
   "prestart": "npm run build",  
   "start": "concurrently \"npm run build:watch\" \"npm run serve\"",  
   "pretest": "npm run build",  
   "test": "concurrently \"npm run build:watch\" \"karma start karma.conf.js\"",  
   "pretest:once": "npm run build",  
   "build:watch": "tsc -p / -w",  
   "build:upgrade": "tsc",  
   "serve:upgrade": "http-server",  
   "build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup-config.js",  
   "serve:aot": "lite-server -c bs-config.aot.json",  
   "build:babel": "babel -d --extensions \".es6\" --source-maps",  
   "copy-dist-files": "node ./copy-dist-files.js",  
   "i18n": "ng-xi18n",  
   "lint": "tslint .//**/*.ts -t verbose"  
  },  
  "keywords": [],  
  "author": "",  
  "license": "MIT",  
  "dependencies": {  
   "@angular/animations": "~4.2.0",  
   "@angular/common": "~4.2.0",  
   "@angular/compiler": "~4.2.0",  
   "@angular/compiler-cli": "~4.2.0",  
   "@angular/core": "~4.2.0",  
   "@angular/forms": "~4.2.0",  
   "@angular/http": "~4.2.0",  
   "@angular/platform-browser": "~4.2.0",  
   "@angular/platform-browser-dynamic": "~4.2.0",  
   "@angular/platform-server": "~4.2.0",  
   "@angular/router": "~4.2.0",  
   "@angular/tsc-wrapped": "~4.2.0",  
   "@angular/upgrade": "~4.2.0",  
   "angular-in-memory-web-api": "~0.3.2",  
   "core-js": "^2.4.1",  
   "rxjs": "^5.1.0",  
   "systemjs": "0.19.39",  
   "zone.js": "^0.8.4"  
  },  
  "devDependencies": {  
   "@types/angular": "^1.5.16",  
   "@types/angular-animate": "^1.5.5",  
   "@types/angular-cookies": "^1.4.2",  
   "@types/angular-mocks": "^1.5.5",  
   "@types/angular-resource": "^1.5.6",  
   "@types/angular-route": "^1.3.2",  
   "@types/angular-sanitize": "^1.3.3",  
   "@types/jasmine": "2.5.36",  
   "@types/node": "^6.0.45",  
   "babel-cli": "^6.16.0",  
   "babel-preset-angular2": "^0.0.2",  
   "babel-preset-es2015": "^6.16.0",  
   "canonical-path": "0.0.2",  
   "concurrently": "^3.0.0",  
   "http-server": "^0.9.0",  
   "jasmine": "~2.4.1",  
   "jasmine-core": "~2.4.1",  
   "karma": "^1.3.0",  
   "karma-chrome-launcher": "^2.0.0",  
   "karma-cli": "^1.0.1",  
   "karma-jasmine": "^1.0.2",  
   "karma-jasmine-html-reporter": "^0.2.2",  
   "karma-phantomjs-launcher": "^1.0.2",  
   "lite-server": "^2.2.2",  
   "lodash": "^4.16.2",  
   "phantomjs-prebuilt": "^2.1.7",  
   "protractor": "~5.1.0",  
   "rollup": "^0.41.6",  
   "rollup-plugin-commonjs": "^8.0.2",  
   "rollup-plugin-node-resolve": "2.0.0",  
   "rollup-plugin-uglify": "^1.0.1",  
   "source-map-explorer": "^1.3.2",  
   "tslint": "^3.15.1",  
   "typescript": "~2.3.2"  
  },  
  "repository": {}  
 }  

Tương tự trong file bsconfig.json, tìm và xóa đoạn text "src".
 {  
  "server": {  
   "baseDir": "",  
   "routes": {  
    "/node_modules": "node_modules"  
   }  
  }  
 }  

File tsconfig.json.
 {  
  "compilerOptions": {  
   "target": "es5",  
   "module": "commonjs",  
   "moduleResolution": "node",  
   "sourceMap": true,  
   "emitDecoratorMetadata": true,  
   "experimentalDecorators": true,  
   "lib": [ "es2015", "dom" ],  
   "noImplicitAny": true,  
   "suppressImplicitAnyIndexErrors": true,  
   "typeRoots": [  
    "../node_modules/@types/"  
   ]  
  },  
  "compileOnSave": true,  
  "exclude": [  
   "node_modules/*",  
   "**/*-aot.ts"  
  ]  
 }  

File systemjs.config.js.
 /**  
  * System configuration for Angular samples  
  * Adjust as necessary for your application needs.  
  */  
 (function (global) {  
  System.config({  
   paths: {  
    // paths serve as alias  
    'npm:': 'node_modules/'  
   },  
   // map tells the System loader where to look for things  
   map: {  
    // our app is within the app folder  
    'app': 'app',  
    // angular bundles  
    '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',  
    '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',  
    '@angular/core': 'npm:@angular/core/bundles/core.umd.js',  
    '@angular/common': 'npm:@angular/common/bundles/common.umd.js',  
    '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',  
    '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',  
    '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',  
    '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',  
    '@angular/http': 'npm:@angular/http/bundles/http.umd.js',  
    '@angular/router': 'npm:@angular/router/bundles/router.umd.js',  
    '@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',  
    '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',  
    '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',  
    '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',  
    // other libraries  
    'rxjs':           'npm:rxjs',  
    'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'  
   },  
   // packages tells the System loader how to load when no filename and/or no extension  
   packages: {  
    app: {  
     main: './main.js',  
     defaultExtension: 'js',  
     meta: {  
      './*.js': {  
       loader: 'systemjs-angular-loader.js'  
      }  
     }  
    },  
    rxjs: {  
     defaultExtension: 'js'  
    }  
   }  
  });  
 })(this);  

Click phải lên file package.json, chọn Restore Packages để tiến hành cài đặt các packages của AngularJS 2 dùng cho project ASP.NET MVC của chúng ta.



Chờ trong giây lát, khi quá trình cài đặt package hoàn tất, trong Solution Explorer, click Show All Files, chúng ta sẽ thấy có một thư mục node_modules xuất hiện như hình bên dưới. Chú ý là chúng ta không include thư mục node_modules này vào project ASP.NET MVC.


3. Thêm các file TypeScript (ts) để bootstrap Angular 2 vào ASP.NET MVC.
Trong project của chúng ta, tạo mới folder và đặt tên là app như trong file systemjs.config.js đã khai báo.
   map: {  
    // our app is within the app folder  
    'app': 'app', 
Bên trong folder app, tiến hành thêm các files TypeScript (ts):
- app.module.ts: dùng để định nghĩa ra các module.
 import { NgModule } from '@angular/core';  
 import { BrowserModule } from '@angular/platform-browser';  
 import { AppComponent } from './app.component';  
 @NgModule({  
   imports: [BrowserModule],  
   declarations: [AppComponent],  
   bootstrap: [AppComponent]  
 })  
 export class AppModule { }  
- app.component.ts: dùng để định nghĩa các component dùng cho module.
 import { Component } from '@angular/core';  
 @Component({  
   selector: 'app',  
   template: '<h1>Page Says: {{text}}</h1>'  
 })  
 export class AppComponent { text = 'Hello World'; }  
Sau đó trong thư mục gốc, tạo file main.ts. Đây là đoạn bootstrap code mà AngularJS 2 dùng để load các module.
 import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';  
 import { AppModule } from './app/app.module';  
 platformBrowserDynamic().bootstrapModule(AppModule);  
Tiến hành build project, lúc này project sẽ xuất hiện các file .js.js.map như hình bên dưới:


4. Cập nhật các file cshtml để load và render Angular 2.
Mở file Views\Shared\_Layout.cshtml, trong tag <head></head> bổ sung các scripts như đoạn code bên dưới.
 <head>  
   <meta charset="utf-8" />  
   <meta name="viewport" content="width=device-width, initial-scale=1.0">  
   <title>@ViewBag.Title - My ASP.NET Application</title>  
   @Styles.Render("~/Content/css")  
   @Scripts.Render("~/bundles/modernizr")  

   <script src="~/node_modules/core-js/client/shim.min.js"></script>  
   <script src="~/node_modules/zone.js/dist/zone.js"></script>  
   <script src="~/node_modules/systemjs/dist/system.src.js"></script>  
   <script src="systemjs.config.js"></script>  
   <script>  
     System.import('main.js').catch(function(err){ console.error(err); });  
   </script>  

 </head>  

Mở file Views\Home\Index.cshtml, cập nhật lại như đoạn code bên dưới:
 @{  
   ViewBag.Title = "Home Page";  
 }  
 <app>loading...</app>

Tag <app></app>  đã được định nghĩa trong selector: 'app' trong file app.component.ts.
 import { Component } from '@angular/core';  
 @Component({  
   selector: 'app',  
   template: '<h1>Page Says: {{text}}</h1>'  
 })  
 export class AppComponent { text = 'Hello World'; }  

Cuối cùng tiến hành build và chạy thử (F5).

Source code tham khào ở đây


--------
Share to be shared
--------

Saturday, April 15, 2017

Tạo demo áp dụng Dependency Injection trong ASP.NET MVC

Bước 1: Tạo project ASP.NET MVC như hình bên dưới, đặt tên là DemoDependencyInjection.

Chọn template cho project là MVC, sau đó click OK để tạo project.

Bước 2: Tạo mới một Controller.

Chọn MVC5 Controller - Empty.

Đặt tên là DIController.

DIController sau khi tạo xong.
 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Web;  
 using System.Web.Mvc;  
 namespace DemoDependencyInjection.Controllers  
 {  
   public class DIController : Controller  
   {  
     // GET: DI  
     public ActionResult Index()  
     {  
       return View();  
     }  
   }  
 }  

Bước 3: Cài đặt Nuget package Microsoft.Extensions.DependencyInjection.
Trong cửa sổ Package Manager Console, nhập "install-package Microsoft.Extensions.DependencyInjection".

Bước 4: Cập nhật file Startup.cs.
Ban đầu khi tạo mới project thì bên trong file Startup.cs chúng ta chỉ có một method Configuration.
 using Microsoft.Owin;  
 using Owin;  
 [assembly: OwinStartupAttribute(typeof(DemoDependencyInjection.Startup))]  
 namespace DemoDependencyInjection  
 {  
   public partial class Startup  
   {  
     public void Configuration(IAppBuilder app)  
     {  
       ConfigureAuth(app);  
     }  
   }  
 }  

Chúng ta bổ sung thêm method ConfigureServices như bên dưới.
 public void ConfigureServices(IServiceCollection services)  
 {  
 }  

Hãy nhớ bổ sung namespace "Microsoft.Extensions.DependencyInjection" cho class Startup.

Cập nhật lại method Configuration như bên dưới
 public void Configuration(IAppBuilder app)  
 {  
    ConfigureAuth(app);  

    var services = new ServiceCollection();  
    ConfigureServices(services);  
 }  

Tạo class MyDepedencyResolver kế thừa từ interface IDependencyResolver và bổ sung đoạn code như bên dưới, chú ý bổ sung namespace System.Web.MvcMicosoft.Extensions.DependencyInjection.
 using System;  
 using System.Collections.Generic;  
 using System.Web.Mvc;  
 using Microsoft.Extensions.DependencyInjection;  
 namespace DemoDependencyInjection  
 {  
   public class MyDependencyResolver : IDependencyResolver  
   {  
     protected IServiceProvider serviceProvider;  
     public MyDependencyResolver(IServiceProvider serviceProvider)  
     {  
       this.serviceProvider = serviceProvider;  
     }  
     public object GetService(Type serviceType)  
     {  
       return this.serviceProvider.GetService(serviceType);  
     }  
     public IEnumerable<object> GetServices(Type serviceType)  
     {  
       return this.serviceProvider.GetServices(serviceType);  
     }  
   }  
 }  

Sau khi đã tạo class MyDependencyResolver xong, tiến hành cập nhật method Configuration trong file Startup.cs.
 public void Configuration(IAppBuilder app)  
 {  
    ConfigureAuth(app);  

    var services = new ServiceCollection();  
    ConfigureServices(services);  
    var resolver = new MyDependencyResolver(services.BuildServiceProvider());  
    DependencyResolver.SetResolver(resolver);  
 }  

Bước 5: Tạo service.
Tạo interface IDIService gồm method GetCode như bên dưới.
 namespace DemoDependencyInjection  
 {  
   public interface IDIService  
   {  
     string GetCode();  
   }  
 }  

Tạo class DIService kế thừa từ interface đã tạo ở trên, đồng thời implement method GetCode.
 namespace DemoDependencyInjection  
 {  
   public class DIService : IDIService  
   {  
     public string GetCode()  
     {  
       return "Hello " + GetHashCode();  
     }  
   }  
 }  

Bước 6: Sử dụng service trong controller.
Vào lại class DIController, khai báo một biến _service từ interface IDIService.
private readonly IDIService _service;  

vả bổ sung hàm khởi tạo có tham số đầu vào cho DIController như bên dưới
public DIController(IDIService service)  
{  
   this._service = service;  
} 

Cập nhật lại action Index bên trong DIController, nó sẽ nhận kết quả từ method GetCode của serivce và gán vào ViewBag.Message.
 // GET: DI  
 public ActionResult Index()  
 {  
     // say Hello  
     ViewBag.Message = this._service.GetCode();  
     return View();  
 }  

Bên dưới là đoạn code đầy đủ của DIController.
 using System.Web.Mvc;  
 namespace DemoDependencyInjection.Controllers  
 {  
   public class DIController : Controller  
   {  
     private readonly IDIService _service;  
     public DIController(IDIService service)  
     {  
       this._service = service;  
     }  
     // GET: DI  
     public ActionResult Index()  
     {  
       // say Hello  
       ViewBag.Message = this._service.GetCode();  
       return View();  
     }  
   }  
 }  

Bước 7: Tiến hành Add View cho action Index.

Đặt tên cho view là Index.


Cập nhật code cho view Index hiển thị thông điệp từ ViewBag.Message.
 @{  
   ViewBag.Title = "Index";  
 }  
 <h2>@ViewBag.Message</h2>  

Bước 8: Đăng ký service trong file Startup.cs.
Vào method ConfigureServices trong file Startup.cs và đăng ký vào danh sách service.
 public void ConfigureServices(IServiceCollection services)  
 {  
    services.AddTransient<IDIService, DIService>();  
 }  

Tiến hành BuildRun project, trên thanh Address đi đến trang DI.

Lúc này chúng ta sẽ nhận thông báo lỗi vì DIController chưa được đăng ký cho phép có hàm khởi tạo có tham số đầu vào. Để cho phép điều này, ta cần đăng ký controller vào danh sách service.

Tạo mới một static class đặt tên ServiceProviderExtensions và bổ sung static method AddControllersAsServices như là một extension của IServiceCollection như bên dưới.
 using Microsoft.Extensions.DependencyInjection;  
 using System;  
 using System.Collections.Generic;  
 namespace DemoDependencyInjection  
 {  
   public static class ServiceProviderExtensions  
   {  
     public static IServiceCollection AddControllersAsServices(this IServiceCollection services, IEnumerable<Type> controllerTypes)  
     {  
       foreach (var type in controllerTypes)  
       {  
         services.AddTransient(type);  
       }  
       return services;  
     }  
   }  
 }  

Vào lại file Startup.cs, trong method ConfigureServices tiến hành đăng ký tất cả controller vào danh sách service.
 public void ConfigureServices(IServiceCollection services)  
 {  
     services.AddControllersAsServices(typeof(Startup).Assembly.GetExportedTypes()  
         .Where(t => !t.IsAbstract && !t.IsGenericTypeDefinition)  
         .Where(t => typeof(IController).IsAssignableFrom(t)   
         || t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)));  

     services.AddTransient<IDIService, DIService>();  
 }  

Tiến hành BuildRun project, lúc này khi đi đến trang DI đã không còn lỗi và hiển thị thông điệp từ method GetCode.

Tiếp theo hãy thử Refresh trang, lúc này chúng ta sẽ có một kết quả hiển thị khác.
Đó là bởi vì trong method ConfigureServices chúng ta đăng ký service một cách tạm thời.
services.AddTransient<IDIService, DIService>();  
Mỗi khi có yêu cầu sử dụng DIService, nó sẽ tạo mới một instance, do đó kết quả trả về từ method GetCode của DIService sẽ khác nhau cho mỗi lần yêu cầu.

Trong trường hợp nếu chúng ta muốn chỉ tạo ra một và chỉ một instance của DIService được sử dụng cho mọi yêu cầu, thay thế đoạn code trên bằng đoạn code bên dưới.
services.AddSingleton<IDIService, DIService>();  

----------------------
Share to be shared
----------------------