Entity framework update modified fields only

Coding

 public virtual int Update(T entity, params Expression>[] 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();
        }

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

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

npx create-react-js pagination

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

npm i bootstrap countries-api prop-types react-flags

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

import "bootstrap/dist/css/bootstrap.min.css";

Components

Trong folder src, tạo folder components

Tạo 2 component

  • CountryCard.jsx Component
  • Pagination.jsx Component

CountryCard Component

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 (
            <Fragment>
                <div className="col-sm-6 col-md-4 country-card">
                    <div className="country-card-container border-gray rounded border mx-2 my-3 d-flex flex-row align-items-center p-0 bg-light">
                        <div className="h-100 position-relative border-gray border-right px-2 bg-white rounded-left">
                            <Flag country={code2} format="png" pngSize={64} basePath="./img/flags" className="d-block h-100" />
                        </div>
                        <div className="px-3">
                            <span className="country-name text-dark d-block font-weight-bold">
                                {name.common}
                            </span>
                            <span className="country-region text-secondary text-uppercase">
                                {region}
                            </span>
                        </div>
                    </div>
                </div>
            </Fragment>
        )
    }
}

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

Pagination Component

 
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 = &#91;leftSpillPage, ...extraPages, ...pages&#93;;
      } else if (!leftSpill && rightSpill) {
        const extraPages = range(endPage + 1, endPage + singleSpillOffset);
        pages = &#91;...pages, ...extraPages, rightSpillPage&#93;;
      } else if (leftSpill && rightSpill) {
        pages = &#91;leftSpillPage, ...pages, rightSpillPage&#93;;
      }

      return &#91;1, ...pages, totalPages&#93;;
    }

    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 (
      <Fragment>
        <nav aria-label="Countries Pagination">
          <ul className="pagination">
            {pages.map((page, index) => {
              if (page === LEFT_PAGE)
                return (
                  <li key={index} className="page-item">
                    <a
                      className="page-link"
                      href="#"
                      aria-label="Previous"
                      onClick={this.handleMoveLeft}
                    >
                      <span aria-hidden="true">&laquo;</span>
                      <span className="sr-only">Previous</span>
                    </a>
                  </li>
                );

              if (page === RIGHT_PAGE)
                return (
                  <li key={index} className="page-item">
                    <a
                      className="page-link"
                      href="#"
                      aria-label="Next"
                      onClick={this.handleMoveRight}
                    >
                      <span aria-hidden="true">&raquo;</span>
                      <span className="sr-only">Next</span>
                    </a>
                  </li>
                );

              return (
                <li
                  key={index}
                  className={`page-item${
                    currentPage === page ? " active" : ""
                  }`}
                >
                  <a
                    className="page-link"
                    href="#"
                    onClick={e => this.handleClick(page, e)}
                  >
                    {page}
                  </a>
                </li>
              );
            })}
          </ul>
        </nav>
      </Fragment>
    );
  }
}

Pagination.propTypes = {
  totalRecords: PropTypes.number.isRequired,
  pageLimit: PropTypes.number,
  pageNeighbours: PropTypes.number,
  onPageChanged: PropTypes.func
};

export default Pagination;

App.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 <CountryCard key={country.cca3} country={country} />
  }
  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 (
      <div className="container mb-5">
        <div className="row d-flex flex-row py-5">
          <div className="w-100 px-4 py-5 d-flex flex-row flex-wrap align-items-center justify-content-between">

            <div className="d-flex flex-row align-items-center">
              <h2 className={headerClass}>
                <strong className="text-secondary">{totalCountries}</strong>{" "}
                Countries
              </h2>
              {currentPage && (
                <span className="current-page d-inline-block h-100 pl-4 text-secondary">
                  Page <span className="font-weight-bold">{currentPage}</span> /{" "}
                  <span className="font-weight-bold">{totalPages}</span>
                </span>
              )}
            </div>
            <div className="d-flex flex-row py-4 align-items-center">
              <Pagination
                totalRecords={totalCountries}
                pageLimit={18}
                pageNeighbours={1}
                onPageChanged={this.onPageChanged}
              />
            </div>
          </div>
          {displayCountries}
        </div>
      </div>
    );
  }
}

export default App;


Download mã nguồn ở đây

GitHub

Sử dụng cache trong ASP.NET MVC với Redis Cache, Memory Cache

Khi lập trình ứng dụng chúng ta hay gặp vấn đề làm sao cho tối ưu tài nguyên của hệ thống, tăng tốc độ trải nghiệm từ phía end user. Đặc biệt là các ứng dụng web.

Cache là gì ? 
Lý thuyết: Cache là tên gọi của bộ nhớ đệm – nơi lưu trữ các dữ liệu nằm chờ các ứng dụng hay phần cứng xử lý. Mục đích của nó  để tăng tốc độ xử lý (có sẵn xài liền không cần tốn thời gian đi lùng sục tìm kéo về).

Thực tế: Cache là các dữ liệu trong phiên làm việc trước của các ứng dụng, chương trình mà hệ điều hành lưu lại nhằm giúp việc tải data trong các phiên làm việc sau được nhanh hơn.

Tip này hướng dẫn các bạn cách implement cache vào ứng dụng, sử dụng thư viện đã được wrap lại từ Memery Cache & Redis Cache

Đầu tiên tải về thư viện tại Nuget: https://www.nuget.org/packages/colorlife.tuanitpro.com/

Hoặc cài đặt trực tiếp trong Visual Studio

Tạo class Customer để test

public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

