Restful API using ASP.NET Core Web API + React

min read

Vừa qua mình đã tạo một ví dụ về Login & Register bằng ASP Core 2.1 + React. Tạo dự án thông qua Visual Studio 2019 đã có tích hợp sẵn React cho ta chọn lựa để tạo project. 

Demo:

Github : Download project

Nhưng hôm này mình muốn tạo project xử lý Backend riêng bên Asp.net Core Web API, để xây dựng web API riêng biệt tách riêng hoàn toàn với React.
Sau đó mình sẽ tạo một thư mục ClientApp thông qua câu lệnh sau để tạo project React theo ý muốn : npx create-react-app ClientApp , Sau đó mình sẽ copy thư mục ClientApp này vào thư mục gốc của project web API

Ok, Đầu tiên mình sẽ tạo project Web API như sau:

Restful API using ASP.NET Core Web API + React

Restful API using ASP.NET Core Web API + React

Restful API using ASP.NET Core Web API + React

Restful API using ASP.NET Core Web API + React

Restful API using ASP.NET Core Web API + React

Xây dựng Backend trong Asp.net Core Web API

Sau khi tạo được project Asp.net Core Web API xong.
Mở file appsettings.json cài đặt chuỗi kết nối SQL SERVER của ta, bạn nào chưa cài SQL SERVER thì cài vào nhé, mình cài rồi mình sài SQL SERVER 2012

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "EFDataContext": "Server=DESKTOP-2F1MPHI\\SQLEXPRESS;Database=DemoReactCrud;Trusted_Connection=True;MultipleActiveResultSets=true"
  }

}

Ta tiến hành tạo một số model sau:
+ Tạo đường dẫn file Models/Product.cs, model Product.cs chưa các thuộc tính chứa thông tin dữ liệu

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebApiAspCore.Models
{
    public class Product
    {
        public int idProduct { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
        public string  Slug { get; set; }
        public int idCategory { get; set; }
        public Category Category { get; set; }

    }
}

+ Models/Category.cs : Chứa các thuộc tính loại Category 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebApiAspCore.Models
{
    public class Category
    {
        public int idCategory { get; set; }
        public string Name { get; set; }
        public string SlugCategory { get; set; }
        public virtual ICollection<Product> Products { get; set; }
    }
}

+ Models/FormModelView.cs : cài đặt các lớp cho chức năng Form (Thêm Product, Thêm Category) 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebApiAspCore.Models
{
    public class FormCategoryView
    {
        public string Name { get; set; }
        public string SlugCategory { get; set; }
    }
    public class FormProductView
    {
        public string Title { get; set; }
        public string Body { get; set; }
        public string Slug { get; set; }
        public int idCategory { get; set; }
    }
}

+ Models/EFDataContext.cs : Viết lớp kế thừa tử DBContext để xử lý chuổi kết nối SQL SERVER, giúp chúng ta thiết lập các khóa chính, khóa ngoại của bạn, đồng thời giúp ta truy vấn thông tin dữ liệu từng model table trong Database 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using WebApiAspCore.Models;
namespace WebApiAspCore.Models
{
    public class EFDataContext : DbContext
    {
        public EFDataContext(DbContextOptions<EFDataContext> options)
              : base(options) { }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //config primary key(product,category)
            modelBuilder.Entity<Product>().HasKey(s => s.idProduct);
            modelBuilder.Entity<Category>().HasKey(s => s.idCategory);

            //set config replationship Product vs Category
            modelBuilder.Entity<Category>()
                .HasMany<Product>(s=>s.Products)
                .WithOne(a=>a.Category)
                .HasForeignKey(a => a.idCategory)
                .OnDelete(DeleteBehavior.Restrict);

        }
        public DbSet<Product> Products { get; set; }
        public DbSet<Category> Categories { get; set; }

    }
}

Các Model trên mình có chia sẻ nhiều trong các bài viết về ASP.NET MVC, ASP CORE 2.1 các bạn có thể xem lại nhé

Tiếp tục bạn mở file Startup.cs trong project lên thêm dòng lệnh sau vào hàm ConfigureServices để cài đặt gọi chuổi kết nối

services.AddDbContext<EFDataContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("EFDataContext")));

Cài đặt Cors trong Startup.cs để tránh bị lỗi Cors, khi request từ một server khác. Ở đây React sẽ chạy port http://localhost:3000, nhưng WEB API chạy port khác https://localhost:44362/ cho nên ta cần cài đặt Cors để tranh bị lỗi như hình sau

Restful API using ASP.NET Core Web API + React

Thêm đoạn code sau vào hàm ConfigureServices 

