Bắt đầu với Vue JS. Ứng dụng CRUD dùng Vuex, Vutify, .NET Core

Cuối tuần rảnh rỗi ngồi tìm hiểu thêm về VueJs. (Latest stable version: 2.6.11) Framework Frontend nổi tiếng bên cạnh ReactJs và AngularJs.

Theo như định nghĩa trên trang chủ thì Vue là một framework dùng để xây dựng giao diện người dùng (user interfaces). Cụ thể, Vue là một progressive framework (framework linh động), cho phép và khuyến khích phát triển ứng dụng theo từng bước. Phần lõi của VueJS chủ yếu là vào phần View, ngoài ra có thể cài thêm các thành phần, thư viện hỗ trợ để đáp ứng nhu cầu xây dựng những ứng dụng SPA (Single-Page-Applications) . Khác với các monolithic framework (framework nguyên khối) cung cấp tất cả mọi thứ cần có để xây dựng app trong một framework duy nhất.

Để bắt đầu không gì nhanh nhất bằng cách vào thẳng trang chủ của nó tại https://vuejs.org, đọc và thực hành theo.

Do tìm hiểu về SPA nên không quan tâm mấy cách dùng VueJs kiểu nhúng file js nữa, mà bay thẳng vào trang CLI của nó cho nhanh. Link: https://cli.vuejs.org/guide/installation.html

Có vài câu lệnh CLI cần quan tâm đó là

Cài đặt
npm install -g @vue/cli
Khởi tạo dự án
vue create hello-world
Khởi động server Vue
vue-cli-service serve
Build & mang đi khoe
vue-cli-service build

Bắt tay vào làm thôi

Link mã nguồn: https://github.com/tuanitpro/vue-crud

Deploy website lên VPS với Gitlab CI/CD

Sau một thời gian code ra được môt trang web xịn xò thì bạn có nhu cầu đưa lên internet để mọi người được biết. Bắt đầu với công việc đầu tiên là tìm mua tên miền, hosting hoặc VPS. Bạn có thể mua VPS lại Vultr. 1 VPS có thể cài đặt được nhiều trang web.

Như vậy sau quá trình chuẩn bị thành công, chúng ta sẽ đẩy code từ local lên hosting, server. Quá trình này khá là mất công. Cách cổ điển hiện nay được nhiều người dùng là dùng FTP, hoặc upload thằng từ các trang quản lý. Đây là công việc khá nhàm chán, tốn thời gian. Khi có một thay đổi nhỏ là phải đi thao tác lại.

Chính vì vậy khái niệm CI/CD ra đời. CI/CD là một bộ đôi công việc, bao gồm CI (Continuous Integration) và CD (Continuous Delivery), ý nói là quá trình tích hợp (integration) thường xuyên, nhanh chóng hơn khi code cũng như thường xuyên cập nhật phiên bản mới (delivery).

Có khá nhiều công cụ hỗ trợ CI/CD, nhưng trong phạm vi bài viết chỉ đề cập công cụ CI/CD của Gitlab, và tất nhiên sử dụng tính năng free. Nếu bạn dùng nhiều hơn, phải trả thêm phí. Khi đó công việc sau khi code xong chỉ là nhấn nút push lên gitlab, ngồi uống cafe và đợi thành qủa.

Để tiết kiệm thời gian cài đặt và mọi thứ suôn sẻ, bạn cần có một chút kiến thức về VPS, SSH, public key, private key…

Một số thứ cần chuẩn bị trước khi bắt tay vào làm.

  • Domain: https://noithatrongviet.com
  • VPS tại vultr.com. Đã cài đặt nginx, thêm domain vào VPS. Xem hướng dẫn
  • Source code: ReactJs
  • Tạo SSH key trong VPS
  • Thêm SSH key vào Gitlab, CI/CD của Project
  • Cài đặt Gitlab Runner
  • Thêm file .gitlab-ci.yml tại folder root trên Gitlab

Chuẩn bị project ReactJs trên Gitlab

Tạo public key & private key cho CI/CD

Login vào SSH của VPS gõ lệnh

ssh-keygen -t rsa -C "[email protected]" -P "" -q -f ~/.ssh/gitlab

Hệ thống tự sinh ra 2 file là gitlab, và gitlab.pub

Trong cửa sổ lệnh terminal gõ:

cat gitlab.pub

Copy đoạn code public key, dán vào mục SSH của tài khoản Gitlab.

Tiếp theo gõ lệnh cat gitlab để lấy private key

cat gitlab

