Using Serilog + Seq with Minimal API in NET 7

Nhân dịp Microsoft tung ra con hàng .NET 7, nên mình update lại cái project cũ lên .NET 7, sẵn configure lại Serilog cho nó đẩy data ra seq cho dễ đọc.

Sample sử dụng NET 7 với minial api, cũng khá là hay ho.

Đầu tiên dựng sẵn cái server seq log bằng Docker

docker run --name seqlogger -d -p 5341:5341 -p 5342:80 -e ACCEPT_EULA=Y datalust/seq

Tiếp theo tạo một ứng dụng net core đơn giản

donet new web -o AntCommerce.Module.Order

OK đã xong, cái app đơn giản.

Add thêm đồ chơi liên quan Serilog vào app

dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Enrichers.Environment
dotnet add package Serilog.Exceptions
dotnet add package Serilog.Extensions.Logging
dotnet add package Serilog.Sinks.Seq

Chúng ta có 2 cách để bắt sử dụng Serilog. Cấu hình bằng code và appSettings.json

Sử dụng code. Edit trong file Program.cs

builder.Host.UseSerilog();
Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Information()
                .Enrich.FromLogContext()
                .Enrich.WithExceptionDetails()
                .Enrich.WithMachineName()
                .WriteTo.Console()
                .WriteTo.Seq("http://localhost:5341", Serilog.Events.LogEventLevel.Information)
                .CreateLogger();

Sử dụng appSettings.json. Edit trong file Program.cs

builder.Host.UseSerilog((context, loggerConfig) 
    => loggerConfig.ReadFrom.Configuration(context.Configuration));

Thêm vào file appsettings.json

  "Serilog": {
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Microsoft": "Information",
        "System": "Information",
        "Microsoft.EntityFrameworkCore.Database.Command": "Information"
      }
    },
    "WriteTo": [
      {
        "Name": "Console"
      },
      {
        "Name": "Seq",
        "Application": "Order API",
        "Args": { "serverUrl": "http://localhost:5341" }
        }
    ],
    "Enrich": [  
      "FromLogContext",
      "WithMachineName",
      "WithProcessId",
      "WithThreadId",
      "WithMachineEnvironmentName",
      "WithExceptionDetails"
    ],
    "Properties": {
      "Application": "Order API"
    }
  }

Bắt đầu sử dụng

Tận hưởng thành quả

Chúc các bạn vui vẻ, happy coding.

Source code: https://github.com/tuanitpro/AntCommerce

Cấu hình Jenkins Reverse Proxy cho IIS

Bước 1: Cài đặt IIS, thêm sites mới cho Jenkins.

Bước 2: Cài đặt URL Rewrite và IIS ARR

Bước 3: Chọn Application Request Routing Cache -> Server Proxy Settings

Bước 4: Cấu hình IIS site jenkins theo domain

Bước 5: Thêm file web.config vào folder chứa jenkins

Bước 6: Copy nội dung web.config dưới đây vào file của bạn, thay nội dung domain thành của bạn

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<outboundRules>
<rule name="ReverseProxyOutboundRule1" preCondition="ResponseIsHtml1" enabled="true">
<match filterByTags="A, Form, Img" pattern="^http(s)?://localhost:80/(.*)" />
<action type="Rewrite" value="http{R:1}://jenkins.tuanitpro.com/{R:2}" />
</rule>
<rule name="RestorageAccessEncoding" preCondition="RestorageAccessEncoding" enabled="true">
<match serverVariable="HTTP_ACCEPT_ENCODING" pattern="^(.*)" />
<action type="Rewrite" value="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" />
</rule>
<preConditions>
<preCondition name="ResponseIsHtml1">
<add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
</preCondition>
<preCondition name="RestorageAccessEncoding">
<add input="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" pattern=".+" />
</preCondition>
</preConditions>
</outboundRules>
<rules>
<rule name="ReverseProxyInboundRule1" enabled="true" stopProcessing="true">
<match url="(.*)" />
<action type="Rewrite" url="http://localhost:80/{R:1}" />
<serverVariables>
<set name="HTTP_X_ORIGINAL_ACCEPT_ENCODING" value="{HTTP_ACCEPT_ENCONDING}" />
<set name="HTTP_ACCEPT_ENCODING" />

<set name="HTTP_FORWARDED" value="for={REMOTE_ADDR};by={LOCAL_ADDR};host=&quot;{HTTP_HOST}&quot;;proto=&quot;https&quot;" />
</serverVariables>

</rule>
</rules>
</rewrite>
<urlCompression doStaticCompression="false" />
</system.webServer>
</configuration>

Nguồn tham khảo:

– https://wiki.jenkins.io/display/JENKINS/Running+Jenkins+behind+IIS

– https://rlevchenko.com/2018/12/27/using-iis-as-a-reverse-proxy-for-jenkins/

Jenkins là gì? Cách cài đặt Jenkins trên Windows

Jenkins là gì?

Jenkins là một opensource dùng để thực hiện chức năng tích hợp liên tục (gọi là CI – Continuous Integration) và xây dựng các tác vụ tự động hóa. Nó tích hợp các source code của các members trong team lại nhanh chóng một cách liên tục, theo dõi sự thực thi và trạng thái thông qua các bước kiểm thử (Integration testunits test). Tất nhiên là nhằm giúp sản phẩm chạy ổn định.

CI/CD với jenkins
Các hệ thống tích hợp liên tục (CI) là một phần quan trọng của bất kỳ nhóm Agile nào vì chúng giúp thực thi các lý tưởng phát triển Agile.
Đây là một công cụ xây dựng liên tục, cho phép các nhóm tập trung vào công việc của họ bằng cách tự động hóa quá trình xây dựng, quản lý nhân tạo và quy trình triển khai.
Chức năng và tính linh hoạt cốt lõi của Jenkins cho phép nó phù hợp với nhiều môi trường khác nhau và có thể giúp hợp lý hóa quy trình phát triển cho tất cả các bên liên quan.

CI là gì? CD là gì?

Credit: Dzone

CI là viết tắt của Continuous Integration

Là tích hợp liên tục, nhằm liên tục tích hợp các source code của các thành viên trong team lại một cách nhanh chóng. Giúp kiểm soát được tình hình phát triển thông qua các bước kiểm thử unit tests, Integration tests.

CI Workflow