services.AddCors(options =>
            {
                options.AddPolicy(name: MyAllowSpecificOrigins,
                                  builder =>
                                  {
                                      builder.WithOrigins("http://localhost:3000")
                                                              .AllowAnyHeader()
                                                        .AllowAnyMethod();
                                  });
            });

Add tiếp code sau vào hàm Configure

app.UseCors(MyAllowSpecificOrigins);

Full source Startup.cs 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using WebApiAspCore.Models;
namespace WebApiAspCore
{
    public class Startup
    {
        readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy(name: MyAllowSpecificOrigins,
                                  builder =>
                                  {
                                      builder.WithOrigins("http://localhost:3000")
                                                              .AllowAnyHeader()
                                                        .AllowAnyMethod();
                                  });
            });
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            services.AddDbContext<EFDataContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("EFDataContext")));

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }
            app.UseCors(MyAllowSpecificOrigins);
            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

Ok vậy là ta cài đặt xong những thư cần thiết cho project của ta, giờ chúng ta cần phải chạy lệnh Migration trong project ASP để tạo database 
# add-migration dbreact
# update-database 

Bạn chạy 2 câu lệnh trên, nếu không có xãy ra lỗi gì, thì bạn sẽ được một thư mục Migrations chưa các thông tin setup database. Okay bạn thử mở SQL SERVER lên xem có database vừa tạo hay không

Okay giờ ta hãy tạo Controller để xử lý một số request từ React thôi: 

+ Tạo ProductsController.cs trong thư mục Controllers như sau: 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using WebApiAspCore.Models;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace WebApiAspCore.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController : ControllerBase
    {
        public readonly EFDataContext _db;
        public ProductsController(EFDataContext db)
        {
            this._db = db;
        }
        // GET: api/<ProductsController>
        [HttpGet]
        public IEnumerable<Product> Get()
        {
            var products = _db.Products.Select(s => new Product
            {
                idProduct = s.idProduct,
                Title = s.Title,
                Body = s.Body,
                Slug = s.Slug,
                idCategory = s.idCategory,
                Category = _db.Categories.Where(a => a.idCategory == s.idCategory).FirstOrDefault()
            }).ToList();
            return products;
        }

        // GET api/<ProductsController>/5
        [HttpGet("{id}")]
        public Product Get(int id)
        {
            var products = _db.Products.Select(s => new Product
            {
                idProduct = s.idProduct,
                Title = s.Title,
                Body = s.Body,
                Slug = s.Slug,
                idCategory = s.idCategory,
                Category = _db.Categories.Where(a => a.idCategory == s.idCategory).FirstOrDefault()
            }).Where(a => a.idProduct == id).FirstOrDefault();
            return products;
        }

        // POST api/<ProductsController>
        [HttpPost]
        public async Task<IActionResult> Post([FromBody] FormProductView _product)
        {

            var product = new Product()
            {
                Title = _product.Title,
                Body = _product.Body,
                Slug = _product.Slug,
                Category = _db.Categories.Find(_product.idCategory)
            };
            _db.Products.Add(product);
            await _db.SaveChangesAsync();
            if (product.idProduct > 0)
            {
                return Ok(1);
            }
            return Ok(0);
        }

        // PUT api/<ProductsController>/5
        [HttpPut("{id}")]
        public async Task<IActionResult> Put(int id, [FromBody] FormProductView _user)
        {
            var product = _db.Products.Find(id);
            product.Title = _user.Title;
            product.Body = _user.Body;
            product.Slug = _user.Slug;
            product.Category = _db.Categories.Find(_user.idCategory);
            await _db.SaveChangesAsync();
            return Ok(1);
        }

        // DELETE api/<ProductsController>/5
        [HttpDelete("{id}")]
        public async Task<IActionResult> Delete(int id)
        {
            var product = _db.Products.Find(id);
            _db.Products.Remove(product);
            await _db.SaveChangesAsync();
            return Ok(1);
        }
    }
}

