لقد ناقشت كيفية تحميل الصور في Reactjs باستخدام Asp.Net Core WebAPI. نحن نقوم بإنشاء واجهة برمجة تطبيقات الويب Asp.Net Core وأنشأنا قاعدة بيانات خادم SQL مع جوهر إطار عمل الكيان. ثم قمت بإنشاء وحدة تحكم asp.net Core API لتحميل الصور.
لقد أنشأنا التطبيق من جانب العميل في Reactjs. وقد تم تصميم نموذج مع رافع الصور لهذا الغرض. يتم عرض معاينة الصورة المحددة بشكل منفصل. داخل حدث إرسال النموذج، قمت بتحميل الصورة المحددة في Asp.Net Web API.
الأدوات: VS Code، Visual Studio، SSMS، Postman
العميل: Reactjs
واجهة برمجة التطبيقات: Asp.Net Core WebAPI
وحدة التحكم هذه متصلة باتصال قاعدة بيانات وتوفر طرقًا لسرد سجلات الموظفين وإدراجها وتحديثها وحذفها من خلال معالجة طلبات HTTP GET وPOST وPUT وDELETE.
تعمل وحدة التحكم هذه من خلال التفاعل مع نموذج الموظف حيث يمكن للموظفين تحميل صورهم. تم تصميم هذا النموذج من جانب React وتم إنشاؤه باستخدام الأمر npx create-react-app في الدليل الجذر للمشروع.
يتم توفير الأساليب ذات الصلة لعمليات GET وPOST وPUT وDELETE. باستخدام طلب GET، يتم إدراج كافة سجلات الموظفين الموجودة. يقوم طلب PUT بتحديث سجل موظف محدد ويستخدم أيضًا لتغيير صورة الملف الشخصي للموظف. يضيف طلب POST سجل موظف جديدًا ويقوم بتحميل صورة الملف الشخصي للموظف. يؤدي طلب الحذف إلى حذف سجل موظف محدد.
بالإضافة إلى ذلك، يتم توفير طريقتين خاصتين، SaveImage() وDeleteImage() لتحميل الصور وحذفها في سجلات الموظفين. تتعامل هذه الطرق مع تحميل وحذف صور الملف الشخصي للموظفين.
using System ; using System . Collections . Generic ; using System . Linq ; using System . Threading . Tasks ; using Microsoft . AspNetCore . Http ; using Microsoft . AspNetCore . Mvc ; using Microsoft . EntityFrameworkCore ; using EmployeeRegisterAPI . Models ; using System . IO ; using Microsoft . AspNetCore . Hosting ; namespace EmployeeRegisterAPI . Controllers { [ Route ( "api/[controller]" ) ] [ ApiController ] public class EmployeeController : ControllerBase { private readonly EmployeeDbContext _context ; private readonly IWebHostEnvironment _hostEnvironment ; public EmployeeController ( EmployeeDbContext context , IWebHostEnvironment hostEnvironment ) { _context = context ; this . _hostEnvironment = hostEnvironment ; } // GET: api/EmployeeModels [ HttpGet ] public async Task < ActionResult < IEnumerable < EmployeeModel > > > GetEmployees ( ) { return await _context . Employees . Select ( x => new EmployeeModel ( ) { EmployeeID = x . EmployeeID , EmployeeName = x . EmployeeName , Occupation = x . Occupation , ImageName = x . ImageName , ImageSrc = String . Format ( "{0}://{1}{2}/Images/{3}" , Request . Scheme , Request . Host , Request . PathBase , x . ImageName ) } ) . ToListAsync ( ) ; } // GET: api/Employee/5 [ HttpGet ( "{id}" ) ] public async Task < ActionResult < EmployeeModel > > GetEmployeeModel ( int id ) { var employeeModel = await _context . Employees . FindAsync ( id ) ; if ( employeeModel == null ) { return NotFound ( ) ; } return employeeModel ; } // PUT: api/Employee/5 // To protect from overposting attacks, enable the specific properties you want to bind to, for // more details, see https://go.microsoft.com/fwlink/?linkid=2123754. [ HttpPut ( "{id}" ) ] public async Task < IActionResult > PutEmployeeModel ( int id , [ FromForm ] EmployeeModel employeeModel ) { if ( id != employeeModel . EmployeeID ) { return BadRequest ( ) ; } if ( employeeModel . ImageFile != null ) { DeleteImage ( employeeModel . ImageName ) ; employeeModel . ImageName = await SaveImage ( employeeModel . ImageFile ) ; } _context . Entry ( employeeModel ) . State = EntityState . Modified ; try { await _context . SaveChangesAsync ( ) ; } catch ( DbUpdateConcurrencyException ) { if ( ! EmployeeModelExists ( id ) ) { return NotFound ( ) ; } else { throw ; } } return NoContent ( ) ; } // POST: api/Employee // To protect from overposting attacks, enable the specific properties you want to bind to, for // more details, see https://go.microsoft.com/fwlink/?linkid=2123754. [ HttpPost ] public async Task < ActionResult < EmployeeModel > > PostEmployeeModel ( [ FromForm ] EmployeeModel employeeModel ) { employeeModel . ImageName = await SaveImage ( employeeModel . ImageFile ) ; _context . Employees . Add ( employeeModel ) ; await _context . SaveChangesAsync ( ) ; return StatusCode ( 201 ) ; } // DELETE: api/Employee/5 [ HttpDelete ( "{id}" ) ] public async Task < ActionResult < EmployeeModel > > DeleteEmployeeModel ( int id ) { var employeeModel = await _context . Employees . FindAsync ( id ) ; if ( employeeModel == null ) { return NotFound ( ) ; } DeleteImage ( employeeModel . ImageName ) ; _context . Employees . Remove ( employeeModel ) ; await _context . SaveChangesAsync ( ) ; return employeeModel ; } private bool EmployeeModelExists ( int id ) { return _context . Employees . Any ( e => e . EmployeeID == id ) ; } [ NonAction ] public async Task < string > SaveImage ( IFormFile imageFile ) { string imageName = new String ( Path . GetFileNameWithoutExtension ( imageFile . FileName ) . Take ( 10 ) . ToArray ( ) ) . Replace ( ' ' , '-' ) ; imageName = imageName + DateTime . Now . ToString ( "yymmssfff" ) + Path . GetExtension ( imageFile . FileName ) ; var imagePath = Path . Combine ( _hostEnvironment . ContentRootPath , "Images" , imageName ) ; using ( var fileStream = new FileStream ( imagePath , FileMode . Create ) ) { await imageFile . CopyToAsync ( fileStream ) ; } return imageName ; } [ NonAction ] public void DeleteImage ( string imageName ) { var imagePath = Path . Combine ( _hostEnvironment . ContentRootPath , "Images" , imageName ) ; if ( System . IO . File . Exists ( imagePath ) ) System . IO . File . Delete ( imagePath ) ; } } }
يسمح النموذج للمستخدم بإضافة موظف أو تعديله. يوفر المكون نموذجًا يمكنك من خلاله تحميل صورة وإدخال معلومات حول اسم الموظف ووظيفته. يتكون النموذج من منطقة تحميل الصور وحقلين لإدخال النص.
يتم استخدام العديد من المتغيرات. يحتفظ المتغير defaultImageSrc بمسار الصورة الافتراضي. يحتوي المتغير الأوليFieldValues على القيم الأولية التي سيتم استخدامها في الحالة الأولية للمكون. باستخدام useState، يتم تتبع حالات القيم داخل المكون.
يتم استخدام وظيفة useEffect أثناء تحميل المكون وعندما يتغير متغير RecordForEdit، فإنه يؤدي إلى إعادة تحميل المكون ويتم تحديث متغير القيم باستخدام وظيفة setValues.
يتم استدعاء الدالة HandleInputChange عند تغيير حقول إدخال النص. تلتقط هذه الوظيفة اسم وقيمة الحقل الذي تم تغييره باستخدام e.target.name وe.target.value وتقوم بتحديث القيم المتغيرة باستخدام وظيفة setValues.
يتم استدعاء وظيفة showPreview إذا تم تغيير منطقة تحميل الصورة. تقوم هذه الوظيفة بحفظ مسار الملف المحدد في متغير imageFile وإنشاء معاينة للملف باستخدام كائن FileReader وتحديث متغير imageSrc.
تتحقق وظيفة التحقق من صحة النموذج. تُستخدم هذه الوظيفة للتأكد من ملء متغير القيم بشكل صحيح. تقوم الدالة setErrors بتحديث متغير الأخطاء وتقوم الدالةsetForm بإعادة تعيين النموذج ومسح متغير الأخطاء.
يتم استدعاء الدالة HandleFormSubmit عند إرسال النموذج. تسترد هذه الوظيفة بيانات النموذج باستخدام كائن FormData وترسل البيانات إلى الخادم عن طريق استدعاء وظيفة addOrEdit.
تطبق الدالة ApplyErrorClass فئة الحقل غير الصالح على الحقول الخاطئة، مما يتسبب في عرض هذه الحقول مع وجود أخطاء.
يوفر المكون نموذجًا يمكنك من خلاله تحميل صورة وإدخال معلومات حول اسم الموظف ووظيفته. يتكون النموذج من منطقة تحميل الصور وحقلين لإدخال النص.
import React , { useState , useEffect } from 'react'
const defaultImageSrc = '/img/3135715.png'
const initialFieldValues = {
employeeID : 0 ,
employeeName : '' ,
occupation : '' ,
imageName : '' ,
imageSrc : defaultImageSrc ,
imageFile : null
}
export default function Employee ( props ) {
const { addOrEdit , recordForEdit } = props
const [ values , setValues ] = useState ( initialFieldValues )
const [ errors , setErrors ] = useState ( { } )
useEffect ( ( ) => {
if ( recordForEdit != null )
setValues ( recordForEdit ) ;
} , [ recordForEdit ] )
const handleInputChange = e => {
const { name , value } = e . target ;
setValues ( {
... values ,
[ name ] : value
} )
}
const showPreview = e => {
if ( e . target . files && e . target . files [ 0 ] ) {
let imageFile = e . target . files [ 0 ] ;
const reader = new FileReader ( ) ;
reader . onload = x => {
setValues ( {
... values ,
imageFile ,
imageSrc : x . target . result
} )
}
reader . readAsDataURL ( imageFile )
}
else {
setValues ( {
... values ,
imageFile : null ,
imageSrc : defaultImageSrc
} )
}
}
const validate = ( ) => {
let temp = { }
temp . employeeName = values . employeeName == "" ? false : true ;
temp . imageSrc = values . imageSrc == defaultImageSrc ? false : true ;
setErrors ( temp )
return Object . values ( temp ) . every ( x => x == true )
}
const resetForm = ( ) => {
setValues ( initialFieldValues )
document . getElementById ( 'image-uploader' ) . value = null ;
setErrors ( { } )
}
const handleFormSubmit = e => {
e . preventDefault ( )
if ( validate ( ) ) {
const formData = new FormData ( )
formData . append ( 'employeeID' , values . employeeID )
formData . append ( 'employeeName' , values . employeeName )
formData . append ( 'occupation' , values . occupation )
formData . append ( 'imageName' , values . imageName )
formData . append ( 'imageFile' , values . imageFile )
addOrEdit ( formData , resetForm )
}
}
const applyErrorClass = field => ( ( field in errors && errors [ field ] == false ) ? ' invalid-field' : '' )
return (
< >
< div className = "container text-center" >
< p className = "lead" > p >
div >
< form autoComplete = "off" noValidate onSubmit = { handleFormSubmit } >
< div className = "card" style = { { backgroundColor : '#ced114' } } >
< img src = { values . imageSrc } className = "card-img-top" />
< div className = "card-body" >
< div className = "form-group" >
< input type = "file" accept = "image/*" className = { "form-control-file" + applyErrorClass ( 'imageSrc' ) }
onChange = { showPreview } id = "image-uploader" />
div >
< div className = "form-group" style = { { backgroundColor : '#ced114' } } >
< input className = { "form-control" + applyErrorClass ( 'employeeName' ) } placeholder = "Employee Name" name = "employeeName"
value = { values . employeeName }
onChange = { handleInputChange } />
div >
< div className = "form-group" >
< input className = "form-control" placeholder = "Occupation" name = "occupation"
value = { values . occupation }
onChange = { handleInputChange } />
div >
< div className = "form-group text-center" >
< button type = "submit" className = "btn btn-light" style = { { color : '#ced114' } } > Submit button >
div >
div >
div >
form >
>
)
}
يعالج مكون React هذا إنشاء سجل الموظف وتحريره وحذفه وعرضه.
ويستخدم الخطافات useState وuseEffect React. يعرّف useState متغير حالة لحالة المكون ويعيد setState. يقوم useEffect بتشغيل دالة استجابة لأحداث مثل تحميل المكون أو إعادة رسمه.
يتم استخدام Axios لإجراء مكالمات خدمات الويب RESTful. تعمل وظيفة مساعدة تسمى "employeeAPI" على إنشاء مكالمات RESTful ويمكنها تغيير عنوان URL للمورد باستخدام ميزات Axios.
تقوم وظيفة RefreshEmployeeList باسترداد سجلات الموظفين من الخادم وتعيينها كحالة المكون مع setEmployeeList.
تتولى وظيفة addOrEdit إنشاء سجل الموظف أو تحديثه. يحتوي كائن FormData على إدخالات المستخدم لسجل الموظف.
تطلب وظيفة onDelete من المستخدم تأكيد ما إذا كان يريد حذف سجل ثم ترسل طلب حذف إلى الخادم.
يتم استخدام وظيفة imageCard لعرض بطاقات الموظفين. تتضمن البطاقة صورة الموظف واسمه ووصفه الوظيفي وزر الحذف.
وأخيرًا، يشتمل المكون على رأس جمبوترون، ونموذج موظف، وجدول بطاقات الموظفين. يعرض الجدول بطاقات الموظفين بثلاثة أعمدة وصفوف إضافية حيث لا يكون العمود الثالث مشغولاً.
{imageCard(employeeList[3 * i])} | {employeeList[3 * i + 1] ? imageCard(employeeList[3 * i + 1]) : null} | {employeeList[3 * i + 2] ? imageCard(employeeList[3 * i + 2]) : null} |
import React , { useState , useEffect } from 'react'
import Employee from './Employee'
import axios from "axios" ;
export default function EmployeeList ( ) {
const [ employeeList , setEmployeeList ] = useState ( [ ] )
const [ recordForEdit , setRecordForEdit ] = useState ( null )
useEffect ( ( ) => {
refreshEmployeeList ( ) ;
} , [ ] )
const employeeAPI = ( url = 'https://localhost:44334/api/Employee/' ) => {
return {
fetchAll : ( ) => axios . get ( url ) ,
create : newRecord => axios . post ( url , newRecord ) ,
update : ( id , updatedRecord ) => axios . put ( url + id , updatedRecord ) ,
delete : id => axios . delete ( url + id )
}
}
function refreshEmployeeList ( ) {
employeeAPI ( ) . fetchAll ( )
. then ( res => {
setEmployeeList ( res . data )
} )
. catch ( err => console . log ( err ) )
}
const addOrEdit = ( formData , onSuccess ) => {
if ( formData . get ( 'employeeID' ) == "0" )
employeeAPI ( ) . create ( formData )
. then ( res => {
onSuccess ( ) ;
refreshEmployeeList ( ) ;
} )
. catch ( err => console . log ( err ) )
else
employeeAPI ( ) . update ( formData . get ( 'employeeID' ) , formData )
. then ( res => {
onSuccess ( ) ;
refreshEmployeeList ( ) ;
} )
. catch ( err => console . log ( err ) )
}
const showRecordDetails = data => {
setRecordForEdit ( data )
}
const onDelete = ( e , id ) => {
e . stopPropagation ( ) ;
if ( window . confirm ( 'Are you sure to delete this record?' ) )
employeeAPI ( ) . delete ( id )
. then ( res => refreshEmployeeList ( ) )
. catch ( err => console . log ( err ) )
}
const imageCard = data => (
< div className = "card" onClick = { ( ) => { showRecordDetails ( data ) } } >
< img src = { data . imageSrc } className = "card-img-top rounded-circle" />
< div className = "card-body" >
< h5 > { data . employeeName } h5 >
< span > { data . occupation } span > < br />
< button className = "btn btn-light delete-button" onClick = { e => onDelete ( e , parseInt ( data . employeeID ) ) } >
< i className = "far fa-trash-alt" > i >
button >
div >
div >
)
return (
< div className = "row" >
< div className = "col-md-12" style = { { backgroundColor : '#ced114' } } >
< div className = "jumbotron jumbotron-fluid py-4" style = { { backgroundColor : '#ced114' } } >
< div className = "container text-center" style = { { backgroundColor : '#ced114' } } >
< h1 className = "display-4" style = { { backgroundColor : '#ced114' , color : 'yellow' } } > Employee Register h1 >
div >
div >
div >
< div className = "container text-center" >
< p className = "lead" > p >
div >
< div className = "col-md-4" >
< Employee
addOrEdit = { addOrEdit }
recordForEdit = { recordForEdit }
/>
div >
< div className = "col-md-8" >
< table >
< tbody >
{
//tr > 3 td
[ ... Array ( Math . ceil ( employeeList . length / 3 ) ) ] . map ( ( e , i ) =>
< tr key = { i } >
< td > { imageCard ( employeeList [ 3 * i ] ) } td >
< td > { employeeList [ 3 * i + 1 ] ? imageCard ( employeeList [ 3 * i + 1 ] ) : null } td >
< td > { employeeList [ 3 * i + 2 ] ? imageCard ( employeeList [ 3 * i + 2 ] ) : null } td >
tr >
)
}
tbody >
table >
div >
div >
)
}
أنا أستخدم .Net Core 6.0 في مشروعي. حزم Nuget التي يجب تنزيلها في المشروع مذكورة أدناه.
Microsoft.VisualStudio.Web.CodeGeneration.Design
PM > NuGet I nstall-Package Microsoft.VisualStudio.Web.CodeGeneration.Design -Version 6.0.13
Microsoft.EntityFrameworkCore
PM > NuGet I nstall-Package Microsoft.EntityFrameworkCore -Version 7.0.4
Microsoft.EntityFrameworkCore.SqlServer
PM > NuGet I nstall-Package Microsoft.EntityFrameworkCore.SqlServer -Version 7.0.4
Microsoft.EntityFrameworkCore.Tools
PM > NuGet I nstall-Package Microsoft.EntityFrameworkCore.Tools -Version 7.0.4
Microsoft.AspNetCore.Cors
PM > NuGet I nstall-Package Microsoft.AspNetCore.Cors -Version 2.2.0
أولاً، دعونا نضيف فصلًا دراسيًا إلى مجلد النماذج الخاص بنا. لقد أطلقت عليه اسم "نموذج الموظف".
public class EmployeeModel
{
[ Key ]
public int EmployeeID { get ; set ; }
[ Column ( TypeName = "nvarchar(50)" ) ]
public string EmployeeName { get ; set ; }
[ Column ( TypeName = "nvarchar(50)" ) ]
public string Occupation { get ; set ; }
[ Column ( TypeName = "nvarchar(100)" ) ]
public string ImageName { get ; set ; }
}
لنقم بإنشاء فئة سياق في مجلد النماذج الخاص بنا.
using Microsoft . EntityFrameworkCore ;
namespace EmployeeRegisterAPI . Models
{
public class EmployeeDbContext : DbContext //burada ef Dbcontext den kalitim aldirdik
{
public EmployeeDbContext ( DbContextOptions < EmployeeDbContext > options ) : base ( options )
{
}
public DbSet < EmployeeModel > Employees { get ; set ; }
}
}
دعنا ننتقل إلى المجلد Startup.cs ونقوم بإنشاء سلسلة اتصال لأسلوب ConfigureServices.
public void ConfigureServices ( IServiceCollection services )
{
services . AddControllers ( ) ;
//Baglanti dizesini olusturduk ve bu kodda sql server icin bir ConnectionString olusturduk "DevConnection" diye bunula baglanti bilgilerimizi appsettings de belirtecegiz
services . AddDbContext < EmployeeDbContext > ( options => options . UseSqlServer ( Configuration . GetConnectionString ( "DevConnection" ) ) ) ;
}
دعنا نأتي إلى appsettings.json هنا نكتب معلومات الاتصال بملف json مثل هذا
"Logging" : {
"LogLevel" : {
"Default" : "Information" ,
"Microsoft" : "Warning" ,
"Microsoft.Hosting.Lifetime" : "Information"
}
} ,
"AllowedHosts" : "*" ,
//buradan sonraki kod satirlaridir
"ConnectionStrings" : {
"DevConnection" : "Server=LAPTOP-6OAEM3JA; Database=EmployeeDB; Trusted_Connection=True; MultipleActiveResultSets=True;"
}
}
بعد ذلك، نقوم ببناء مشروعنا، ونذهب إلى مدير الحزم ونقوم بعمليات الترحيل.
PM > Add-Migration " InitialCreate "
لنقم بإنشاء الترحيل باستخدام الأمر ونبني مشروعنا ثم نعطي هذا الأمر
PM > update-database
ثم دعونا نتحقق مما إذا كان قد تم إنشاء قاعدة البيانات من MSSQL أم لا.
وبعد ذلك لنقم بإنشاء وحدة تحكم لنموذجنا، وسأستخدم وحدة تحكم API مع الإجراءات التي تستخدم وحدة تحكم EF.
الصور سوف تأتي هنا، 2 منهم
لقد أنشأنا عناصر التحكم الخاصة بنا، وحددنا نموذجنا وملف السياق، وحددنا اسم وحدة التحكم الخاصة بنا.
5 تسمى هذه الخدمات AddDb.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DevConnection"))); ستقوم الوظيفة بإنشاء طلب لوحدة تحكم الموظف هذه في كل مرة نقوم فيها بتقديم طلب إلى وحدة تحكم الموظف هذه. مُنشئ StaffController هو StaffController العام (EmployeeDbContext context) قيمة StaffDbContext في هذا الرمز هي طريقة حقن التبعية asp.net، والتي تتم إدارتها تلقائيًا بواسطة الإطار نفسه، وبالتالي فإن الطلب الموجود في وحدة التحكم هذه سيتواصل مع قاعدة البيانات في الوقت الحالي.
الآن، لنصمم جانب العميل بحيث يتفاعل مع نموذج الموظف لتحميل صور الملف الشخصي للموظف، فلنقم بإنشاء تطبيق تفاعل في دليل المشروع. نحتاج إلى فتح موجه الأوامر من دليل هذا المشروع.
npx create-react-app employee-register-client
أمر Oxios ليتم استخدامه من جانب العميل
$ npm install react-axios
لقد أنشأنا هيكل مشاريعنا، من الآن فصاعدًا، أريدك أن تراجع الأكواد الخاصة بي في الريبو الخاص بي. حظ سعيد :)
GET /api/Employee
المعلمة | الدواء | توضيح |
---|---|---|
api_key | string | ضروري . مفتاح API الخاص بك. |
GET /api/Employee/${id}
المعلمة | الدواء | توضيح |
---|---|---|
id | string | ضروري . القيمة الأساسية للعنصر المراد استدعاؤه |
{ "employeeID" : 3 , "employeeName" : " piedhorse " , "occupation" : " C# " , "imageName" : " capture_20231646962.jpeg " , "imageFile" : null , "imageSrc" : " https://localhost:44334/Images/capture_20231646962.jpeg " }