Chu trình làm việc

  1. Bước đầu tiên, các thành viên trong team dev sẽ bắt đầu pull code mới nhất từ repo về branch để thực hiện các yêu cầu chức năng nhất định.
  2. Tiếp đó là quá trình lập trình và test code để đảm bảo chất lượng của chức năng cũng như toàn bộ source code.
  3. Thành viên code xong thì sẵn sàng cho việc commit vào branch develop của team.
  4. Thành viên cập nhật code mới từ repo về local repo
  5. Merge code và giải quyết conflict.
  6. Build và đảm bảo code pass qua các tests dưới local.
  7. Commit code lên repo
  8. Máy chủ CI lắng nghe các thay đổi code từ repository và có thể tự động build/test, sau đó đưa ra các thông báo (pass/failure) cho các thành viên.

CD là viết tắt của Continuous Delivery

Continuous Delivery là chuyển giao liên tục, là 1 tập hợp các kỹ thuật để triển khai tích hợp souce code trên môi trường staging ( một môi trường rất giống với môi trường production). Với cách này ta có thể đảm bảo source code được review, kiểm thử một cách tỉ mỉ trước khi deploy lên môi trường production.

Jenkins Workflow

Cài đặt Jenkins trên Windows 10 / Windows Server

Download bản cài đặt cho windows tại địa chỉ: https://www.jenkins.io/download/

Mặc định Jenkins sẽ chạy ở địa chỉ localhost:8080. Bạn có thể thay đổi lại PORT trong file jenkins.xml để an toàn hoặc riêng tư hơn.

Chờ quá trình cài đặt Plugins hoàn tất là xong. Mặc định Jenkins chỉ truy cập được từ internal. Để có thể truy cập Jenkins từ ngoài mạng internet, ví dụ https://jenkins.tuanitpro.com/ chúng ta cần cấu hình Reverse Proxy cho IIS.

 

Phần mềm ERP là gì?

Là nhà quản trị, chắc hẳn bạn luôn quan tâm tới những giải pháp công nghệ hữu ích cho quản lý và điều hành doanh nghiệp. ERP là một mô hình phần mềm ra đời với mục đích như vậy. Tuy nhiên, trải qua thời gian và chịu ảnh hưởng bởi làn sóng công nghệ 4.0, phần mềm ERP không còn chiếm thế độc quyền được nữa.

1. Phần mềm ERP là gì?

 

ERP (viết đầy đủ là Enterprise Resource Planning) nghĩa là hoạch định nguồn lực của doanh nghiệp.

 

Phần mềm ERP hiểu đơn giản là một mô hình công nghệ all-in-one, tích hợp nhiều ứng dụng khác nhau thành các module của một gói phần mềm duy nhất, giúp tự động hoá từ A đến Z các hoạt động liên quan tới tài nguyên của doanh nghiệp. Mục đích của phần mềm ERP là tạo ra một hệ thống dữ liệu tự động hợp nhất và xuyên suốt qua các phòng ban và khâu hoạt động như quản lý mua hàng, quản lý sản xuất, quản lý nhân sự,…

 

erp-la-gi-01

Mô hình cấu trúc của ERP với các phân hệ chính (Nguồn ảnh: www.fast.com.vn)

 

Một hệ thống ERP đầy đủ sẽ bao gồm các phân hệ sau:

 

  • Kế toán tài chính (Finance)
  • Lập kế hoạch và quản lý sản xuất (Production Planning and Control)
  • Quản lý mua hàng (Purchase Control)
  • Quản lý bán hàng và phân phối (Sales and Distribution)
  • Quản lý dự án (Project Management)
  • Quản lý nhân sự (Human Resource Management)
  • Quản lý dịch vụ (Service Management)
  • Quản lý hàng tồn kho (Stock Control)
  • Báo cáo thuế (Tax Reports)
  • Báo cáo quản trị (Management Reporting)

 

Một số phần mềm ERP hiện đại còn có thêm các giải pháp liên kết các module cố định với thiết bị hỗ trợ như điện thoại di động, thiết bị quét mã vạch, máy tính cầm tay,…

 

2. Đặc trưng của phần mềm ERP

 

Để phân biệt với các giải pháp quản trị doanh nghiệp khác, phần mềm ERP có 4 đặc điểm chính sau:

 

1. ERP là một hệ thống quản trị sản xuất kinh doanh hợp nhất. Mọi thành viên doanh nghiệp (từ nhà quản lý tới nhân viên), mọi công đoạn và phòng ban chức năng xâu chuỗi thành một quá trình hoạt động sản xuất kinh doanh có trật tự.

 

2. ERP là một hệ thống phần mềm hỗ trợ chứ không phải dây chuyền sản xuất tự động thay thế sức người.

 

3. ERP là một hệ thống quản lý hoạt động theo quy tắc và kế hoạch rõ ràng. Các nhân viên với nhiệm vụ cụ thể cần được xác định từ trước cùng với quy định nhất quán, chặt chẽ; kế hoạch sản xuất kinh doanh phải được lập ra theo định kỳ tuần, tháng, năm.

 

4. ERP là hệ thống liên kết giữa các phòng ban trong công ty để chúng cùng làm việc, trao đổi, cộng tác qua lại với nhau chứ không phải mỗi phòng ban là một cát cứ hoạt động riêng lẻ.

 

3. Lợi ích của ERP đối với doanh nghiệp

 

Bởi lẽ tất cả quy trình làm việc đều được thực hiện trên ERP, nên doanh nghiệp có một cơ sở dữ liệu tập trung duy nhất. Đó là tiền đề đầu tiên cho việc “data mining” – phân tích các dữ liệu theo nhiều góc khác nhau nhằm trợ giúp đưa ra quyết định kinh doanh.

 

ERP giúp kiểm soát thông tin tài chính

 

Thông thường, thông tin tài chính sẽ phải tập hợp số liệu từ nhiều bộ phận khác nhau nên chắc chắn có độ chênh lệch nhất định. Khi sử dụng giải pháp ERP, mọi thứ liên quan đến tài chính được tổng hợp lại ở một nơi – một phiên bản duy nhất xuyên suốt tất cả các phòng ban, cơ sở. Khi một con số được thay đổi, tất cả thông tin liên quan đều được tự động tính toán và hiển thị lại cho trùng khớp, giúp hạn chế tiêu cực trong tài chính doanh nghiệp.

 

Cũng nhờ sự hỗ trợ của phần mềm quản lý, các doanh nghiệp lớn và phức tạp không cần phải đợi đến cuối tháng hoặc cuối quý mới có thể tổng hợp số liệu nữa. Bất cứ khi nào muốn có một báo cáo tài chính chính xác và kịp thời cho lãnh đạo, chỉ cần nhìn vào những con số sau cùng của dòng dữ liệu trên ERP.

 

ERP giúp tăng tốc độ dòng công việc

 

Doanh nghiệp càng lớn thì quy trình làm việc càng nhiều bước, nhiều khâu giống như một mạng lưới phức tạp. Tốc độ dòng công việc phụ thuộc vào hai yếu tố chính: có xác định được đúng nơi cần chuyển dữ liệu đến không, và trong  quá trình chuyển giao đó có gặp phải chướng ngại vật gì.

 