Private key này sẽ gán vào mục Settings CI/CD của repository. Trong mục Variables tạo 1 key mới là SSH_PRIVATE_KEY, dán private key của bạn vào đây

Quay lại cửa sổ Terminal. Gõ nano authorized_keys. Copy public key của bạn vào đây và lưu lại. Chú ý phân quyền đọc file

nano authorized_keys

Tiếp theo gõ nano config

nano config

Nhập code bên dưới và lưu lại

Host gitlab.com
  UseKeychain yes
  AddKeysToAgent yes
  PreferredAuthentications publickey
  IdentityFile ~/.ssh/gitlab

Để kiểm tra private key hoạt động chưa, gõ

ssh -i ~/.ssh/gitlab -o StrictHostKeyChecking=no username@ip-host -p PORT

Mục đích là để có thể login SSH và đẩy file lên mà không cần đăng nhập. Để push file sau khi deploy xong vào VPS, chúng ta sử dụng công cụ rsync. Đọc thêm bài viết hướng dẫn tại: Rsync – Công cụ đồng bộ dữ liệu hiệu quả – Cảm ơn hocvps.com đã viết bài này

Cài đặt Gitlab Runner

Gitlab Runner là thành phần cực kỳ quan trọng trong workflow Gitlab CI. Nếu không có Runner thì sẽ không có lệnh test, deploy nào được thực thi. Runner có nhiều loại, phân biệt dựa vào cái gọi là executor. Khi khởi tạo runner, bạn sẽ phải chọn nó là loại executor nào, và nó sẽ quyết định môi trường thực thi các câu lệnh trong file config ở trên. Bạn có thể tham khảo link https://docs.gitlab.com/runner/executors/ để biết sự khác nhau của các executor cũng như cách cài đặt, cấu hình chúng.

Trong repository setting, mục CI/CD, trên VPS mình chọn cài manual, excutor chọn Shell. Hoặc dùng luôn Share Runner mà không cần cài đặt cũng được.

Đến đây tạm xong phần cài đặt cần thiết. Chúng ta sẽ quay lại repo trên Gitlab.

Thêm file .gitlab-ci.yml tại folder root trên Gitlab

Mặc định Gitlab không có cơ chế nào về CI cho dự án của bạn, chỉ khi nào dự án của bạn có file .gitlab-ci.yml nằm ở thư mục gốc thì Gitlab mới nhận dạng được dự án của bạn muốn áp dụng Gitlab CI. File này có định dạng và cần hợp lệ thì mới có thể hoạt động được, không thì khi bạn push code lên thì Gitlab sẽ báo lỗi file định dạng nội dung của file cấu hình không hợp lệ. Tham khảo cú pháp của cấu hình này tại https://docs.gitlab.com/ce/ci/yaml/

Chú ý phải đúng format. Bạn có thể sửa trực tiếp trên Gitlab, nó sẽ kiểm tra giúp file có đúng format hay không. Đoạn code dưới đây làm 2 việc. Đầu tiên build code từ branh master ra bản release. Câu lệnh npm run build –prod khá quen thuộc. Sau đó deploy lên production thông qua Rsync kết hợp SSH.

# Using the node image to build the React app

image: node:latest
variables:
  PUBLIC_URL: /
# Cache node modules - speeds up future builds
cache:
  paths:
  - node_modules
stages:
- build
- deploy
build:
  stage: build
  script:
    - npm install # Install all dependencies
    - npm run build --prod # Build for prod
  artifacts:
    paths:
    - build 
  only:
    - master # Only run on master branch
deploy_production:
  stage: deploy
  image: ubuntu
  before_script:
  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
  - eval $(ssh-agent -s)
  - mkdir -p ~/.ssh
  - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
  - chmod 700 ~/.ssh/id_rsa
  - eval "$(ssh-agent -s)"
  - ssh-add ~/.ssh/id_rsa
  - ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
  - apt-get install rsync -y -qq
  - apt-get install curl -y -qq

  script:
    - echo "Deploying to server"
    - ssh -i ~/.ssh/gitlab -o StrictHostKeyChecking=no USERNAME@IP-HOST -p PORT
    - rsync -avz --progress -a -e "ssh -p PORT" build/ USERNAME@IP-HOST:/home/www/noithatrongviet.com/public_html
    - echo "Deployed"
  environment:
    name: production
    url: http:noithatrongviet.com
  only:
    - master # Only run on master branch

