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.

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

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