Rõ ràng việc chuyển chứng từ bằng giấy tới tay một nhân viên xác định không thể sánh bằng tốc độ của chứng từ điện tử. Hay trong việc vận chuyển hàng hoá từ kho bãi tới các cơ sở kinh doanh, một quyết định được đồng bộ lên hệ thống ERP sẽ tới được tay người thủ kho rất nhanh chóng. Bằng cách giải quyết các “nút cổ chai” và rút gọn khoảng cách địa lý, phần mềm ERP giúp tăng tốc độ dòng công việc trong doanh nghiệp.

ERP giúp hạn chế sai sót khi nhiều người cùng nhập một dữ liệu

 

Trên thực tế, nhiều khi doanh nghiệp gặp rắc rối bởi nhầm lẫn dữ liệu khi đi qua các bộ phận làm việc khác nhau. Chẳng hạn, con số viết tay “14” thùng hàng rất dễ nhầm thành “19”, hay lỗi gõ Word biến khách hàng ” Phạm Quỳnh” thành khách hàng “Phạm Quyên”. Những sai lầm như vậy ảnh hưởng không nhỏ tới tiến độ làm việc của cả quy trình, làm xấu đi hình ảnh doanh nghiệp, suy giảm tính minh bạch, hơn nữa còn gây mất đoàn kết nội bộ nhân viên.

 

Với ERP, dữ liệu chỉ cần được nhập một lần duy nhất bởi người đầu tiên rồi được lưu trữ nguyên vẹn trên hệ thống. Bất cứ nhân viên nào khác trong doanh nghiệp đều tiếp cận tới dữ liệu gốc này chứ không phải một bản copy “tam sao thất bản”.

ERP giúp dễ dàng kiểm soát quá trình làm việc của nhân viên

 

Một cơ sở dữ liệu tập trung và các quy trình nghiệp vụ được sắp xếp thành dòng cố định sẽ giúp doanh nghiệp dễ dàng áp dụng các cơ chế kiểm soát nội bộ. Chức năng Audit track (tìm vết) của phần mềm ERP cho phép nhanh chóng tìm ra nguồn gốc những bút toán cần kiểm tra cũng như những nhân viên liên quan đến bút toán đó.

 

Việc giám sát từng khâu làm việc của nhân viên cũng được tối ưu. Nhà quản lý chỉ cần ở một nơi, mở giao diện hợp nhất của ERP ra là có thể nắm trong tay tất cả kết quả làm việc của tất cả nhân viên, từ những con số nhỏ nhất như trong buổi sáng nay nhân viên đó đã bán các sản phẩm nào và đem về doanh thu bao nhiêu.

 

Một số phần mềm ERP còn có tính năng tự động phân tích cơ sở dữ liệu để gán nhân viên vào nhiệm vụ phù hợp với thế mạnh của họ, nhà quản lý không phải mất nhiều thời gian cho công đoạn này.

 

ERP giúp tạo ra mạng xã hội nội bộ trong doanh nghiệp

 

Phần mềm ERP thường tích hợp tính năng liên lạc nội bộ giữa các người dùng thuộc cùng một hệ thống. Đó là việc chat riêng tư hoặc cập nhật trạng thái cá nhân giống như cơ chế hoạt động của một mạng xã hội nội bộ thực thụ.

 

erp-la-gi-02

ERP tạo ra một mạng xã hội nội bộ trong doanh nghiệp

 

4. Hạn chế của phần mềm ERP

 

Tuy mang lại nhiều lợi ích cho doanh nghiệp, nhưng có rất nhiều ý kiến cho rằng giải pháp ERP đã lỗi thời trong kỷ nguyên 4.0 – khi công nghệ đang cải tiến liên tục và hướng tới sự nhỏ gọn, chuyên biệt hoá từng bộ phận.

 

ERP đòi hỏi chi phí sử dụng lớn nhưng không đáp ứng đúng nhu cầu của doanh nghiệp

 

ERP không cho phép tách lẻ từng ứng dụng phục vụ các công đoạn làm việc khác nhau của doanh nghiệp mà cố định trong một gói tổng hợp với chi phí rất lớn (ít nhất là 30.000 $ – dựa trên báo giá của các nhà cung cấp). Tuy nhiên, đa số doanh nghiệp lại không cần thiết sử dụng tất cả các phân hệ trong đó, khiến cho việc mua cả gói ERP mà không dùng hết bị lãng phí nghiêm trọng. Chưa kể tới những ứng dụng thừa đó không thể xoá đi mà vẫn tồn tại cồng kềnh ở đó.

 

Lại có một số doanh nghiệp cần dùng thêm các phần mềm đặc thù khác để đảm bảo hoạt động trơn tru. Khi đó, vấn đề lớn nhất là làm sao để ERP tích hợp tốt với các giải pháp bên ngoài này, từ việc trao đổi dữ liệu cho đến quy trình làm việc. Tất nhiên, việc này không hề dễ dàng, vì ERP gần như được lập trình cố định.

 

Như vậy, về cấu trúc phần mềm, ERP bị cồng kềnh và cứng nhắc so với đa số doanh nghiệp. Lại thêm mức chi phí sử dụng đắt đỏ, doanh nghiệp cần thực sự thận trọng khi quyết định sử dụng ERP hay không.

ERP đòi hỏi tốc độ triển khai chậm chạp, mất nhiều công sức

 

Việc triển khai một giải pháp công nghệ phụ thuộc rất nhiều vào tốc độ làm việc của bên cung cấp phần mềm và tốc độ làm quen với phương thức làm việc mới của doanh nghiệp. Đáng tiếc là với ERP, cả hai yếu tố này đều tiêu tốn nhiều thời gian và công sức.

 

Trước hết là việc tích hợp đầy đủ hệ thống cồng kềnh của ERP vào doanh nghiệp. Công đoạn này yêu cầu phải trang bị máy chủ trung tâm dữ liệu và cơ sở hạ tầng mạng tới tất cả “ngõ ngách” nhỏ nhất của doanh nghiệp. Vấn đề bảo mật, yêu cầu sao lưu và khôi phục dữ liệu cũng phải tính toán kỹ lưỡng để đảm bảo ổn định vận hành cho toàn hệ thống. Về việc nhân viên trong doanh nghiệp sẽ sử dụng ERP như thế nào, có nhiều ý kiến trái chiều, trong đó nổi bật lên là sự lo ngại về việc phải ngay lập tức thay đổi cách vận hành của cả một bộ máy doanh nghiệp.

 

ERP gây gia tăng rủi ro trong quá trình sản xuất, kinh doanh

 