Chú các thông tin quan trọng: USERNAME@IP-HOST PORT là thông tin tên đăng nhập, port SSH của bạn. /home/www/noithatrongviet.com/public_html thay = đường dẫn trên host của bạn. Xong nhấn nút Commit và vào mục CI/CD -> Pipelines hoặc CI/CD -> Jobs kiểm tra kết quả của bạn.Tận hưởng thành quảChúc các bạn thành công. Từ nay cứ commit code là Gitlab tự động build và deploy lên host cho bạn, quá đã phải không.

How to Upload Multiple Files React Native

Khi lập trình ứng dụng mobile, có khá nhiều thứ liên quan đến hình ảnh. Hướng dẫn sau đây sẽ giúp bạn có thể upload nhiều hình ảnh trong app React Native, tương tự Facebook hay instagram…

Chuẩn bị

Trong hướng dẫn này sử dụng api code dot.net core. Bạn có thể sử dụng ngôn ngữ khác như nodejs, php, java… Bạn có thể tham khảo thêm tại đây.

Trong React Native sử dụng 3 thư viện

npm install react-native-image-crop-picker'
npm install react-native-actionsheet
npm install axios

Các bạn tham khảo thêm ở github của thư viện để biết cách cấu hình chi tiết.

Code backend API

[HttpPost]
[Route("")]
public async Task<IActionResult> Post([FromForm]List<IFormFile> files)
{
    try
    {
        var today = DateTime.Today;
        var uploadPath = @"C:\web\files\files"; // Path.Combine(_hostingEnvironment.WebRootPath, "Content", "Files");
        uploadPath += $"\\{today.Year}\\{today.Month}".PadLeft(2, '0');
        if (!Directory.Exists(uploadPath))
        {
            Directory.CreateDirectory(uploadPath);
        }
        
        var fileUrls = new List<string>();
        foreach (var file in files)
        {
            if (file.Length > 0)
            {
                var fileExtension = Path.GetExtension(file.FileName);
                string fileName = $"{FileHelper.GenerateUniqueFileName}{fileExtension}";
                await file.CopyToAsync(stream);
                var fileUrl = $"files/{today.Year}/{today.Month}".PadLeft(2, '0') + "/" + fileName;
                fileUrls.Add(fileUrl);
            }
        }

        var fileItemResponse = new ResultDataObject
        {
            Data = fileUrls,
            Code = (int)ErrorCodeResultEnum.Ok,
            Message = "Ok"
        };
        return Ok(fileItemResponse);
    }
    catch (Exception ex)
    {
        var fileItemResponse = new ResultDataObject
        {
            Message = ex.Message,
            Code = (int)ErrorCodeResultEnum.Failure
        };
        return Ok(fileItemResponse);
    }
}

Code React Native

Chọn ảnh từ thư viện sử dụng ImagePicker, sử dụng state để lưu lại ảnh đã chọn

onActionSelectPhotoDone = index => {
    switch (index) {
      case 0:
        ImagePicker.openCamera({}).then(image => {
          this.setState({
            localPhotos: [...this.state.localPhotos, image]
          });
        });
        break;
      case 1:
        ImagePicker.openPicker({
          multiple: true,
          maxFiles: 10,
          mediaType: 'photo'
        }).then(images => {
          images.forEach((image) => {
           this.setState({
            localPhotos: [...this.state.localPhotos, image]
          });
          });
        }).catch(error => {
          alert(JSON.stringify(error));
        });
        break;
      default:
        break;
    }
  };

Code hiển thị hình ảnh sau khi chọn

renderListPhotos = localPhotos => {
    const photos = localPhotos.map((photo, index) => (
      <TouchableOpacity key={index}
        onPress={() => {
           this.showActionSheet(index);
        }}
      >
        <Image style={styles.photo} source={{ uri: photo.path }} />
      </TouchableOpacity>
    ));

    return photos;
  };

  renderSelectPhotoControl = localPhotos => {
    return (
      <View style={styles.sectionContainer}>
        <Text style={styles.sectionTitle}>Select photos</Text>
        <ScrollView style={styles.photoList} horizontal={true}>
          {this.renderListPhotos(localPhotos)}
          <TouchableOpacity onPress={this.onPressAddPhotoBtn.bind(this)}>
            <View style={[styles.addButton, styles.photo]}>
              <Text style={styles.addButtonText}>+</Text>
            </View>
          </TouchableOpacity>
        </ScrollView>
      </View>
    );
  };

