وبلاگها ، سایتها و مقالات ایرانی (داخل و خارج از ایران)
Visual Studio
ASP. Net
طراحی و توسعه وب
PHP
اسکیوال سرور
سی شارپ
عمومی دات نت
ویندوز
مسایل اجتماعی و انسانی برنامه نویسی
متفرقه
public class Person { public int ID { get; set; } public string Firstname { get; set; } public string Lastname { get; set; } public string Email { get; set; } public string PhoneNumber { get; set; } public override string ToString() { return $"{ID}: {Firstname} {Lastname} - {Email} - {PhoneNumber}"; } }
Install-Package GenFu
var person = A.New<Person>(); Console.WriteLine(person);
18: Diedra Morgan - Zachary.Garcia@telus.net - (531) 273-9001
var people = A.ListOf<Person>(5); people.ForEach(Console.WriteLine);
97: Maria MacKenzie - Alexandra.Johnson@rogers.ca - (670) 787-3053 34: Alexander Scott - Isaiah.Price@gmail.com - (730) 645-4946 66: Kevin Perez - Gabrielle.Alexander@hotmail.com - (230) 758-8233 81: Maria Evans - Vanessa.Bell@rogers.ca - (508) 572-4343 79: Tyler Parker - Alyssa.Taylor@telus.net - (297) 357-7617
A.Configure<Person>().Fill(x => x.ID, 0); var people = A.ListOf<Person>(5); people.ForEach(Console.WriteLine);
0: Darron Gonzalez - Benjamin.Daeninck@hotmail.com - (405) 418-7783 0: Melanie Garcia - Jennifer.Griffin@microsoft.com - (711) 277-8826 0: James Hughes - Tristan.Ward@live.com - (734) 400-8322 0: Miranda Torres - Ross.Davis@rogers.ca - (495) 479-8147 0: David Hughes - Jillian.Alexander@live.com - (361) 617-6642
var i = 1; A.Configure<Person>() .Fill(c => c.ID, () => i++); var people = A.ListOf<Person>(5); people.ForEach(Console.WriteLine);
1: Paul Long - Carlos.Kelly@telus.net - (202) 573-6278 2: Jesse Iginla - Liberty.Moore@gmail.com - (589) 791-3606 3: Raymundo Price - Ang.Taylor@live.com - (336) 400-1601 4: Elizabeth Getzlaff - Leslie.Campbell@att.com - (662) 582-9010 5: Abigail Bailey - Tristan.Ross@live.com - (225) 661-7023
A.Configure<Person>() .Fill(c => c.ID, 0) .Fill(c => c.Email, c => $"{c.Firstname}.{c.Lastname}@gmail.com"); var people = A.ListOf<Person>(5); people.ForEach(Console.WriteLine);
0: Patrick Perry - Patrick.Perry@gmail.com - (796) 460-6576 0: Rebecca Main - Rebecca.Main@gmail.com - (757) 472-3332 0: Kimberly Carter - Kimberly.Carter@gmail.com - (436) 484-8273 0: Sara Lewis - Sara.Lewis@gmail.com - (424) 717-7682 0: Lauren Ross - Lauren.Ross@gmail.com - (277) 294-5776
6. استفاده از Extensionهای درون ساخت GenFu برای مقداردهی
A.Configure<Person>() .Fill(x => x.Firstname).AsPersonTitle(); var people = A.ListOf<Person>(5); people.ForEach(Console.WriteLine);
64: Miss. Ratzlaff - Bryce.Simmons@att.com - (386) 309-2414 7: Air Marshall Yarobi - Ariana.Russell@att.com - (459) 238-0717 96: Air Marshall Taylor - Luke.Olsen@gmail.com - (775) 401-5281 28: Doctor Cox - Leah.Diaz@att.com - (569) 464-7961 99: Master Phillips - Chloe.Scott@hotmail.com - (578) 221-9021
7. GenFu WireFrame
ضمنا اون موارد خطا هم به خاطر همین exception داخلی Type Load Exception در فریمورک اصلی بود که پس از ارتقا به نسخه 3.1 برطرف شد.
دوستان از لینک زیر میتوانید نسخه بروز رسانی شده با ASP.NET Core 3.1 را استفاده نمایید.
Demo.JqGrid.core3.1.rar (6.25 MB)
سورس پروژه رو با این تغییرات در گیتهاب هم منتشر کردم
SASS #1
SASS چیست؟
SASS مخفف Syntactically Awesome Style Sheets است که توسط آقای Hampton Catlin طراحی و ایجاد شده است و همانند CoffeeScript که پس از کامپایل به جاوااسکریپت تبدیل میشد، SASS نیز پس از کامپایل به CSS تبدیل میشود. SASS با استفاده از متغیرها، mixins، ارث بری و قوانین تودرتو، CSS را با مهارت زیادی در بهترین حالت تولید میکند.
SASS باعث کمتر نوشتن کد CSS، سبب افزایش خوانایی و دستکاری کردن راحتتر و پویای آن میشود. این مساله راهی عالی برای نوشتن کدهای CSS کاربردیتر است و میتواند سرعت گردش کار هر توسعه دهنده و یا طراح وب را افزایش دهد.
وقتی اولین بار SASS عرضه شد، syntax آن تفاوت قابل توجهی با CSS داشت (پسوند فایلهای آن SASS. است) که به جای نوشتن براکتها، از تورفتگی استفاده میشد و دیگر نیازی به نوشتن ";" نبود. البته با عدم استقبال از این syntax مواجه شد و با عرضهی نسخه 3 SASS، (که پسوند فایلهای آن SCSS. است) syntax آن بسیار شبیه به CSS شد؛ البته با همهی ویژگیهای SASS.
برای مثال کد CSS زیر را میخواهیم به دو روش بنویسیم:
header { margin: 0; padding: 0; color: #fff; }
$color: #fff; header { margin: 0; padding:0; color: $color; }
$color: #fff header margin: 0 padding: 0 color: $color
توجه: syntax ایی که در این سری آموزشی با آن کار میکنیم SCSS. است.
کامپایل کردن SASS
روشهای مختلفی برای کامپایل فایلهای SASS وجود دارند:- روش اصلی استفاده از SASS در Ruby است که پس از نصب Ruby و اجرای فرمان SASS ،gem install sass نصب میشود و برای کامپایل، اجرای فرمان زیر:
sass myfile.scss myfile.css
- استفاده از برنامههای گرافیکی مانند Hammer , CodeKit و Compass.
- استفاده از برنامههای رایگان مانند libsass که با یک کامپایلر سریع نوشته شده با ++C/C است و همچنین میتوانید libsass را از طریق NPM با node-sass نصب کنید.
npm install node-sass
- استفاده از افزونه Web Essentials در Visual Studio
خب سوالی که ممکن است برای شما پیش آمده باشد این است که باید از کدام یک از این روشها را استفاده کنیم؟
بستگی به این دارد که شما چه کاری را میخواهید انجام دهید.
- در صورتیکه بر روی یک پروژهی بزرگ با میزان کد زیاد کار میکنید، استفاده از Ruby SASS، کمی کند کار کامپایل را انجام میدهد.
- اگر بخواهید از libsass استفاده کنید، این مسئله وجود دارد که به طور %100 با قابلیتهای Ruby SASS برابری ندارد.
- در صورتیکه نمیخواهید از command line استفاده کنید، برنامههای گرافیکی گزینهای عالی هستند. شما میتوانید طوری تنظیم کنید که تمامی تغییراتی که در فایل SASS انجام میشود، به صورت خودکار کار کامپایل انجام شود.
- اگر هم فقط میخواهید کدی را که نوشتهاید تست کنید، میتوانید از ابزارهای آنلاین مانند SassMeister استفاده کنید.
در طراحی ارائه شده، یک رشتهی اتصالی بیشتر در کل برنامه وجود ندارد. اطلاعات بیشتر
- خیر. این طراحی از سیستم مسیریابی و همچنین ViewEngine سفارشی خاصی استفاده میکند که مختص ASP.NET MVC است.
- ایجاد صورت حساب پرداخت با استفاده از InvoiceBuilder
- درگاه مجازی
- استفاده از پروکسی
- توکن پرداخت
- تزریق وابستگی
- Logging
var result = _onlinePayment.Request(Gateways.Mellat, 123, 25000, "http://www.mywebsite.com/foo/bar/");
var result = _onlinePayment.Request(invoice => { invoice .SetTrackingNumber(123) .SetAmount(25000) .SetCallbackUrl("http://www.mywebsite.com/foo/bar/") .UseGateway(Gateways.Mellat); });
- تولید اتوماتیک کد رهگیری به صورت افزایشی
- تولید اتوماتیک کد رهگیری به صورت تصادفی
- ایجاد یک تولید کننده کد رهگیری توسط شما
- صورت حساب سفارشی برای امکانات اختصاصی درگاههای بانکی
var result = _onlinePayment.Request(invoice => { invoice .UseAutoIncrementTrackingNumber() .SetAmount(25000) .SetCallbackUrl("http://www.mywebsite.com/foo/bar/") .UseGateway(Gateways.Mellat); });
var result = _onlinePayment.Request(invoice => { invoice .UseAutoRandomTrackingNumber() .SetAmount(25000) .SetCallbackUrl("http://www.mywebsite.com/foo/bar/") .UseGateway(Gateways.Mellat); });
public class MyTrackingNumberProvider : ITrackingNumberProvider { public Task<long> ProvideAsync(CancellationToken cancellationToken = new CancellationToken()) { // تولید و برگشت کد در اینجا } }
var result = _onlinePayment.Request(invoice => { invoice .UseTrackingNumberProvider<MyTrackingNumberProvider>() // یا .UseTrackingNumberProvider(new MyTrackingNumberProvider()) // یا .UseTrackingNumberProvider(services => new MyTrackingNumberProvider()) });
services.AddParbad() .ConfigureGateways(gateways => { gateways .AddParbadVirtual() .WithOptions(options => options.GatewayPath = "/MyVirtualGateway"); });
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); // ثبت درگاه مجازی app.UseParbadVirtualGateway(); }
public void Configuration(IAppBuilder app) { var parbad = ParbadBuilder.CreateDefaultBuilder() .ConfigureGateways(gateways => { gateways .AddParbadVirtual() .WithOptions(options => options.GatewayPath = "/MyVirtualGateway"); }) .Build(); app.UseParbadVirtualGateway(parbad.Services); }
var result = _onlinePayment.Request(Gateways.ParbadVirtualGateway, 123, 25000, "http://www.mywebsite.com/foo/bar/");
services.AddParbad() .ConfigureGateways(gateways => { gateways .AddMellat() .WithOptions(options => { options.TerminalId = 123; options.UserName = "abc"; options.UserPassword = "xyz; ) .WithProxy(new Uri("Proxy Server URL"), "UserName", "Password"); });
public class MyTokenProvider : IPaymentTokenProvider { public Task<string> ProvideTokenAsync(Invoice invoice, CancellationToken cancellationToken = new CancellationToken()) { // تولید و برگرداندن توکن در اینجا } public Task<string> RetrieveTokenAsync(CancellationToken cancellationToken = new CancellationToken()) { // خواندن و برگرداندن توکن در اینجا } }
services.AddParbad() .ConfigurePaymentToken(builder => builder.AddPaymentTokenProvider<MyTokenProvider>(ServiceLifetime.Transient));
پیشنیازها
«بررسی روش آپلود فایلها در ASP.NET Core»
«ارسال فایل و تصویر به همراه دادههای دیگر از طریق jQuery Ajax »
- در مطلب اول، روش دریافت فایلها از کلاینت، در سمت سرور و ذخیره سازی آنها در یک برنامهی ASP.NET Core بررسی شدهاست که کلیات آن در اینجا نیز صادق است.
- در مطلب دوم، روش کار با FormData استاندارد بررسی شدهاست. هرچند در مطلب جاری از jQuery استفاده نمیشود، اما نکات نحوهی کار با شیء FormData استاندارد، در اینجا نیز یکی است.
برپایی پروژههای مورد نیاز
ابتدا یک پوشهی جدید مانند UploadFilesSample را ایجاد کرده و در داخل آن دستور زیر را اجرا میکنیم:
dotnet new react
سپس در این پوشه، پوشهی ClientApp پیشفرض آنرا حذف میکنیم؛ چون کمی قدیمی است. همچنین فایلهای کنترلر و سرویس آب و هوای پیشفرض آنرا به همراه پوشهی صفحات Razor آن، حذف و پوشهی خالی wwwroot را نیز به آن اضافه میکنیم.
همچنین بجای تنظیم پیش فرض زیر در فایل کلاس آغازین برنامه:
spa.UseReactDevelopmentServer(npmScript: "start");
spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
اکنون در ریشهی پروژهی ASP.NET Core ایجاد شده، دستور زیر را صادر میکنیم تا پروژهی کلاینت React را با فرمت جدید آن ایجاد کند:
> create-react-app clientapp
> cd clientapp > npm install --save bootstrap axios react-toastify
- برای استفاده از شیوهنامههای بوت استرپ، بستهی bootstrap نیز در اینجا نصب میشود که برای افزودن فایل bootstrap.css آن به پروژهی React خود، ابتدای فایل clientapp\src\index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
- برای نمایش پیامهای برنامه از کامپوننت react-toastify استفاده میکنیم که پس از نصب آن، با مراجعه به فایل app.js نیاز است importهای لازم آنرا اضافه کنیم:
import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css";
render() { return ( <React.Fragment> <ToastContainer />
ایجاد کامپوننت React فرم ارسال فایلها به سمت سرور
پس از این مقدمات، فایل جدید clientapp\src\components\UploadFileSimple.jsx را ایجاد کرده و به صورت زیر تکمیل میکنیم:
import React, { useState } from "react"; import axios from "axios"; import { toast } from "react-toastify"; export default function UploadFileSimple() { const [description, setDescription] = useState(""); const [selectedFile1, setSelectedFile1] = useState(); const [selectedFile2, setSelectedFile2] = useState(); return ( <form> <fieldset className="form-group"> <legend>Support Form</legend> <div className="form-group row"> <label className="form-control-label" htmlFor="description"> Description </label> <input type="text" className="form-control" name="description" onChange={event => setDescription(event.target.value)} value={description} /> </div> <div className="form-group row"> <label className="form-control-label" htmlFor="file1"> File 1 </label> <input type="file" className="form-control" name="file1" onChange={event => setSelectedFile1(event.target.files[0])} /> </div> <div className="form-group row"> <label className="form-control-label" htmlFor="file2"> File 2 </label> <input type="file" className="form-control" name="file2" onChange={event => setSelectedFile2(event.target.files[0])} /> </div> <div className="form-group row"> <button className="btn btn-primary" type="submit" > Submit </button> </div> </fieldset> </form> ); }
- توسط آن یک textbox به همراه دو فیلد ارسال فایل، به فرم اضافه شدهاند.
- مرحلهی بعد، دسترسی به فایلهای انتخابی کاربر و همچنین مقدار توضیحات وارد شدهاست. به همین جهت با استفاده از useState Hook، روش دریافت و تنظیم این مقادیر را مشخص کردهایم:
const [description, setDescription] = useState(""); const [selectedFile1, setSelectedFile1] = useState(); const [selectedFile2, setSelectedFile2] = useState();
- پس از طراحی state این فرم، مرحلهی بعدی، استفاده از متدهای set تمام useStateهای فوق است. برای مثال در مورد یک textbox معمولی، میتوان آنرا به صورت inline تعریف کرد و با هر بار تغییری در محتوای آن، این رخداد را به متد setDescription ارسال نمود تا مقدار وارد شده را به متغیر حالت description انتساب دهد:
<input type="text" className="form-control" name="description" onChange={event => setDescription(event.target.value)} value={description} />
<input type="file" className="form-control" name="file1" onChange={event => setSelectedFile1(event.target.files[0])} />
تشکیل مدل ارسال دادهها به سمت سرور
در فرمهای معمولی، عموما دادهها به صورت یک شیء JSON به سمت سرور ارسال میشوند؛ اما در اینجا وضع متفاوت است و به همراه توضیحات وارد شده، دو فایل باینری نیز وجود دارند.
در حالت ارسال متداول فرمهایی که به همراه المانهای دریافت فایل هستند، ابتدا یک ویژگی enctype با مقدار multipart/form-data به المان فرم اضافه میشود و سپس این فرم به سادگی قابلیت post-back به سمت سرور را پیدا میکند:
<form enctype="multipart/form-data" action="/upload" method="post"> <input id="file-input" type="file" /> </form>
let file = document.getElementById("file-input").files[0]; let formData = new FormData(); formData.append("file", file); fetch('/upload/image', {method: "POST", body: formData});
در یک برنامهی React نیز باید دقیقا چنین مراحلی طی شوند. تا اینجا کار دسترسی به مقدار files[0] و تشکیل متغیرهای حالت فرم را انجام دادهایم. در مرحلهی بعد، شیء FormData را تشکیل خواهیم داد:
// ... export default function UploadFileSimple() { // ... const handleSubmit = async event => { event.preventDefault(); const formData = new FormData(); formData.append("description", description); formData.append("file1", selectedFile1); formData.append("file2", selectedFile2); toast.success("Form has been submitted successfully!"); setDescription(""); }; return ( <form onSubmit={handleSubmit}> </form> ); }
ارسال مدل دادههای فرم React به سمت سرور
پس از تشکیل شیء FormData در متد مدیریت کنندهی handleSubmit، اکنون با استفاده از کتابخانهی axios، کار ارسال این اطلاعات را به سمت سرور انجام خواهیم داد:
// ... export default function UploadFileSimple() { const apiUrl = "https://localhost:5001/api/SimpleUpload/SaveTicket"; // ... const [isUploading, setIsUploading] = useState(false); const handleSubmit = async event => { event.preventDefault(); const formData = new FormData(); formData.append("description", description); formData.append("file1", selectedFile1); formData.append("file2", selectedFile2); try { setIsUploading(true); const { data } = await axios.post(apiUrl, formData, { headers: { "Content-Type": "multipart/form-data" }} }); toast.success("Form has been submitted successfully!"); console.log("uploadResult", data); setIsUploading(false); setDescription(""); } catch (error) { setIsUploading(false); toast.error(error); } }; return ( // ... ); }
در قطعه کد فوق، متغیر جدید حالت isLoading را نیز مشاهده میکنید. از آن میتوان برای فعال و غیرفعال کردن دکمهی submit فرم در زمان ارسال اطلاعات به سمت سرور، استفاده کرد:
<button disabled={ isUploading } className="btn btn-primary" type="submit" > Submit </button>
اعتبارسنجی سمت کلاینت فایلهای ارسالی به سمت سرور
در اینجا شاید نیاز باشد نوع و یا اندازهی فایلهای انتخابی توسط کاربر را تعیین اعتبار کرد. به همین جهت متدی را برای اینکار به صورت زیر تهیه میکنیم:
const isFileValid = selectedFile => { if (!selectedFile) { // toast.error("Please select a file."); return false; } const allowedMimeTypes = [ "image/png", "image/jpeg", "image/gif", "image/svg+xml" ]; if (!allowedMimeTypes.includes(selectedFile.type)) { toast.error(`Invalid file type: ${selectedFile.type}`); return false; } const maxFileSize = 1024 * 500; const fileSize = selectedFile.size; if (fileSize > maxFileSize) { toast.error( `File size ${(fileSize / 1024).toFixed( 2 )} KB must be less than ${maxFileSize / 1024} KB` ); return false; } return true; };
اکنون برای استفادهی از این متد دو راه وجود دارد:
الف) استفاده از آن در متد مدیریت کنندهی submit اطلاعات:
const handleSubmit = async event => { event.preventDefault(); if (!isFileValid(selectedFile1) || !isFileValid(selectedFile2)) { return; }
ب) استفادهی از آن جهت غیرفعال کردن دکمهی submit:
<button disabled={ isUploading || !isFileValid(selectedFile1) || !isFileValid(selectedFile2) } className="btn btn-primary" type="submit" > Submit </button>
نمایش درصد پیشرفت آپلود فایلها
کتابخانهی axios، امکان دسترسی به میزان اطلاعات آپلود شدهی به سمت سرور را به صورت یک رخداد فراهم کردهاست که در ادامه از آن برای نمایش درصد پیشرفت آپلود فایلها استفاده میکنیم:
const startTime = Date.now(); const { data } = await axios.post(apiUrl, formData, { headers: { "Content-Type": "multipart/form-data" }, onUploadProgress: progressEvent => { const { loaded, total } = progressEvent; const timeElapsed = Date.now() - startTime; const uploadSpeed = loaded / (timeElapsed / 1000); setUploadProgress({ queueProgress: Math.round((loaded / total) * 100), uploadTimeRemaining: Math.ceil((total - loaded) / uploadSpeed), uploadTimeElapsed: Math.ceil(timeElapsed / 1000), uploadSpeed: (uploadSpeed / 1024).toFixed(2) }); } });
const [uploadProgress, setUploadProgress] = useState({ queueProgress: 0, uploadTimeRemaining: 0, uploadTimeElapsed: 0, uploadSpeed: 0 });
const showUploadProgress = () => { const { queueProgress, uploadTimeRemaining, uploadTimeElapsed, uploadSpeed } = uploadProgress; if (queueProgress <= 0) { return <></>; } return ( <table className="table"> <thead> <tr> <th width="15%">Event</th> <th>Status</th> </tr> </thead> <tbody> <tr> <td> <strong>Elapsed time</strong> </td> <td>{uploadTimeElapsed} second(s)</td> </tr> <tr> <td> <strong>Remaining time</strong> </td> <td>{uploadTimeRemaining} second(s)</td> </tr> <tr> <td> <strong>Upload speed</strong> </td> <td>{uploadSpeed} KB/s</td> </tr> <tr> <td> <strong>Queue progress</strong> </td> <td> <div className="progress-bar progress-bar-info progress-bar-striped" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow={queueProgress} style={{ width: queueProgress + "%" }} > {queueProgress}% </div> </td> </tr> </tbody> </table> ); };
{showUploadProgress()}
در اینجا از کامپوننت progress-bar خود بوت استرپ برای نمایش درصد آپلود فایلها استفاده شدهاست. اگر style آنرا هر بار با مقدار جدید queueProgress به روز رسانی کنیم، سبب نمایش پویای این progress-bar خواهد شد.
یک نکته: اگر میخواهید درصد پیشرفت آپلود را در حالت آزمایش local بهتر مشاهده کنید، دربرگهی network، سرعت را بر روی 3G تنظیم کنید (مانند تصویر ابتدای بحث)؛ در غیراینصورت همان ابتدای کار به علت بالا بودن سرعت ارسال فایلها، 100 درصد را مشاهده خواهید کرد.
دریافت فرم React درخواست پشتیبانی، در سمت سرور و ذخیرهی فایلهای آن
بر اساس نحوهی تشکیل FormData سمت کلاینت:
const formData = new FormData(); formData.append("description", description); formData.append("file1", selectedFile1); formData.append("file2", selectedFile2);
using Microsoft.AspNetCore.Http; namespace UploadFilesSample.Models { public class Ticket { public int Id { set; get; } public string Description { set; get; } public IFormFile File1 { set; get; } public IFormFile File2 { set; get; } } }
پس از آن کنترلر ذخیره سازی اطلاعات Ticket را مشاهده میکنید:
using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using UploadFilesSample.Models; namespace UploadFilesSample.Controllers { [Route("api/[controller]")] [ApiController] public class SimpleUploadController : Controller { private readonly IWebHostEnvironment _environment; public SimpleUploadController(IWebHostEnvironment environment) { _environment = environment; } [HttpPost("[action]")] public async Task<IActionResult> SaveTicket([FromForm]Ticket ticket) { var file1Path = await saveFileAsync(ticket.File1); var file2Path = await saveFileAsync(ticket.File2); //TODO: save the ticket ... get id return Created("", new { id = 1001 }); } private async Task<string> saveFileAsync(IFormFile file) { const string uploadsFolder = "uploads"; var uploadsRootFolder = Path.Combine(_environment.WebRootPath, "uploads"); if (!Directory.Exists(uploadsRootFolder)) { Directory.CreateDirectory(uploadsRootFolder); } //TODO: Do security checks ...! if (file == null || file.Length == 0) { return string.Empty; } var filePath = Path.Combine(uploadsRootFolder, file.FileName); using (var fileStream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(fileStream); } return $"/{uploadsFolder}/{file.Name}"; } } }
- تزریق IWebHostEnvironment در سازندهی کلاس کنترلر، سبب میشود تا از طریق خاصیت WebRootPath آن، به wwwroot دسترسی پیدا کنیم و فایلهای نهایی را در آنجا ذخیره سازی کنیم.
- همانطور که ملاحظه میکنید، هنوز هم model binding کار کرده و میتوان شیء Ticket را به نحو متداولی دریافت کرد:
public async Task<IActionResult> SaveTicket([FromForm]Ticket ticket)
const { data } = await axios.post(apiUrl, formData, { headers: { "Content-Type": "multipart/form-data" }} });
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: UploadFilesSample.zip
برای اجرای آن، پس از صدور فرمان dotnet restore که سبب بازیابی وابستگیهای سمت کلاینت نیز میشود، ابتدا به پوشهی clientapp مراجعه کرده و فایل run.cmd را اجرا کنید. با اینکار react development server بر روی پورت 3000 شروع به کار میکند. سپس به پوشهی اصلی برنامهی ASP.NET Core بازگشت شده و فایل dotnet_run.bat را اجرا کنید. این اجرا سبب راه اندازی وب سرور برنامه و همچنین ارائهی برنامهی React بر روی پورت 5001 میشود.