Việc đơn giản hoá dòng dữ liệu trên một hệ thống duy nhất sẽ rất thuận lợi khi ERP hoạt động trơn tru. Tuy nhiên, chỉ cần phát sinh một vấn đề trong khâu bất kỳ, một công đoạn làm việc sẽ bị tắc nghẽn, kéo theo sự đình trệ của toàn bộ quy trình phía sau. Cũng phải hiểu rằng việc triển khai ERP không chỉ ảnh hưởng đến một phần nhất định của doanh nghiệp mà là tất cả các bộ phận, hoạt động. Nhà quản trị doanh nghiệp không được phép liều mình với ERP, bởi cái giá phải trả nếu giải pháp này không phù hợp là quá lớn: doanh nghiệp bị “chết” trong suốt quãng thời gian dài.

 

ERP rất khó nâng cấp khi doanh nghiệp cần thay đổi

 

Các nhà cung cấp giải pháp ERP phải đáp ứng yêu cầu của nhiều nhóm với các nhu cầu, quy trình và mục tiêu rất khác nhau. Kết quả là, hầu hết các giải pháp ERP đều chỉ có thế mạnh trong một lĩnh vực – như tài chính – và yếu hơn nhiều ở những phân hệ khác. Một vấn đề nữa là doanh nghiệp luôn mong muốn được cải tiến công nghệ để thức thời hơn trong kỷ nguyên 4.0. Giải pháp ERP gặp phải bất lợi lúc này, khi mà nếu muốn thay đổi dù chỉ một tính năng, doanh nghiệp sẽ phải tạm ngưng hoạt động và đưa cả hệ thống ERP cồng kềnh ra để lập trình lại.

 

Trong khi có, các nhà cung cấp phần mềm chuyên biệt lại không ngừng cải tiến và đưa ra phiên bản nâng cấp miễn phí cho khách hàng đang sử dụng. Họ cũng sẵn sàng tư vấn và hỗ trợ 24/7, chứ không phải như với ERP – doanh nghiệp mua một hệ thống về và phải tự tìm cách xoay sở với nó.

 

5. Mô hình quản lý bằng ERP phù hợp với doanh nghiệp như thế nào?

 

Doanh nghiệp có thể cân nhắc giải pháp phần mềm ERP khi nhìn nhận chính xác mình đã ở một trong 3 tình huống sau:

 

  1. Doanh nghiệp thường xuyên xảy ra sai sót trong quá trình nhập / xuất và chuyển dữ liệu, ví dụ như chênh lệch số lượng hàng hoá tồn kho, nhầm lẫn giao hàng cho khác, chồng chéo thông tin hoá đơn,… hoặc ngày càng nhiều khách hàng trung thành than phiền về chất lượng sản phẩm / dịch vụ.
  2. Doanh nghiệp bắt đầu tăng nhanh về khối lượng giao dịch kinh doanh, có dự định mở rộng quy mô và  muốn phòng tránh rủi ro phát sinh.
  3. Doanh nghiệp hiện đang phải làm việc với bộ máy quản lý cồng kềnh, hiệu quả kém; muốn thực hiện tái cấu trúc tổng thể.

 

Doanh nghiệp phải có sẵn nguồn kinh phí lớn, nhằm phục vụ triển khai phần mềm và vẫn duy trì trả lương cho công nhân viên đều đặn. Một tiêu chí nữa doanh nghiệp cần đáp ứng là sự đồng lòng ủng hộ của nhân viên và / hoặc một chiến lược truyền thông nội bộ hiệu quả.

 

6. Một vài doanh nghiệp cung cấp phần mềm ERP ở Việt Nam

 

Nhiều doanh nghiệp Việt Nam hiện nay đã đủ khả năng tự thiết kế những phần mềm ERP “thuần Việt” như BRAVO, ERPViet, Amis.vn, MyERP,…

 

Một trong những thế mạnh hiển nhiên của các phần mềm ERP trong nước là có quy trình xử lý tài chính – kế toán tuân thủ theo chuẩn mực kế toán Việt Nam hiện hành, cũng như cập nhật kịp thời và nhanh chóng những thay đổi liên tục của các thông tư, quyết định, hướng dẫn,… trong nước.

 

7. Về các phần mềm chuyên biệt – giải pháp thay thế của ERP

 

Khi giải pháp ERP trở nên lỗi thời trước những bước tiến vượt bậc của công nghệ, những phần mềm chuyên biệt cho từng lĩnh vực trong doanh nghiệp đã nhanh chóng thay thế và chiếm lĩnh thị trường. Chúng tiết kiệm chi phí, nhỏ gọn, tân tiến và linh hoạt hơn  ERP rất nhiều khi áp dụng vào thực tế vận hành doanh nghiệp.

 

Rất nhiều doanh nghiệp đã lựa chọn giải pháp thay thế này. Thay vì tiêu tốn nguồn chi phí khổng lồ cho một hệ thống cồng kềnh vừa thừa vừa thiếu, doanh nghiệp chỉ lựa chọn sử dụng những phần mềm chuyên biệt mà họ thực sự cần.  Những phần mềm này được thiết kế ra để phục vụ một và chỉ một nghiệp vụ nhất định, nên chúng sở hữu nhiều tính năng chuyên sâu và hỗ trợ hiệu quả. Đặc biệt, mua một bộ các phần mềm chuyên biệt vẫn rẻ hơn nhiều so với một hệ thống ERP.

 

Nếu như ERP đòi hỏi phải tốn nhiều thời gian để set up và chạy thử, thì các phần mềm chuyên biệt lại chỉ mất từ 1-2 ngày là doanh nghiệp đã sở hữu bộ dữ liệu hoàn chỉnh và nhân viên đã có thể sử dụng thành thạo rồi. Song song với việc tiến hành set up một phần mềm, các phòng ban / bộ phận khác vẫn hoạt động hiệu quả bình thường. Theo đó, doanh nghiệp có thể lần lượt triển khai từng phần mềm để không làm gián đoạn tiến độ vận hành chung của doanh nghiệp.

Nguồn: https://base.vn

Logging with Serilog, Sentry, ASP.NET Core and ReactJs

Khi phát triển dự án, chúng ta luôn mong muốn sản phẩm làm ra chạy thật ngon, không lỗi, không phàn nàn từ khách hàng.

Tuy nhiên đời không như mơ, đôi khi sự cố vẫn xảy ra. Chính vì vậy logging là việc cần thiết, và cũng có nhiều công cụ hỗ trợ cho việc đó. Trong phạm vi bài viết này, các bạn sẽ được biết thêm một công cụ log ngon lành đơn giản dành cho .NET Core & ReactJs. Đó là https://serilog.net, http://sentry.io. Nếu bạn quan tâm thêm thì có thể tìm hiểu về log4net, nlog…. Có bài viết so sánh tại https://stackify.com/nlog-vs-log4net-vs-serilog/