Sử dụng axios & FormData để Upload

  onDoUploadPress() {
    const { localPhotos } = this.state;
    if (localPhotos && localPhotos.length > 0) {
      let formData = new FormData();
      localPhotos.forEach((image) => {
        const file = {
          uri: image.path,
          name: image.filename || Math.floor(Math.random() * Math.floor(999999999)) + '.jpg',
          type: image.mime || 'image/jpeg'
        };
        formData.append('files', file);
      });

      axios
        .post('https://api.tradingproedu.com/api/v1/fileupload', formData)
        .then(response => {
          this.setState({ logs: JSON.stringify(response.data) });
        })
        .catch(error => {
          alert(JSON.stringify(error));
        });
    } else {
      alert('No photo selected');
    }
  }

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

Full source code https://github.com/tuanitpro/react-native-upload-files

SonarQube: Code Quality and Security

Trong quá trình lập trình & phát triển phần mềm chúng ta thường hay gặp vấn đề về quản lý chất lượng code, code smell, dirty code hay technical debt, thậm chí tồn tại lỗ hổng bảo mật. Nhất là khi dự án có sự tham gia của nhiều member, với trình độ kinh nghiệm khác nhau. Hoặc khi dự án chưa có rules, coding conventions, coding styles rõ ràng. Đến một ngày nào đó cần maintainance dự án hay develop thêm feature mới, chúng ta mới giật mình nhìn lại đống code cũ, tại sao nó lại tệ hại như vậy.

SonarQube là một open source platform, được phát triển bởi SonarSource dành cho việc kiểm tra liên tục chất lượng code (code quality), review code một cách tự động để phát hiện ra các bugs, code smell, lỗ hổng bảo mật cho 25+ ngôn ngữ lập trình khác nhau. SonarQube hỗ trợ báo cáo duplicated code, coding standards, unit tests, code coverage, code complexity, comments, bugs, and security vulnerabilities.

Cài đặt SonarQube trên Windows

Hướng dẫn cài đặt và bắt đầu với SonarQube. Bạn có thể sử dụng Docker hoặc download file về chạy bình thường. Yêu cầu máy có Java JDK 11 trở lên. Cấu hình máy tối thiểu 2GB RAM. https://docs.sonarqube.org/latest/requirements/requirements/

Đầu tiên vào https://www.sonarqube.org/downloads/ chọn bản Community (miễn phí). Giải nén và tìm thư mục bin để chạy. Đối với Windows có thể cài thành service để tiện sử dụng.

Nếu gặp lỗi liên quan JAVA, các bạn chú ý cài lại Java JDK. Sau đó tìm conf/wrapper.conf sửa lại dòng sau:

wrapper.java.command=C:\Program Files\Java\jdk-12.0.2\bin\java

Truy cập: http://localhost:9000 username/password: admin/admin để kiểm tra. Nếu thành công thì chúng ta xong bước cài đặt. Tiếp theo tích hợp dự án vào SonarQube để phân tích.

Tích hợp dự án của bạn vào SonarQube

Sau khi đăng nhập thành công, click vào http://localhost:9000/projects/create để tạo dự án mới. Nhập key & tên dự án, sau đó chuyển sang màn hình nhập key, chọn loại dự án của bạn, và download file scanner của nó về, giải nén và thêm vào biến môi trường %PATH%. Ví dụ mình làm về Windows, dot net core và reactjs thì cần

sonar-scanner-4.0.0.1744-windows
sonar-scanner-msbuild-4.6.2.2108-net46
sonar-scanner-msbuild-4.6.2.2108-netcoreapp2.0

Sau đó mở project của bạn và chạy câu lệnh theo hướng dẫn để sonarqube phân tích.

SonarScanner.MSBuild.exe begin /k:"KEY_CUA_BAN" /d:sonar.host.url="http://localhost:9000" /d:sonar.login="API_KEY_CUA_BAN"

MsBuild.exe /t:Rebuild

SonarScanner.MSBuild.exe end /d:sonar.login="API_KEY_CUA_BAN"

Kết quả sau khi áp dụng

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

Dãy số Fibonacci trong C#

Quy luật của dãy số Fibonacci: số tiếp theo bằng tổng của 2 số trước, 2 số đầu tiên của dãy số là 0, 1. Ví dụ: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, …

Sử dụng yield trong C# để trả về dãy số Fibonacci

static void Main()
{
 foreach(var value in Fibonacci())
 {
 Console.Write(value + " ");
 if(value > 1000) 
 {
 break;
 }
 }
}

static IEnumerable<int>  Fibonacci()
{
 int current = 0;
 int next = 1;
 while(true)
 {
 yield return current;
 int oldCurrent = current;
 current = next;
 next = next + oldCurrent;
 } 
}

