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.
Create Register & Login using ASP Core 2.1 + React (part 1)
Create Register & Login using ASP Core 2.1 + React (part 2)
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:
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
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é
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
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