Bản thân mình đánh giá thằng serilog ngon, mạnh mẽ, dễ áp dụng vào dự án. Nó hỗ trợ rất nhiều loại log: file, console, sentry, elasticsearch…

Sentry.io hiểu đơn giản như một database lưu trữ & cung cấp theo dõi tất cả log của chúng ta. Có bản free, gửi email khi có issue. Đánh giá ngon. Muốn ngon hơn nữa thì dùng bản trả phí. Tuy nhiên bản miễn phí cũng đủ dùng cho dự án nhỏ rồi.

 

Việc áp dụng khá đơn giản, chúng ta sẽ bắt đầu với dự án ASP.NET Core trước.

Việc đăng ký tạo project trên nó khá đơn giản. Sau khi đăng ký nó sẽ cho bạn một đoạn mã DNS để thêm vào project. Đăng ký tài khoản sentry & tạo project.

 

Logging with Serilog, Sentry, ASP.NET Core and ReactJs

 

 

Logging with Serilog, Sentry, ASP.NET Core and ReactJs

 

Logging with Serilog, Sentry, ASP.NET Core and ReactJs

Cấu hình sentry vào project. File Program.cs

public class Program
{
	public static void Main(string[] args)
	{
		using (SentrySdk.Init("__DNS__"))
		{
		// App code
		CreateWebHostBuilder(args).Build().Run();
		}
	}

	public static IWebHostBuilder CreateWebHostBuilder(string[] args) 
	{
		WebHost.CreateDefaultBuilder(args)
		.UseKestrel()
		.UseIISIntegration()
		.UseStartup();
	}
}