+ Tạo CategoriesController.cs trong thư mục Controllers

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using WebApiAspCore.Models;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace WebApiAspCore.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class CategoriesController : ControllerBase
    {
        public readonly EFDataContext _db;
        public CategoriesController(EFDataContext db)
        {
            this._db = db;
        }
        // GET: api/<CategoriesController>
        [HttpGet]
        public IEnumerable<Category> Get()
        {
            return _db.Categories.ToList() ;
        }

        // GET api/<CategoriesController>/5
        [HttpGet("{id}")]
        public Category Get(int id)
        {
            return _db.Categories.Find(id);
        }

        // POST api/<CategoriesController>
        [HttpPost]
        public async Task<IActionResult> Post([FromBody] FormCategoryView _category)
        {
            var cate = new Category()
            {
                Name = _category.Name,
                SlugCategory = _category.SlugCategory
            };
            _db.Categories.Add(cate);
            await _db.SaveChangesAsync();
            if (cate.idCategory > 0)
            {
                return Ok(1);
            }
            return Ok(0);
        }

        // PUT api/<CategoriesController>/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody] string value)
        {
        }

        // DELETE api/<CategoriesController>/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}

Trong 2 file Controller trên các bạn xem lại từng hàm cho dễ hiểu nhé, giờ ta hãy run project , sau đó mở ứng dụng Postman lên để request thử xem mấy hàm (POST,GET,PUT,DELETE) trong 2 file controller trên hoạt động không nhé

Restful API using ASP.NET Core Web API + React

Restful API using ASP.NET Core Web API + React

Restful API using ASP.NET Core Web API + React

Xây dựng FrontEnd trong React

Sau khi tạo được project web Asp.net Core Web API. thì tiếp theo chúng ta cần phải, tải project React về, sao đó chỉ cần copy thư mục vừa tải xong paste vào thư mục web API Core vừa tạo bên trên thôi là được

npx create-react-app ClientApp

Cài một số thư viện cần thiết cho react như sau:

    "@material-ui/core": "^4.11.0",
    "@material-ui/icons": "^4.9.1",
    "@material-ui/lab": "^4.0.0-alpha.56",
    "axios": "^0.21.0",
    "bootstrap": "^3.4.1",
    "react": "^16.14.0",
    "react-bootstrap": "^0.31.5",
    "react-dom": "^16.14.0",
    "react-router-bootstrap": "^0.24.4",
    "react-router-dom": "^4.3.1",

Nếu bạn cần sài cái nào thì install thêm, còn không sài thì khỏi install, để giảm nhẹ cho project
Okay, sau khi install đầy đủ thì bạn chỉ cần copy thư mục ClientApp đến Project  Web API , bạn sẽ được project như hình bên dưới đây

Restful API using ASP.NET Core Web API + React

Tạo file đường dẫn thư mục như sau:
+ ClientApp/src/api/apiService.js : dùng chứa các hàm xử lý gửi request đến web api 

import axios from 'axios';
let API_URL="https://localhost:44362/api";
export function callApi(endpoint, method='GET',body){
        return axios({
            method,
            url:`${API_URL}/${endpoint}`,
            data:body,
        }).catch(e=>{
            console.log(e)
        })
}

export function GET_ALL_PRODUCTS(endpoint){
    return callApi(endpoint,"GET");
}
export function GET_PRODUCT_ID(endpoint,id){
    return callApi(endpoint+"/"+id,"GET");
}
export function POST_ADD_PRODUCT(endpoint,data){
    return callApi(endpoint,"POST",data);
}
export function PUT_EDIT_PRODUCT(endpoint,data){
    return callApi(endpoint,"PUT",data);
}
export function DELETE_PRODUCT_ID(endpoint){
    return callApi(endpoint,"DELETE");
}
export function GET_ALL_CATEGORIES(endpoint){
    return callApi(endpoint,"GET");
}

Tạo một số component sau:
+ ClientApp/components/MenuTop.js : component MenuTop.js chứa layout top cho đẹp thôi ::)

import React from 'react';
import {Link} from 'react-router-dom'
import { makeStyles } from '@material-ui/core/styles';
import {AppBar,Toolbar,Typography,IconButton} from '@material-ui/core';
import MenuIcon from '@material-ui/icons/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import Menu from '@material-ui/core/Menu';
import MoreVertIcon from '@material-ui/icons/MoreVert';

const useStyles = makeStyles((theme) => ({
    root: {
       width:'100%',
       flexGrow: 1,
    },
    title:{
        flexGrow:1
    },
    linkTo:{
      textDecoration:'none',
      color:'#000'
    },
    linkHome:{
      textDecoration:'none',
      color:'#fff'
    }
  }));
export default function MenuTop() {
  const classes = useStyles();
  const [anchorEl, setAnchorEl] = React.useState(null);
  const isMenuOpen = Boolean(anchorEl);
  const handleProfileMenuOpen = (event) => {
    setAnchorEl(event.currentTarget);
  };
  const handleMenuClose = () => {
    setAnchorEl(null);
  };
  const menuId = 'primary-search-account-menu';
  const renderMenu = (
    <Menu
      anchorEl={anchorEl}
      anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
      id={menuId}
      keepMounted
      transformOrigin={{ vertical: 'top', horizontal: 'right' }}
      open={isMenuOpen}y

      onClose={handleMenuClose}
    >
      <MenuItem onClick={handleMenuClose}><Link to="/products" className={classes.linkTo}>Product</Link></MenuItem>
      <MenuItem onClick={handleMenuClose}><Link to="/categories" className={classes.linkTo}>Categories</Link></MenuItem>
      </Menu>
  );
    return (
        <div className={classes.root}>
            <AppBar position="static" color="primary">
              <Toolbar>
                <IconButton edge="start" color="inherit" aria-label="menu">
                    <MenuIcon />
                </IconButton>
                <Typography variant="h6" className={classes.title}>
                     <Link to="/" className={classes.linkHome}>Demo React ASP Core</Link>
                </Typography>
                <IconButton edge="end" color="inherit" aria-label="MoreVert" aria-controls={menuId}
                 aria-haspopup="true"
              onClick={handleProfileMenuOpen}>
                    <MoreVertIcon />
                </IconButton>
              </Toolbar>
            </AppBar>
            {renderMenu}
        </div>
    )
}