Kết quả:

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

Download Or Save Image File From URL

Hướng dẫn này cho phép bạn có thể download một file ((image,video,zip,pdf,doc,xls,ect) từ một đường dẫn url trên website khác, lưu vào ổ cứng hoặc server của bạn. Demo dưới đây viết bằng ASP.NET C#, sẽ lấy ảnh theo đường dẫn http://tuanitpro.com/wp-content/uploads/2014/09/cardvisit.jpg sau đó lưu và thư mục được chỉ định (thư mục Uploads)

Code

void GetFileFromUrl(string fileName, string url)
    {
        byte[] content;
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
        WebResponse response = request.GetResponse();

        Stream stream = response.GetResponseStream();
        using (BinaryReader reader = new BinaryReader(stream))
        {
            content = reader.ReadBytes(500000);
            reader.Close();
        }
        response.Close();
        FileStream fs = new FileStream(fileName, FileMode.Create);
        BinaryWriter bw = new BinaryWriter(fs);
        try
        {
            bw.Write(content);

        }
        finally
        {
            bw.Close();
            fs.Close();
        }
    }

Code Button Download

 protected void Button1_Click(object sender, EventArgs e)
    {
        string url = "http://tuanitpro.com/wp-content/uploads/2014/09/cardvisit.jpg";
        string fileName = Server.MapPath("~/Uploads") + "\\mylogo.jpg";
        GetFileFromUrl(fileName, url);

        Response.Write("The file has been saved at: " + fileName);
    }

Entity framework update modified fields only

Coding

 public virtual int Update(T entity, params Expression<func<t, object="">>[] properties)
        {
            if (entity.Id < 1)
            {
                return Insert(entity).Id;
            }
            if (properties?.Any() == true)
            {
                _dbContext.Attach(entity);
                foreach (var prop in properties)
                {
                    _dbContext.Entry(entity).Property(prop).IsModified = true;
                }
            }
            else
            {
                _dbContext.Entry(entity).State = EntityState.Modified;
            }

            return _dbContext.SaveChanges();
        }

        public virtual int Update(T entity, object replaceBy)
        {
            if (entity.Id < 1)
            {
                return Insert(entity).Id;
            }
            if (replaceBy == null)
            {
                return _dbContext.SaveChanges();
            }

            var properties = replaceBy.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);            
            if (properties?.Any() == true)
            {
                foreach (var prop in properties)
                {
                    var valueOfProp = prop.GetValue(replaceBy, null);
                    var propOfEntity = entity.GetType().GetProperty(prop.Name);
                    propOfEntity.SetValue(entity, Convert.ChangeType(valueOfProp, propOfEntity.PropertyType), null);

                    _dbContext.Entry(entity).Property(propOfEntity.Name).IsModified = true;
                }
            }
            else
            {
                _dbContext.Entry(entity).State = EntityState.Modified;
            }

            return _dbContext.SaveChanges();
        }
</func<t,>

Using

var contact = new Contact { Id = 1, FullName = "Le Thanh Tuan" };

var rs1 = contactService.Update(contact, x => x.FullName);

var rs2 = contactService.Update(contact, new
{
	Id = 1,
	Phone = "0976060432",
	UserName = "tuanitpro",                
	FullName = "Le Thanh Tuan"                             
});

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.

Handle errors in ASP.NET Core

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
if (env.IsProduction() || env.IsStaging())
{
app.UseExceptionHandler(options =>
{
options.Run(async context =>
{
context.Response.StatusCode = 500;
context.Response.ContentType = “text/plain”;
await context.Response.WriteAsync(“CONTACT: 097 6060 432”);

var exceptionHandlerPathFeature =
context.Features.Get();
var now = System.DateTime.Now;
var eventLog = new EventLog
{
DateCreated = now,
RequestPath = exceptionHandlerPathFeature?.Path,
IPAddress = context.Request.HttpContext.Connection.RemoteIpAddress.ToString(),
Message = exceptionHandlerPathFeature?.Error.Source
};
string eventLogString = JsonConvert.SerializeObject(eventLog);

eventLogString += exceptionHandlerPathFeature?.Error.StackTrace + “\n\r”;

File.AppendAllText(“log.txt”, eventLogString);

ColorLife.Core.Helpers.IEmailProvider emailProvider = new ColorLife.Core.Helpers.EmailProvider(“[Urgent] Bug “, “[email protected]”);
emailProvider.Send(eventLogString, null);
});
});
app.UseHsts();
}

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