Cấu hình Serilog File Startup.cs

 		
public Startup(IConfiguration configuration, IHostingEnvironment hostingEnvironment)
{
	Configuration = configuration;

	var builder = new ConfigurationBuilder()
		.SetBasePath(hostingEnvironment.ContentRootPath)
		.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
		.AddJsonFile($"appsettings.{hostingEnvironment.EnvironmentName}.json", reloadOnChange: true, optional: true)
		.AddEnvironmentVariables();

	Configuration = builder.Build();            

	Log.Logger = new LoggerConfiguration()
		.Enrich.FromLogContext()
		.Enrich.WithExceptionDetails()                
		.WriteTo.Sentry(o => o.Dsn = new Dsn("__DNS__"))
	.CreateLogger();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{   
	// TODO
	loggerFactory.AddSerilog();
}

Nếu bạn muốn log trong từng controller thì Serilog cũng hỗ trợ ngon luôn.

using Microsoft.Extensions.Logging;

private readonly ILogger _logger;
public IdentityController(ILogger logger)
{
	_logger = logger;
}

   
public async Task Login(string username, string password)
{
	_logger.LogInformation("Log here");
}

Như vậy xong phần cấu hình cho project .NET Core. Tiếp theo đăng ký sentry project reactjs.

Cài đặt thư viện sentry cho reactjs

npm i @sentry/browser

Thêm đoạn code vào App.js hoặc chỗ nào bạn muốn log.

import * as Sentry from '@sentry/browser'
Sentry.init({
  dsn: '__DNS__'
})
class App extends Component {
  componentDidCatch (error, errorInfo) {
    this.setState({ error })
    Sentry.withScope(scope => {
      scope.setExtras(errorInfo)
      const eventId = Sentry.captureException(error)
      this.setState({ eventId })
    })
  }

Như vậy là xong. Bây giờ chúng ta sẽ vào Sentry.io để xem log của dự án.

Logging with Serilog, Sentry, ASP.NET Core and ReactJs
Logging with Serilog, Sentry, ASP.NET Core and ReactJs

Happy coding. Chúc các bạn thành công & làm ra sản phẩm không bug, không issue, không phàn nàn từ khách hàng.

Pagination trong ReactJs

Khi làm các ứng dụng web, chúng ta luôn có nhu cầu lấy dữ liệu từ các remote server, API… các dữ liệu mạng xã hội, tin tức, shopping, thanh toán thì rất nhiều records, do đó chúng ta cần giải pháp phân trang để giảm tải sự hiển thị quá nhiều data gây khó chịu cho người dùng.

Hướng dẫn đơn giản sau đây giúp bạn dễ dàng phân trang trong app ReactJs

Ví dụ ở đây sử dụng API dữ liệu các quốc gia, 248 quốc gia tất cả. Chúng ta sẽ bắt đầu với ứng dụng react đơn giản.

Tạo một app react js

[code lang="js"]
npx create-react-js pagination
[/code]

Cài đặt các thư viện cần thiết

[code lang="js"]
npm i bootstrap countries-api prop-types react-flags
[/code]

Sau khi cài đặt thành công chúng ta copy folder flags ở địa chỉ node_modules\react-flags\vendor đưa vào folder public\img

Open file index.js, thêm code bootstrap

[code lang=”js”] import “bootstrap/dist/css/bootstrap.min.css”; [/code]

Components

Trong folder src, tạo folder components

Tạo 2 component

  • CountryCard.jsx Component
  • Pagination.jsx Component

CountryCard Component

[code lang=”js”] import React, { Component, Fragment } from ‘react’; import PropTypes from ‘prop-types’; import Flag from ‘react-flags’; class CountryCard extends Component { render() { const { cca2: code2 = “”, region = null, name = {} } = this.props.country || {}; return (

{name.common} {region}

) } } CountryCard.propTypes = { country: PropTypes.shape({ cca2: PropTypes.string.string, region: PropTypes.string.isRequired, name: PropTypes.shape({ common: PropTypes.string.isRequired }).isRequired }).isRequired } export default CountryCard [/code]

Pagination Component

[code lang=”js”] import React, { Component, Fragment } from “react”; import PropTypes from “prop-types”; const LEFT_PAGE = “LEFT”; const RIGHT_PAGE = “RIGHT”; const range = (from, to, step = 1) => { let i = from; const range = []; while (i <= to) { range.push(i); i += step; } return range; }; class Pagination extends Component { constructor(props) { super(props); const { totalRecords = null, pageLimit = 30, pageNeighbours = 0 } = props; this.pageLimit = typeof pageLimit === “number” ? pageLimit : 30; this.totalRecords = typeof totalRecords === “number” ? totalRecords : 0; this.pageNeighbours = typeof pageNeighbours === “number” ? Math.max(0, Math.min(pageNeighbours, 2)) : 0; this.totalPages = Math.ceil(this.totalRecords / this.pageLimit); this.state = { currentPage: 1 }; } componentDidMount() { this.gotoPage(1); } gotoPage = page => { const { onPageChanged = f => f } = this.props; const currentPage = Math.max(0, Math.min(page, this.totalPages)); const paginationData = { currentPage, totalPages: this.totalPages, pageLimit: this.pageLimit, totalRecords: this.totalRecords }; this.setState({ currentPage }, () => onPageChanged(paginationData)); }; handleClick = (page, evt) => { evt.preventDefault(); this.gotoPage(page); }; handleMoveLeft = evt => { evt.preventDefault(); this.gotoPage(this.state.currentPage – this.pageNeighbours * 2 – 1); }; handleMoveRight = evt => { evt.preventDefault(); this.gotoPage(this.state.currentPage + this.pageNeighbours * 2 + 1); }; fetchPageNumbers = () => { const totalPages = this.totalPages; const currentPage = this.state.currentPage; const pageNeighbours = this.pageNeighbours; const totalNumbers = this.pageNeighbours * 2 + 3; const totalBlocks = totalNumbers + 2; if (totalPages > totalBlocks) { let pages = []; const leftBound = currentPage – pageNeighbours; const rightBound = currentPage + pageNeighbours; const beforeLastPage = totalPages – 1; const startPage = leftBound > 2 ? leftBound : 2; const endPage = rightBound < beforeLastPage ? rightBound : beforeLastPage; pages = range(startPage, endPage); const pagesCount = pages.length; const singleSpillOffset = totalNumbers – pagesCount – 1; const leftSpill = startPage > 2; const rightSpill = endPage < beforeLastPage; const leftSpillPage = LEFT_PAGE; const rightSpillPage = RIGHT_PAGE; if (leftSpill && !rightSpill) { const extraPages = range(startPage – singleSpillOffset, startPage – 1); pages = [leftSpillPage, …extraPages, …pages]; } else if (!leftSpill && rightSpill) { const extraPages = range(endPage + 1, endPage + singleSpillOffset); pages = […pages, …extraPages, rightSpillPage]; } else if (leftSpill && rightSpill) { pages = [leftSpillPage, …pages, rightSpillPage]; } return [1, …pages, totalPages]; } return range(1, totalPages); }; render() { if (!this.totalRecords) return null; if (this.totalPages === 1) return null; const { currentPage } = this.state; const pages = this.fetchPageNumbers(); return (

); } } Pagination.propTypes = { totalRecords: PropTypes.number.isRequired, pageLimit: PropTypes.number, pageNeighbours: PropTypes.number, onPageChanged: PropTypes.func }; export default Pagination; [/code]

App.js

[code lang=”js”] import React, { Component } from ‘react’; import Countries from ‘countries-api/lib/data/Countries.json’; import ‘./App.css’; import Pagination from “./components/Pagination”; import CountryCard from ‘./components/CountryCard’; class App extends Component { state = { allCountries: [], currentCountries: [], currentPage: null, totalPages: null }; componentDidMount() { const allCountries = Countries; this.setState({ allCountries }); } onPageChanged = data => { const { allCountries } = this.state; const { currentPage, totalPages, pageLimit } = data; const offset = (currentPage – 1) * pageLimit; const currentCountries = allCountries.slice(offset, offset + pageLimit); this.setState({ currentPage, currentCountries, totalPages }); }; mapCountry(country, index) { return } render() { const { allCountries, currentCountries, currentPage, totalPages } = this.state; const totalCountries = allCountries.length; if (totalCountries === 0) return null; const headerClass = [ “text-dark py-2 pr-4 m-0”, currentPage ? “border-gray border-right” : “” ] .join(” “) .trim(); const displayCountries = currentCountries.map(this.mapCountry); return (

{totalCountries}{” “} Countries

{currentPage && ( Page {currentPage} /{” “} {totalPages} )}

{displayCountries}

); } } export default App; [/code]


Download mã nguồn ở đây

 GitHub

Tạo bot discord check giá coinmarketcap

Các thư viện sử dụng

https://discord.js.org

https://www.npmjs.com/package/node-fetch

Yêu cầu máy đã cài nodejs

Tạo app trên discord: https://discordapp.com/developers/docs/intro

Lấy token để sử dụng sau:

Sử dụng Visual Code để code.

Tạo file bot.js

Code:

npm install discord.js

const Discord = require('discord.js');
const client = new Discord.Client();

const commandPrefix = "!";

client.on('ready', () => {
  console.log(`Logged in as ${client.user.tag}!`);
});

client.on('message', (message)=> {
    if(!message.content.startsWith(commandPrefix)) return;
    const args = message.content.slice(commandPrefix.length).trim().split(/ +/g);
    const command = args.shift().toLowerCase();
       
    switch(command){
    	case "hello":
			hello(message);
    	break;
    	case "help":
			help(message);
    	break;
    	case "ping":
			sendText(message, "Pong");
    	break;
    	case "clear":
    		clear(message);
    	break;
    	case "cmc":
    		coinmarketcap(message);
    	break;
    	case "c":
    		chart(message, args);
    	break;
    	case "p":
    		price(message, args);
    	break;    	
    	default:
    		sendText(message, "Command not found.")
    	break;
    }
});

client.login('NDQyMDAyMTAyODAyMzE3MzMz.DdMfbw.3eV-Qk5N2TpDIDgatQb1ILeD0Hc');

Các hàm chính trong bot

function hello(message){
	message.channel.send("Hello " + message.author + "! Nice to meet you. :smiley: ");
}
function help(message){
	 let embed = new Discord.RichEmbed()            
            .setAuthor("Hello world", "http://icons.iconarchive.com/icons/froyoshark/enkel/256/Bitcoin-icon.png")
            .addField("!hello", "Sends a friendly message!")
            .addField("!help", "Sends this help embed")
            .addField("!cmc", "Coin Market Cap")
            .addField("!ping", "Ping")
            .addField("!p", "Price of coin. Ex: BTCUSDT or price BTC")
            .setTitle("Bot commands:")
            .setFooter("Here you have all bot commands you can use!")
            .setColor("AQUA");        
        message.channel.send({embed: embed});
}
function sendText(message, text){
	message.channel.send(text).then(msg=>{msg.delete(10000)}); 
    message.delete(12000);
}

function clear(message){
	if (message.member.hasPermission("MANAGE_MESSAGES")) {
        message.channel.fetchMessages()
           .then(function(list){
                message.channel.bulkDelete(list);
            }, function(err){message.channel.send("ERROR: ERROR CLEARING CHANNEL.")})                        
    }
    else{
    	console.log("You don't have permission");
    }

Hàm lấy giá trên coinmarketcap.com

npm install node-fetch

function coinmarketcap(message){
	    let url = 'https://api.coinmarketcap.com/v2/global/';	
		let bitcoin_percentage_of_market_cap='';
		let total_market_cap= '';
		let total_volume_24h = '';
		let last_updated = '';
		fetch(url)
    	.then(res => res.json())
    	.then(json => {
    		// console.log(json);
    		bitcoin_percentage_of_market_cap =  json.data.bitcoin_percentage_of_market_cap;
    		total_market_cap = json.data.quotes.USD.total_market_cap;
 			total_volume_24h = json.data.quotes.USD.total_volume_24h;
 			last_updated = json.data.last_updated;
			
 			let embed = new Discord.RichEmbed()
 			.setAuthor("Coinmarketcap", "http://icons.iconarchive.com/icons/froyoshark/enkel/256/Bitcoin-icon.png")
            .addField("Market Cap (USD):", numberFormat(total_market_cap))
            .addField("24h Vol (USD): ", numberFormat(total_volume_24h))
            .addField("BTC Dominance(%): ", bitcoin_percentage_of_market_cap)            
            .setFooter("Last Updated: " + timeConverter(last_updated))
            .setColor("AQUA");

        // Send the embed with message.channel.send()
        message.channel.send({embed: embed}).then(msg=>{msg.delete(10000)}); 
        message.delete(12000);       
    	});    	 
}

Thêm bot vào Server Discord

Open link & thay bằng client_id của bạn trên trình duyệt

Khởi chạy bot

node bot.js

Demo

 

Source code:  GitHub

Chúc các bạn thành công

DONATE

Bitcoin address: 1tmxPQbmycQMehAAZ8FshbvEoQZ6Ri7PT

ETH Address: 0xfe17aaf16bceb4311795b2e8ff0199640bdce54a

ETC Address: 0xfe17aaf16bceb4311795b2e8ff0199640bdce54a

XVG Address: D9iP7fhKbHJKViwUG9MWcAt3JPdCuxnCZJ

Utilities javascript, angualr js

Hướng dẫn convert một Json object về List Object trong javascript JSON. Trường hợp backend trả về 1 object, ví dụ Dictionary…

const jsonObject = {'key1':'value1', 'key2':'value2', 'key3':'value3'};
console.log(jsonObject);
const list = [];
for (const key in jsonObject) {
  if (jsonObject.hasOwnProperty(key)) {
    const temp = {
      Id: key,
      Name: jsonObject[key]
    };
    list.push(temp);
  }
}
console.log(list);

Merge(trộn) 2 object thành 1 object thứ 3, có tất cả thuộc tính & giá trị của 1 & 2.

const resources  = {
  search: 'Search',
  reset: 'Reset',
  save: 'Save change',
  cancel: 'Cancel',
  edit: 'Edit',
  remove: 'Remove',
  add_new: 'Add new',
  tools: 'Tools',        
}
console.log(resources);
const resources_setting = {        
  key: 'Key',
  value: 'Value'        
};
console.log(resources_setting);
function adapter(itemA) {
  for (const key in resources) {
    if (resources.hasOwnProperty(key)) {
      itemA[key] = resources[key];
    }
  }
  return itemA;
}
const resourcesOutput = adapter(resources_setting);
console.log(resourcesOutput);

Cascading Dropdown Ajax

Chào các bạn

Khi lập trình các bạn hay gặp vấn đề khó khăn khi lấy dữ liệu giữa các Dropdown List khác nhau. Ví dụ: Từ tỉnh thành -> quận huyện -> Xã phường…

Đây là khó khăn của rất nhiều bạn sinh viên khi mới bắt đầu học lập trình. Video dưới đây sẽ hướng dẫn bạn cách giải quyết, khá là đơn giản. Hoàn toàn có thể áp dụng nhiều trường hợp khác nhau.

Video không có tiếng, do mình làm trong lúc ngẫu hứng ngoài quán cafe. Các bạn có thể theo dõi, hoặc tải mã nguồn về phân tích thêm.

Slide giới thiệuhttps://docs.google.com/presentation/d/1soxUWN7Xa6Z89Az-PNCtiyGgMVdVSgmqzp1bf7hdi50/edit?usp=sharing

Tài nguyên

SQL Data:

CSDL tên Quốc gia, tỉnh thành phố thị xã quận huyện, xã phường Việt Nam

Demo

Cascading Dropdown Ajax

Code C#

 public class MyDbContext:DbContext
    {
        // Ở đây các bạn có thể khai báo trong Web.config.
        private const string sqlConnection = @"Data Source=.\SQLExpress; Initial Catalog = CountryDb; User Id=sa; Password = sa";

        public MyDbContext() : base(sqlConnection)
        {

        }
      
        public DbSet<Country> Countries { get; set; }
        public DbSet<Province> Provinces { get; set; }
        public DbSet<District> Districts { get; set; }
        public DbSet<Ward> Wards { get; set; }

    }

 

  public JsonResult GetAllCountries()
        {
            using(var db = new MyDbContext())
            {
                var data = db.Countries.OrderBy(x=>x.Name).ToList();
                return Json(data, JsonRequestBehavior.AllowGet);
            }
        }
        /// <summary>
        /// Hàm lấy danh sách tỉnh thành theo CountryId. 
        /// Id = 237 là của Việt Nam. Do database mình quy định vậy
        /// Test OK
        /// </summary>
        /// <param name="id">Id của country</param>
        /// <returns></returns>
        public JsonResult GetAllProvinceByCountryId(int? id=237) 
        {
            using (var db = new MyDbContext())
            {               
                var data = db.Provinces.Where(x=>x.CountryId== id).OrderBy(x=>x.Name).ToList();
                return Json(data, JsonRequestBehavior.AllowGet);
            }
        }
        /// <summary>
        /// Hàm lấy tất cả danh sách quận huyện
        /// Id = 1 là Hà Nội, do database mình quy định vậy
        /// Test OK
        /// </summary>
        /// <param name="id">Id = ProvinceId</param>
        /// <returns></returns>
        public JsonResult GetAllDistrictByProvinceId(int? id = 1)
        {
            using (var db = new MyDbContext())
            {
                var data = db.Districts.Where(x => x.ProvinceId == id).OrderBy(x=>x.Name).ToList();
                return Json(data, JsonRequestBehavior.AllowGet);
            }
        }
        /// <summary>
        /// Hàm lấy danh sách xã phường theo quận huyện
        /// Id= 1 là Ba Đình. Do database quy định
        /// Test OK
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public JsonResult GetAllWardByDistrictId(int? id = 1)
        {
            using (var db = new MyDbContext())
            {
                var data = db.Wards.Where(x => x.DistrictId == id).OrderBy(x=>x.Name).ToList();
                return Json(data, JsonRequestBehavior.AllowGet);
            }
        }

Code js

// File javascript để lấy dữ liệu

// Khai báo URL service của bạn ở đây
var baseService = "/Service";
var countryUrl = baseService + "/GetAllCountries";
var provinceUrl = baseService + "/GetAllProvinceByCountryId";
var districtUrl = baseService + "/GetAllDistrictByProvinceId";
var wardUrl = baseService + "/GetAllWardByDistrictId";
$(document).ready(function () {
    // load danh sách country
    _getCountries();
    $("#ddlCountry").on('change', function () {
        var id = $(this).val();
        if (id != undefined && id != '') {
            _getProvince(id);
        }
    });

    $("#ddlProvince").on('change', function () {
        var id = $(this).val();
        if (id != undefined && id != '') {
            _getDistrict(id);
        }
    });
    $("#ddlDistrict").on('change', function () {
        var id = $(this).val();
        if (id != undefined && id != '') {
            _getWard(id);
        }
    });
    $("#ddlWard").on('change', function () {
        var countryText = $("#ddlCountry option:selected").text();
        var provinceText = $("#ddlProvince option:selected").text();
        var districtText = $("#ddlDistrict option:selected").text();
        var wardText = $("#ddlWard option:selected").text();
        var html = "Quốc gia: " + countryText + " Tỉnh thành: " + provinceText + " " + "Quận huyện: " + districtText + " " + "Xã phường: " + wardText;
        html += "</br>Quê bạn thật là đẹp. Chúc mừng bạn!!!";
        $("#divResult").html(html);
    });
});
function _getCountries() {
    $.get(countryUrl, function (data) {
        if (data != null && data != undefined && data.length) {
            var html = '';
            html += '<option value="">--Không chọn--</option>';
            $.each(data, function (key, item) {
                html += '<option value=' + item.Id + '>' + item.Name + '</option>';
            });
            $("#ddlCountry").html(html);
        }
    });
}
// truyền id của country vào
function _getProvince(id) {
    $.get(provinceUrl + "/"+id, function (data) {
        if (data != null && data != undefined && data.length) {
            var html = '';
            html += '<option value="">--Không chọn--</option>';
            $.each(data, function (key, item) {
                html += '<option value=' + item.Id + '>' + item.Name + '</option>';
            });
            $("#ddlProvince").html(html);
        }
    });
}
// truyền id của province vào
function _getDistrict(id) {
    $.get(districtUrl + "/" + id, function (data) {
        if (data != null && data != undefined && data.length) {
            var html = '';
            html += '<option value="">--Không chọn--</option>';
            $.each(data, function (key, item) {
                html += '<option value=' + item.Id + '>' + item.Name + '</option>';
            });
            $("#ddlDistrict").html(html);
        }
    });
}
// truyền id của district vào
function _getWard(id) {
    $.get(wardUrl + "/" + id, function (data) {
        if (data != null && data != undefined && data.length) {
            var html = '';
            html += '<option value="">--Không chọn--</option>';
            $.each(data, function (key, item) {
                html += '<option value=' + item.Id + '>' + item.Name + '</option>';
            });
            $("#ddlWard").html(html);
        }
    });
}

Chúc các bạn thành công.

Download  GitHub

How to Upload file in Angular

Backend:

Link demo: http://angular.tuanitpro.com/#/uploader

  • Username: tuanitpro
  • Password: 123456

 

uploader.component.css

input[type='file']{
    display: none;
}
#uploader{
    width:100%; color:#fff; 
    font-family:Verdana, Geneva, Tahoma, sans-serif; 
     background:#0094ff;
      text-align:center;
       padding:50px;
        cursor:pointer;
}

uploader.component.html

<div class="container body">
  <div class="main_container">

    <div class="right_col" role="main">


      <div class="">
        <div class="clearfix"></div>
        <div class="row">
          <div class="col-md-12">
            <h2>Angular 4 - Upload file</h2>
          </div>

          <div class="col-md-12 col-sm-12 col-xs-12">
            <div class="x_panel" style="height: auto;">

              <div class="x_content">

                <input type="file" id="fileUploadInput" accept="images/*" (change)="fileChange($event)" />
                <div id="uploader"                    (click)="doUploadFile()">
                 <i class="fa fa-picture-o"></i> {{uploadResult.uploadingText}}
                </div>
                <div class="progress progress-striped" *ngIf="uploadResult.progress > 0">
                  <div class="progress-bar progress-bar-success"  [ngStyle]="{'width':  uploadResult.progress+'%'}">{{uploadResult.progress}}%</div>
                </div>
                <img  *ngIf="uploadResult.fileUrl" src="{{uploadResult.fileUrl}}" style="width:100%;" class="img-response">
              </div>
            </div>
          </div>
        </div>
      </div>





    </div>

  </div>
</div>

uploader.component.ts

import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Http } from '@angular/http';

import $ from 'jquery/dist/jquery.min';

@Component({
  selector: 'app-uploader',
  templateUrl: './uploader.component.html',
  styleUrls: ['./uploader.component.css']
})
export class UploaderComponent implements OnInit {
  uploadingText = 'Chọn file (chỉ chấp nhận file ảnh)';
  uploadResult: any = {
    progress: 0,
    uploadingText: this.uploadingText,
    fileUrl: null
  }
  constructor(private title: Title) { }
  ngOnInit() {
    this.title.setTitle("Angular 4 - Upload file"); 
  }
  doUploadFile() {
    this.uploadResult.progress = 0;
    this.uploadResult.fileUrl = null;
    this.uploadResult.uploadingText = this.uploadingText;
    $("#fileUploadInput").trigger("click");
  }
  fileChange(event) {
    let fileList: FileList = event.target.files;
    if (fileList.length > 0) {
      let file: File = fileList[0];
      let formData: FormData = new FormData();
      formData.append('uploadFile', file);
      let xhr: XMLHttpRequest = new XMLHttpRequest();
      xhr.withCredentials = false;
      xhr.onreadystatechange = () => {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            let json = JSON.parse(xhr.response);
            let fileUrl = 'http://minhquandalat.com/uploads/' + json.Name;
            this.uploadResult.progress = 100;
            this.uploadResult.fileUrl = fileUrl;
            this.uploadResult.uploadingText = "Hoàn thành";

          } else {
            console.log(xhr.response);
          }
        }
      };
      xhr.upload.onprogress = (event) => {
        this.uploadResult.uploadingText = "Đang tải ảnh lên...";
        let percentVal = Math.round(event.loaded / event.total * 100);
        this.uploadResult.progress = percentVal;
      };
      xhr.open('POST', "http://minhquandalat.com/api/FileUpload", true);
      xhr.send(formData);
    }
  }
}

Happy coding