Sử dụng Memory Cache

  private static void MemoryCache_Customer_Test()
        {
            ICacheProviderFactory cacheProvideractory = new CacheProviderFactory();
            IDataCacheProvider dataCacheProvider = cacheProvideractory.CreateFactory();
            Customer customer = new Customer
            {
                FirstName = "Tuan",
                LastName = "Le"
            };
            string cacheKey = "Memory_Cache_Customer";
            dataCacheProvider.Set(cacheKey, customer);
            Console.WriteLine("Before cache: " + customer.FirstName + " " + customer.LastName);
             
            Console.WriteLine("After Update: " + customer.FirstName + " " + customer.LastName);

            var customerInCache = dataCacheProvider.Get(cacheKey);

            Console.WriteLine("Get Customer In MemoryCache: " + customerInCache.FirstName + " " + customerInCache.LastName);
        }

Sử dụng Radis Cache
Đầu tiên tải Redis cho Windows & cài đặt tại: https://github.com/MicrosoftArchive/redis/releases

Code:

  private static void RedisCache_Customer_Test()
        {
            ICacheProviderFactory cacheProvideractory = new CacheProviderFactory(new CacheProviderOptions
            {
                SlidingExpiration = TimeSpan.FromSeconds(30),
                ConnectionString = "localhost:6379" // mặc định
            }, CacheType.Redis);
            IDataCacheProvider dataCacheProvider = cacheProvideractory.CreateFactory();
            Customer customer = new Customer
            {
                FirstName = "Tuan",
                LastName = "Le"
            };
            string cacheKey = "RedisCache_Customer";
            dataCacheProvider.Set(cacheKey, customer);
            Console.WriteLine("Before cache: " + customer.FirstName + " " + customer.LastName);
            Thread.Sleep(5000);
            customer.LastName = "Le_Updated";
            Console.WriteLine("After Update: " + customer.FirstName + " " + customer.LastName);

            var customerInCache = dataCacheProvider.Get(cacheKey);

            Console.WriteLine("Customer In RedisCache: " + customerInCache.FirstName + " " + customerInCache.LastName);
        }

Thư viện còn hỗ trợ một số chức năng nho nhỏ khác, sẽ nói trong bài viết khác. Chúc các bạn thành công 🙂

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

binance price alert, coin watchlists using c#

Ứng dụng theo dõi giá của coin trên sàn Binance & thông báo khi đạt mức giá quy định.

Lấy danh sách coin trên Binance

var listSymbol = new List();
            using (var client = new BinanceClient())
            {
                var rs = await client.GetAllPricesAsync();
                if (rs.Success && rs.Data != null)
                {
                    foreach (var item in rs.Data)
                    {
                        listSymbol.Add(new SelectListItem
                        {
                            Value = item.Symbol,
                            Text = item.Symbol + " " + item.Price
                        });
                    }
                }
            }

 

Tham khảo: https://github.com/JKorf/Binance.Net

Source code: https://github.com/tuanitpro/binance-price-alert

Hướng dẫn tạo Toggle (Collapsible) ẩn/hiện trong Angular 4

Khi thiết kế website chúng ta hay có nhu cầu ẩn hiện một div / panel, hay một vùng dữ liệu nào đó. Khi người dùng bấm vào sẽ hiển thị nội dung, giúp tiết kiệm không gian hiển thị.

Link demo trên W3school, sử dụng Bootstrap. Link

Trong phạm vi bài viết này chúng ta sẽ thực hiện nó trên angular4. Chúng ta sẽ viết một component để tùy ý sử dụng, viết một lần dùng ở nhiều nơi.

Demo 

Tạo  component collapsible.component với 2 file là collapsible.component.html & collapsible.component.ts

Code file HTML

<section id="tuanitpro_collapse">
  <div class="filter">
    <a (click)="toggleCollapse()" title="Click để đóng mở">{{title || 'Tìm kiếm'}}
    <i class="fa fa-times" *ngIf="!isCollapseOpen"></i>
    <i class="fa fa-search" *ngIf="isCollapseOpen"></i>
  </a>
  </div>
  <div class="filter_panel" [hidden]="isCollapseOpen">
    <div class="x_panel">
      <div class="x_content">
        <ng-content></ng-content>
      </div>
    </div>
  </div>
</section>

Code file .ts

import { Component, OnInit, Input, AfterViewInit, ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'app-collapsible',
  templateUrl: './collapsible.component.html'
})
export class CollapsibleComponent implements OnInit, AfterViewInit {
  @Input() title: string;
  @Input() uid: string;

  public isCollapseOpen = true;
  constructor(private ref: ChangeDetectorRef) { }

  ngOnInit() {
  }
  ngAfterViewInit() {
    const savedState = localStorage.getItem(this.uid + '_filterKey');
    if (savedState) {
      if (savedState === 'true') {
        this.isCollapseOpen = true;
      } else {
        this.isCollapseOpen = false;
      }
    }
    this.ref.detectChanges();

  }
  toggleCollapse() {
    const currentState = this.isCollapseOpen;
    this.isCollapseOpen = !currentState;
    localStorage.setItem(this.uid + '_filterKey', String(this.isCollapseOpen));
  }
}

Cách dùng:

import vào app.module.ts

import { CollapsibleComponent } from ‘./collapsible.component’;
@NgModule({
declarations: [
CollapsibleComponent
],
Trong UI cần ẩn hiện
<app-collapsible title="Tiêu đề" uid="category">
<div class="panel">
Nội dung cần ẩn hiện
</div>
</app-collapsible>

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