Đoạn code trên mình có cài đặt 2 link trỏ về component(Product,Categories), để sử dụng được Link ta cần import thư viện 

import {Link} from 'react-router-dom'

Bạn nào chưa tìm hiểu về Material-UI thì lên trang chủ tìm hiểu thêm nhé,

 <MenuItem onClick={handleMenuClose}><Link to="/products" className={classes.linkTo}>Product</Link></MenuItem>
 <MenuItem onClick={handleMenuClose}><Link to="/categories" className={classes.linkTo}>Categories</Link></MenuItem>

+ Tạo file theo đường dẫn ClientApp/components/Home.js 

import React,{useEffect,useState} from 'react'
import { makeStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';
import Alert from '@material-ui/lab/Alert';
import { Redirect } from 'react-router-dom';
import IconButton from '@material-ui/core/IconButton';
import CloseIcon from '@material-ui/icons/Close';
import Button from '@material-ui/core/Button';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import {Link} from 'react-router-dom'
import { GET_ALL_PRODUCTS,DELETE_PRODUCT_ID } from '../api/apiService';
const useStyles = makeStyles((theme) => ({
  root: {
    flexGrow: 1,
    marginTop:20
  },
  paper:{
    width:'100%',
    margin:'auto'
  },
  removeLink:{
    textDecoration:'none'
  }
}));
export default function Home() {
  const classes = useStyles();
  const [products,setProducts] = useState({});
  const [checkDeleteProduct,setCheckDeleteProduct] = useState(false);
  const [close, setClose] = React.useState(false);
  useEffect(() => {
    /* GET ALL PRODUCTS */
    GET_ALL_PRODUCTS(`products`).then(item=>setProducts(item.data))
    
  }, [])
  /* Show body HTML */
  const RawHTML = (body,className) => 
    <div className={className} dangerouslySetInnerHTML={{ __html: body.replace(/\n/g, '<br />')}} />

  /* DELETE PRODUCT ID */
  const deleteProductID = (id)=>{
    
    DELETE_PRODUCT_ID(`products/${id}`).then(item=>{
      console.log(item)
      if(item.data===1){
        setCheckDeleteProduct(true);
        /* UPDATE PRODUCTS */
        setProducts(products.filter(key=>key.idProduct!==id))
      }
    })
  }
 
  return (
    <div className={classes.root}>
         <Grid container spacing={3}>
            <Grid item xs={12}>
              <Paper className={classes.paper}>
                {checkDeleteProduct && <Alert 
                  action={
                  <IconButton
                    aria-label="close"
                    color="inherit"
                    size="small"
                    onClick={() => {
                      setClose(true);
                      setCheckDeleteProduct(false)
                    }}
                  >
                    <CloseIcon fontSize="inherit" />
                  </IconButton>
                }
                >Detele successfuly</Alert>}
              <TableContainer component={Paper}>
                  <Table className={classes.table} aria-label="simple table">
                    <TableHead>
                      <TableRow>
                        <TableCell>Title</TableCell>
                        <TableCell align="center">Body</TableCell>
                        <TableCell align="center">Slug</TableCell>
                        <TableCell align="center">Category</TableCell>
                        <TableCell align="center">Modify</TableCell>
                        <TableCell align="center">Delete</TableCell>
                      </TableRow>
                    </TableHead>
                    <TableBody>
                      {products.length>0 && products.map((row) => (
                        <TableRow key={row.idProduct}>
                          <TableCell component="th" scope="row">{row.title}</TableCell>
                          <TableCell align="left">{RawHTML(row.body,"body")}</TableCell>
                          <TableCell align="center">{row.slug}</TableCell>
                          <TableCell align="center">{row.category.name}</TableCell>
                          <TableCell align="center">
                            <Link to={`/edit/product/${row.idProduct}`} className={classes.removeLink}>
                               <Button size="small" variant="contained" color="primary">Edit</Button></Link>
                            </TableCell>
                          <TableCell align="center">
                           
                              <Button  size="small" variant="contained" color="secondary" onClick={()=>deleteProductID(row.idProduct)}>Remove</Button>
                              
                          </TableCell>
                        </TableRow>
                      ))}
                    </TableBody>
                  </Table>
                </TableContainer>
              </Paper>
            </Grid>
        </Grid>
    </div>
  )
}

Đoạn code component Home.js mình cần import đường dẫn chứa cầu hình request web API vào file Home.js như sau:

import { GET_ALL_PRODUCTS,DELETE_PRODUCT_ID } from '../api/apiService';

Gọi request lấy toàn bộ dữ liệu Product từ Web API thông qua hàm sau đây

GET_ALL_PRODUCTS(`products`).then(item=>setProducts(item.data))

Hiển thị dữ liệu Html mình có cài đặt hàm sau : 

const RawHTML = (body,className) => 
    <div className={className} dangerouslySetInnerHTML={{ __html: body.replace(/\n/g, '<br />')}} />

Sau khi đã có dữ liệu Product , ta sẽ hiện thị dữ liệu ra như đoạn code sau đây

{products.length>0 && products.map((row) => (
                        <TableRow key={row.idProduct}>
                          <TableCell component="th" scope="row">{row.title}</TableCell>
                          <TableCell align="left">{RawHTML(row.body,"body")}</TableCell>
                          <TableCell align="center">{row.slug}</TableCell>
                          <TableCell align="center">{row.category.name}</TableCell>
                          <TableCell align="center">
                            <Link to={`/edit/product/${row.idProduct}`} className={classes.removeLink}>
                               <Button size="small" variant="contained" color="primary">Edit</Button></Link>
                            </TableCell>
                          <TableCell align="center">
                           
                              <Button  size="small" variant="contained" color="secondary" onClick={()=>deleteProductID(row.idProduct)}>Remove</Button>
                              
                          </TableCell>
                        </TableRow>
                      ))}

Xóa Product ta cần nhận idProduct, sau đó request đến Web API để xóa nó, xóa xong, ta cập nhật dữ liệu lại  

const deleteProductID = (id)=>{
    DELETE_PRODUCT_ID(`products/${id}`).then(item=>{
      console.log(item)
      if(item.data===1){
        setCheckDeleteProduct(true);
        /* UPDATE PRODUCTS */
        setProducts(products.filter(key=>key.idProduct!==id))
      }
    })
  }

+ Tạo component Product.js trong thư mục ClientApp/components

import React,{useEffect,useState} from 'react'
import { makeStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import Alert from '@material-ui/lab/Alert';
import { Redirect } from 'react-router-dom';

/*Import api */
import {GET_ALL_CATEGORIES, POST_ADD_PRODUCT} from '../api/apiService';
const useStyles = makeStyles((theme) => ({
  root: {
    flexGrow: 1,
    marginTop:20
  },
  paper: {
    padding: theme.spacing(2),
    margin: 'auto',
    maxWidth: 600,
  },
  title:{
    fontSize:30,
    textAlign:'center'
  },
  link:{
    padding:10,
    display:'inline-block'
  },
  txtInput:{
    width:'98%',
    margin:'1%'
  },
  submit: {
    margin: theme.spacing(3, 0, 2),
  },
}));
const currencies = [
    {
      value: 'USD',
      label: '$',
    },
    {
      value: 'EUR',
      label: '€',
    },
    {
      value: 'BTC',
      label: '฿',
    },
    {
      value: 'JPY',
      label: '¥',
    },
  ];
  
export default function Product() {
  const classes = useStyles();
  /* SET ATTRIBUTE FORM ADD PRODUCT */
  const [checkAdd,setCheckAdd] = useState(false);
  const [title,setTitle] = useState(null)
  const [body,setBody] = useState(null)
  const [slug,setSlug] = useState(null)
  const [category, setCategory] = useState(0);
  const [categories,setCategories] = useState({});

  /* BEFORE RUN */
  useEffect(() => {
    /* GET API CATEGORIES */
    GET_ALL_CATEGORIES('categories').then(item=>{
      setCategories(item.data); 
    });
    
  }, [])

  /* EVENT CHANGE TEXTFIELD IN FORM */
  const handleChangeTitle = (event) => {
    setTitle(event.target.value)
  }
  const handleChangeBody = (event) => {
    setBody(event.target.value)
  }
  const handleChangeSlug = (event) => {
    setSlug(event.target.value)
  }
  const handleChangeCategory = (event) => {
    setCategory(event.target.value);
  };

  /* EVENT BUTTON SUBMIT FORM ADD PRODUCT */
  const addProduct = (event)=>{
    event.preventDefault();
    if(title!=="" && body!=="" && slug!=="" && category>0){
      let product = {
         Title:title,
         Body:body,
         Slug:slug,
         idCategory:category
      }
      POST_ADD_PRODUCT(`products`,product).then(item=>{
         if(item.data===1){
          setCheckAdd(true);
         }
      })
    }
    else{
       alert("Bạn chưa nhập đủ thông tin!");
    }
  }

  /* CHECK setAdd, if true redirect to Home component */
  if(checkAdd){
    return <Redirect to="/" />
  }

  return (
    <div className={classes.root}>
         <Grid container spacing={3}>
            <Grid item xs={12}>
              <Paper className={classes.paper}>
                    <Typography className={classes.title} variant="h4">
                         Add Product
                    </Typography>
                    <Grid item xs={12} sm container>
                        <Grid item xs={12}>
                            <Typography gutterBottom variant="subtitle1">
                                Title
                            </Typography>
                            <TextField id="Title" onChange = {handleChangeTitle} name="Title" label="Title" variant="outlined" className={classes.txtInput} size="small"/>
                        </Grid>
                        <Grid item xs={12}>
                            <Typography gutterBottom variant="subtitle1">
                                Body
                            </Typography>
                            <TextField id="outlined-multiline-static" onChange = {handleChangeBody} label="Body" name="Body"  className={classes.txtInput} multiline rows={4} defaultValue="Body" variant="outlined"/>
                        </Grid>
                        <Grid item xs={12}>
                            <Typography gutterBottom variant="subtitle1">
                                Slug
                            </Typography>
                            <TextField id="Slug" onChange= {handleChangeSlug} name="Slug" label="Slug" variant="outlined" className={classes.txtInput} size="small"/>
                        </Grid>
                        <Grid item xs={12}>
                           <Typography gutterBottom variant="subtitle1">
                                Choose Category
                            </Typography>
                            <TextField
                                id="outlined-select-currency-native"
                                name="idCategory"
                                select
                                value={category}
                                onChange={handleChangeCategory}
                                SelectProps={{
                                    native: true,
                                }}
                                helperText="Please select your currency"
                                variant="outlined"
                                className={classes.txtInput}
                                >
                                <option value="0">Choose category</option>
                                {categories.length>0 && categories.map((option) => (
                                    <option key={option.idCategory} value={option.idCategory}>
                                    {option.name}
                                    </option>
                                ))}
                              </TextField>
                        </Grid>
                        <Grid item xs={12}>
                            <Button type="button" onClick={addProduct}  fullWidth variant="contained" color="primary" className={classes.submit} >
                                Add product
                            </Button>
                        </Grid>
                    </Grid>
              </Paper>
            </Grid>
        </Grid>
    </div>
  )
}

Đoạn code trên ta cũng cần import code sau để sử dụng request API

import {GET_ALL_CATEGORIES, POST_ADD_PRODUCT} from '../api/apiService';

Cài đặt dữ liệu trạng thái ban đầu cho dữ liệu categories như sau

const [categories,setCategories] = useState({});

Gọi hàm GET_ALL_CATEGORIES để nhận tất cả dữ liệu categories từ request Web APi, sao đó setCategories cập nhật dữ liệu trạng thái lại 

 GET_ALL_CATEGORIES('categories').then(item=>{
      setCategories(item.data); 
    });

Hiển thị select option Categories

<TextField
                                id="outlined-select-currency-native" name="idCategory" select
                                value={category} onChange={handleChangeCategory}
                                SelectProps={{
                                    native: true,
                                }}
                                helperText="Please select your currency" variant="outlined" className={classes.txtInput}>
                                <option value="0">Choose category</option>
                                {categories.length>0 && categories.map((option) => (
                                    <option key={option.idCategory} value={option.idCategory}>
                                    {option.name}
                                    </option>
                                ))}
                              </TextField>

Thiếp lập dữ liệu trạng thái ban đầu cho Form thêm product

const [title,setTitle] = useState(null)
const [body,setBody] = useState(null)
const [slug,setSlug] = useState(null)
const [category, setCategory] = useState(0);

Cài đặt sự kiện thay đổi giá trị trạng thái cho từng TextField trong Form

const handleChangeTitle = (event) => {
    setTitle(event.target.value)
  }
  const handleChangeBody = (event) => {
    setBody(event.target.value)
  }
  const handleChangeSlug = (event) => {
    setSlug(event.target.value)
  }
  const handleChangeCategory = (event) => {
    setCategory(event.target.value);
  };
  

Cài đặt trạng thái khi người dùng thêm thành công, sẽ tự đồng Redirect về component Home 

const [checkAdd,setCheckAdd] = useState(false);
 if(checkAdd){
    return <Redirect to="/" />
  }

Thiếp lập tính năng khi người dùng bấm button thêm product, sẽ gọi hàm POST_ADD_PRODUCT để gửi data đển Web API 

const addProduct = (event)=>{
    event.preventDefault();
    if(title!=="" && body!=="" && slug!=="" && category>0){
      let product = {
         Title:title,
         Body:body,
         Slug:slug,
         idCategory:category
      }
      POST_ADD_PRODUCT(`products`,product).then(item=>{
         if(item.data===1){
          setCheckAdd(true);
         }
      })
    }
    else{
       alert("Bạn chưa nhập đủ thông tin!");
    }
  }
  

Thêm sự kiện thêm vào thẻ Button như sau:

<Button type="button" onClick={addProduct}  fullWidth variant="contained" color="primary" className={classes.submit} >
        Add product
</Button>

+ Tạo component EditProduct.js trong thư mục : ClientApp/components

 

import React,{useEffect,useState} from 'react'
import { makeStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import Alert from '@material-ui/lab/Alert';
import { Redirect } from 'react-router-dom';

/*Import api */
import {GET_ALL_CATEGORIES, GET_PRODUCT_ID, PUT_EDIT_PRODUCT} from '../api/apiService';
const useStyles = makeStyles((theme) => ({
  root: {
    flexGrow: 1,
    marginTop:20
  },
  paper: {
    padding: theme.spacing(2),
    margin: 'auto',
    maxWidth: 600,
  },
  title:{
    fontSize:30,
    textAlign:'center'
  },
  link:{
    padding:10,
    display:'inline-block'
  },
  txtInput:{
    width:'98%',
    margin:'1%'
  },
  submit: {
    margin: theme.spacing(3, 0, 2),
  },
}));
export default function EditProduct ({ match, location }){
  const classes = useStyles();
  /* SET ATTRIBUTE FORM EDIT ADD PRODUCT */
  const [checkUpdate,setCheckUpdate] = useState(false);
  const [idProduct,setIdProduct] = useState(0);
  const [title,setTitle] = useState(null)
  const [body,setBody] = useState(null)
  const [slug,setSlug] = useState(null)
  const [category, setCategory] = useState(0);
  const [categories,setCategories] = useState({});

  /* BEFORE RUN */
  useEffect(() => {
    //console.log(location)
    //console.log(match.params.id)
    /* GET PRODUCT ID */
    GET_PRODUCT_ID(`products`,match.params.id).then(product=>{
        setIdProduct(product.data.idProduct)
        setTitle(product.data.title);
        setBody(product.data.body);
        setSlug(product.data.slug);
        setCategory(product.data.category.idCategory)
    })
    /* GET API CATEGORIES */
    GET_ALL_CATEGORIES('categories').then(item=>{
      setCategories(item.data); 
    });
    
  }, [])

  /* EVENT CHANGE TEXTFIELD IN FORM */
  const handleChangeTitle = (event) => {
    setTitle(event.target.value)
  }
  const handleChangeBody = (event) => {
    setBody(event.target.value)
  }
  const handleChangeSlug = (event) => {
    setSlug(event.target.value)
  }
  const handleChangeCategory = (event) => {
    setCategory(event.target.value);
  };

  /* EVENT BUTTON SUBMIT FORM ADD PRODUCT */
  const EditProduct = (event)=>{
    event.preventDefault();
    if(title!=="" && body!=="" && slug!=="" && category>0 && idProduct>0){
      let product = {
         Title:title,
         Body:body,
         Slug:slug,
         idCategory:category
      }
      PUT_EDIT_PRODUCT(`products/${idProduct}`,product).then(item=>{
         if(item.data===1){
          setCheckUpdate(true);
         }
      })
    }
    else{
       alert("Bạn chưa nhập đủ thông tin!");
    }
  }

  /* CHECK setAdd, if true redirect to Home component */
  if(checkUpdate){
    return <Redirect to="/" />
  }

  return (
    <div className={classes.root}>
         <Grid container spacing={3}>
            <Grid item xs={12}>
              <Paper className={classes.paper}>
                    <Typography className={classes.title} variant="h4">
                         Edit Product
                    </Typography>
                    <Grid item xs={12} sm container>
                        <Grid item xs={12}>
                            <Typography gutterBottom variant="subtitle1">
                                Title
                            </Typography>
                            <TextField id="Title" onChange = {handleChangeTitle} value={title} name="Title"  variant="outlined" className={classes.txtInput} size="small"/>
                        </Grid>
                        <Grid item xs={12}>
                            <Typography gutterBottom variant="subtitle1">
                                Body
                            </Typography>
                            <TextField id="outlined-multiline-static" onChange = {handleChangeBody} defaultValue={body}  name="Body"  className={classes.txtInput} multiline rows={4}  variant="outlined"/>
                        </Grid>
                        <Grid item xs={12}>
                            <Typography gutterBottom variant="subtitle1">
                                Slug
                            </Typography>
                            <TextField id="Slug" onChange= {handleChangeSlug} value={slug} name="Slug"  variant="outlined" className={classes.txtInput} size="small"/>
                        </Grid>
                        <Grid item xs={12}>
                           <Typography gutterBottom variant="subtitle1">
                                Choose Category
                            </Typography>
                            <TextField
                                id="outlined-select-currency-native"
                                name="idCategory"
                                select
                                value={category}
                                onChange={handleChangeCategory}
                                SelectProps={{
                                    native: true,
                                }}
                                helperText="Please select your currency"
                                variant="outlined"
                                className={classes.txtInput}
                                >
                                <option value="0">Choose category</option>
                                {categories.length>0 && categories.map((option) => (
                                    <option key={option.idCategory} value={option.idCategory}>
                                    {option.name}
                                    </option>
                                ))}
                              </TextField>
                        </Grid>
                        <Grid item xs={12}>
                            <Button type="button" onClick={EditProduct}  fullWidth variant="contained" color="primary" className={classes.submit} >
                                Update product
                            </Button>
                        </Grid>
                    </Grid>
              </Paper>
            </Grid>
        </Grid>
    </div>
  )
}

Trong component EditProduct.js thì thiết lập cũng giống như component Product.js chỉ khác ở chổ là ở đây mình cần gọi hàm xử lý khác thôi

import {GET_ALL_CATEGORIES, GET_PRODUCT_ID, PUT_EDIT_PRODUCT} from '../api/apiService';

Gọi hàm GET_PRODUCT_ID để nhận dữ liệu product của chính id đó

GET_PRODUCT_ID(`products`,match.params.id).then(product=>{
        setIdProduct(product.data.idProduct)
        setTitle(product.data.title);
        setBody(product.data.body);
        setSlug(product.data.slug);
        setCategory(product.data.category.idCategory)
    });

Gọi GET_ALL_CATEGORIES nhận tất cả dữ liệu categories để hiển thị ra select option, tương tự như trong component Product.js mình có thiết lập

GET_ALL_CATEGORIES('categories').then(item=>{
      setCategories(item.data); 
    });

Thiếp lập tính năng hàm EditProduct, ta cần gọi hàm PUT_EDIT_PRODUCT

const EditProduct = (event)=>{
    event.preventDefault();
    if(title!=="" && body!=="" && slug!=="" && category>0 && idProduct>0){
      let product = {
         Title:title,
         Body:body,
         Slug:slug,
         idCategory:category
      }
      PUT_EDIT_PRODUCT(`products/${idProduct}`,product).then(item=>{
         if(item.data===1){
          setCheckUpdate(true);
         }
      })
    }
    else{
       alert("Bạn chưa nhập đủ thông tin!");
    }
  }

+ Tạo component Category.js trong thư mục : ClientApp/components,  phần component Category này các bạn thiết lập cũng tương tự như các component nãy giờ mình làm, nên mình sẽ bỏ qua phần này 

Mở file App.js trong thư mục ClientApp, chỉnh sửa Route trỏ tới từng component mà mình vừa tạo bên trên

import React, { Component } from 'react';
import { Route } from 'react-router';
import Container from '@material-ui/core/Container';
import CssBaseline from '@material-ui/core/CssBaseline';
import MenuTop from './components/MenuTop';
import Home from './components/Home';
import Product from './components/Product';
import Category from './components/Category';
import EditProduct from './components/EditProduct';
export default class App extends Component {
  displayName = App.name;
  render() {
    return (
       <React.Fragment>
        <CssBaseline />
        <MenuTop />
        <Container maxWidth="md">
           <Route exact path='/' component={Home} />
           <Route path='/home' component={Home} />
           <Route path='/products' component={Product} />
           <Route path='/edit/product/:id' component={EditProduct} />
           <Route path='/categories' component={Category} />
        </Container>
    </React.Fragment>
    );
  }
}

Mở file index.js trong thư mục ClientApp và chỉnh lại như sau:

import './index.css';
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
const rootElement = document.getElementById('root');

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  rootElement);

reportWebVitals();

Ok vậy là xong phần thiếp lập FrontEnd trong React và Backend trong Asp.net Core Web API. Để sử dụng được bạn làm theo mình như sau;
Bước 1: Run project Asp.net Core Web API trong Visual Studio 2019 
Bước 2: Run thư mục ClientApp React:
# cd ClientApp
# npm start 

Demo

Restful API using ASP.NET Core Web API + React

Restful API using ASP.NET Core Web API + React

Restful API using ASP.NET Core Web API + React

 

 

 

 

 

Tag: React
x

Ủng hộ tôi bằng cách click vào quảng cáo. Để tôi có kinh phí tiếp tục phát triển Website!(Support me by clicking on the ad. Let me have the money to continue developing the Website!)