Sqlite دیتابیس مناسبی برای برنامههای چندسکویی است و عموما به عنوان اولین گزینه استفاده میشود. برای کار با این دیتابیس، ما از ماژول sql.js که یکی از ماژولهای معروف در جاوااسکریپت است، استفاده میکنیم. برای نصب آن از طریق npm، به شکل زیر اقدام میکنیم:
npm install sql.js --save
const{app,BrowserWindow}=require("electron"); let win; function onLoad() { win=new BrowserWindow({ width:800, height:600 }); win.loadURL(`file://${__dirname}/index.html`); } app.on("ready",onLoad());
var path=require("path"); path.exists('filepath",(status)=> { .... }); var status=path.existsSync("file"); //=============================== var fs=require("fs"); fs.exists('filepath",(status)=> { .... }); var status=path.existsSync("file");
fs.stat('foo.txt', function(err, stat) { if(err == null) { console.log('فایل موجوده'); } else if(err.code == 'ENOENT') { // فایل وجود نداره fs.writeFile('log.txt', 'Some log\n'); } else { //خطای دیگری رخ داده است } });
try { stats = fs.statSync(path); console.log("File exists."); } catch (e) { console.log("File does not exist."); }
const fs = require('fs'); const sql = require('sql.js'); dbPath = './mydb.sqlite'; dbExists=false; try { dbExists = fs.statSync(dbPath); } catch (e) { } if(!dbExists) { //create Database var sqlStr=fs.readFileSync("./sql.txt"); var db = new sql.Database(); db.run(String(sqlStr)); //write to disk var data=db.export(); var buffer=new Buffer(data); fs.writeFileSync(dbPath,buffer); } else{ var buffer = fs.readFileSync(dbPath); var db = new sql.Database(buffer); }
CREATE TABLE numbers ( id INT PRIMARY KEY UNIQUE NOT NULL, fname VARCHAR (20) NOT NULL, lname VARCHAR (30) NOT NULL, number VARCHAR (15) NOT NULL ); insert into numbers values(1,'ali','yeganeh','03111223344'); insert into numbers values(2,'xxx','yyy','45454555');
بعد از آن نیاز است تا دیتابیس در دسترس Render Processها قرار بگیرد که در مقاله "شیوه کدنویسی در الکترون " در مورد global صحبت کردهایم و نحوه استفاده از آن را فرا گرفتیم:
global.db=db;
در پایان اجرای برنامه لازم است که دیتابیس توسط دستور close بسته شود. سپس کد زیر را در رویداد windows-all-closed مینویسیم:
app.on('window-all-closed', () => { db.close(); if (process.platform !== 'darwin') { app.quit(); } });
(چند مورد خارج از بحث): کد بعدی که مورد استفاده قرار گرفته است و در مقالات قبلی در مورد آن صحبت نکردهایم این است که در سیستمهای مک، وضعیت به این قرار است که اگر شما برنامه را ببیندید، آن برنامه بسته نشده و در پس زمینه فعال است و میتوانید آن را از طریق dock اطراف صفحه، مجددا فعال کنید. ولی با نوشتن کد بالا، ما این وضعیت را اعلام کردهایم که اگر تمامی پنجرهها بسته شدند، کل برنامه را ببند.
همچنین بسیار خوب است که کد زیر را هم همیشه اضافه کنید:
win.on('closed', () => { win = null; });
پس اگر این کد را نوشتید، وضعیت سیستم عامل مک را به خاطر داشته باشید و مجبور هستید کد زیر را نیز اضافه کنید:
app.on('activate', () => { if (win === null) { createWindow(); } });
بعد از اینکه دیتابیس را به شیء global دادیم، در صفحه html کد زیر را وارد میکنیم:
<html> <head> <script src="./jquery.min.js"></script> <link href="./bootstrap-3.3.6-dist/css/bootstrap.min.css" rel="stylesheet"></link> <meta charset="utf-8"> <title></title> <script> const {remote}=require("electron"); let db=remote.getGlobal("db"); </script> </head> <body> <table id="people" class="table table-hover table-striped"> <th> <tr> <td>First Name</td> <td>last Name</td> <td>Phone Number</td> </tr> </th> <tbody> </tbody> </table> </body> </html>
$(document).ready(()=> { //show data var tableBody=$("#people"); db.each("select * from numbers",(row)=>{ let rowTemplate=`<tr><td>${row.fname}</td><td>${row.lname}</td><td>${row.number}</td></tr>`; tableBody.append(rowTemplate); });
حال وقت آن رسیده است که خروجی کار را ببینیم. پس کد npm start را اجرا میکنیم. همانطور که میبینید خروجی به راحتی نمایش داده میشود. در مقاله بعدی بیشتر در این مورد صحبت میکنیم.
برای شروع کار با Backload ابتدا به قسمت Nutget میرویم که در مسیر زیر است :
Backload. A Proffesional Full Featured Asp.Net FileUpload Controller *
پس از نصب دو مورد بالا ، موارد زیر را که در دو عکس پایین میبینید، به پروژهی شما اضافه خواهند شد:
*نکاتی که باید بدانید:
عکس هایی که شما آپلود میکنید در پوشهی Files موجود در ریشهی پروژهی شما قرار میگیرند این پوشه بوسیلهی خود ابزار Backload ساخته میشود.
چنانچه بخواهید در پوشهی Files پوشهای ایجاد کنید، ابتدا View آنرا باز کنید. این View در پروژه، در مسیر Views/ BackloadDemo/Index قرار دارد .
در داخل تگ form یک Hidden Field با نام objectContext اضافه میکنید. Value ایی که شما به این Hidden Field نسبت میدهید، نام پوشهی شما در پوشهی Files خواهد بود. مانند تصویر زیر: در اینجا پوشهای با نام 2014-02 در پوشهی Files وقتی که فایلی را باFile Upload آپلود میکنیم، ایجاد میشود.
چنانچه بخواهید در پوشهای که خودتان ایجاد کردید (که در این مثال 2014-02 میباشد) پوشههای متعدد دیگری هم داشته باشید Hidden Field ای با نام uploadContext ایجاد میکنید؛ مانند تصویر زیر :
اکنون اگر فایل جدیدی را آپلود کنید در مسیر
ذخیره میشود . یعنی بین نام هر پوشه از سمی کولن ; در Value استفاده میکنید.
تا اینجا ما میتوانیم بوسیلهی ابزار Backload عکسها را آپلود ، حذف و مسیر آپلود عکسها را تغییر دهیم.اگر توجه کرده باشید دفعات بعد که پروژه را اجرا میکنید عکسهای موجود در پوشه، در گرید ویو File Upload به شما نمایش داده خواهد شد. حال اگر بخوایم عکسهای موجود در پوشهی دیگری را نمایش دهیم باید چه کار کنیم؟!
گاهی اوقات نیاز داریم که محتویات پوشهای خاص را در گرید ویو File Upload مان نمایش دهیم. برای این کار شما باید کنترلر FileUploadController که در فایل ضمیمه در آموزش هست را در پوشهی Controller پروژهی خود کپی کنید .اگر شما این کنترلر را باز کنید مواردی مانند کلمه کلیدی Task و async را مشاهده خواهید کرد. این موارد در .Net Framework 4 شناسایی نمیشود. برای همین در ابتدای آموزش تاکید کردم که .Net Framework 4.5 و یا بالاتر را برای پروژهی خود انتخاب کنید .
در تابع Handler_GetFilesRequestStarted در این کنترلر باید مشخص کنید که فایلهای موجود در کدام مسیر را برای شما نمایش دهد؛ آن هم با استفاده از دستور زیر :
اکنون قبل از آنکه پروژه را اجرا کنید فایل Backload.Demo.js که در مسیر Scripts/Fileupload هست را باز کنید و url موجود در آنرا مانند عکس زیر تغیییر دهید :
حالا پروژه را اجرا کنید. خواهید دید تمامی فایلهای موجود در مسیری را که شما مشخص کردهاید، برایتان نمایش خواهد داد.
چنانچه بخواهید تعداد مثلا 5 فایل برای شما در گرید ویو مربوط به FileUpload نمایش داده شود، به تابع handler_GetFilesRequestFinished میروید. متغیر limit مشخص میکند که 5 فایل نمایش داده شود. میتوانید این عدد را به دلخواه خود تغییر دهید.
نکتهی بسیار مهم دیگری که باید به آن توجه شود مربوط به Hidden Field نام پوشهها میباشد. بار دیگر پروژه را اجرا کنید. با استفاده از ابزاری مثل FireBug کدهای html صفحهی خود را ببینید. Hidden Field ایی با نام objectContext را پیدا کنید و Value آنرا به test تغییر دهید. فایلی را آپلود کنید حالا به پوشهی Files موجود در ریشهی پروژه بروید. میبینید که پوشهای با نام testایجاد شده و فایلی هم که آپلود کردید در آن قرار دارد که این یک اشکال است. برای اینکه جلوی این گونه کارها را بگیریم به تابع handler_StoreFileRequestStartedAsync میرویم و کد زیر را مینویسیم :
دستور e.Context.PipelineControl.ExecutePipeline = false; باعث میشود که اجرای تابع متوقف شود.
فایل ضمیمه :FileUploadController-462d551688cf48c68cb55343ac5464f3.zip
برای مشاهده مثالهای دیگری دربارهی Backload به این لینک بروید.
موفق باشید.
این نوشتار اولین آموزش من در این سایت میباشد و جا دارد از دوست خوبم "محبوبه قربانی" که باعث شد من با MVC آشنا شوم تشکر کنم.
همانطور که مشاهده میکنید، کنار ایمیل ارسالی توسط ما، یک آیکن قرمز رنگ مبتنی بر عدم رمزنگاری ارتباطات بین سرور ارسال کنندهی آن و جیمیل، قابل مشاهدهاست. در ادامه قصد داریم این وضعیت را تغییر دهیم.
نیاز به مجوز SSL معتبر نصب شدهی بر روی سرور
اولین مرحلهی پیش از شروع به تنظیمات SSL (عبارت صحیحتر آن TLS) بر روی SMTP Server جاری، نیاز به یک مجوز SSL معتبر نصب شدهی بر روی سرور است که نمونهی آن در مطلب «مراحل تنظیم Let's Encrypt در IIS» بررسی شدهاست.
نکتهی مهمی که در اینجا وجود دارد این است که IIS SMTP Server، فقط مجوزهای موجود در قسمت Local_Machine\Personal certificates را مشاهده میکند. بنابراین ابتدا مراحل زیر را طی کنید تا مطمئن شوید که مجوز SSL شما در این قسمت نیز قرار گرفتهاست:
در خط فرمان دستور mmc را وارد کرده و آنرا اجرا کنید. در صفحهی باز شده، گزینهی File -> Add remove snap-ins را انتخاب کنید. در اینجا در لیست ظاهر شده، certificates را انتخاب و add کنید. در صفحهی بعدی نیز computer account و سپس local computer را انتخاب کنید. اکنون بر روی دکمهی ok کلیک کنید تا صفحهی certificate مخصوص local computer ظاهر شود:
مرحلهی بعد یادداشت کردن subject این مجوز است؛ از این جهت که از مقدار آن در مرحلهی بعد استفاده خواهیم کرد.
مراحل تنظیم SSL در IIS SMTP Server
1- پس از یادداشت کردن subject مجوز SSL مدنظر، به خواص SMTP Virtual Server مراجعه کنید:
در اینجا در برگهی Delivery، بر روی دکمهی Advanced کلیک کرده و در قسمت fully-qualified domain name، مقدار subject مجوز را وارد کنید.
2- پس از آن یکبار SMTP Virtual Server را متوقف کرده و سپس اجرا کنید تا مجوز متناظر با تنظیمات جدید را دریافت کند.
3- اکنون اگر به برگهی Access مراجعه کنید، قسمت Secure communication آن فعال شدهاست (پیش از تنظیم fully-qualified domain name این گزینه غیرفعال است) و میتوان گزینهی Requires TLS Encryption آنرا انتخاب کرد. همچنین در اینجا تاریخ انقضای مجوز نیز ذکر میشود.
4- در مرحلهی آخر، در همان برگهی Delivery، بر روی دکمهی outbound security کلیک کرده و گزینهی TSL Encryption آنرا نیز فعال کنید.
تغییرات برنامه جهت ارسال ایمیلهای امن
اکنون اگر با همان تنظیمات قبلی برنامهی خود، ایمیلی را ارسال کنید، پیام خطای زیر را دریافت خواهید کرد:
The SMTP server requires a secure connection or the client was not authenticated. The server response was: 5.7.0 Must issue a STARTTLS command first
<system.net> <mailSettings> <smtp> <network enableSsl="true"/> </smtp> </mailSettings> </system.net>
قسمت security پیام دریافتی فعال شده و TLS را تشخیص دادهاست؛ که بیانگر ارتباط امن بین SMTP Virtual Server ما و جیمیل است.
ASP.NET MVC #10
چطور میشه از نمایش این پارامترها جلوگیری کرد ؟
و در url تنها نام همان اکشن نمایش داده شود
و اینکه من از FormCollection برای دریافت مقادیر فرم استفاده میکنم
تنظیمات ابتدایی Kendo UI Editor
در ذیل کدهای سمت کاربر فعال سازی مقدماتی Kendo UI را مشاهده میکنید. در قسمت tools آن، لیست امکانات و نوار ابزار مهیای آن درج شدهاند.
دو مورد insertImage و insertFile آن نیاز به تنظیمات سمت کاربر و سرور بیشتری دارند.
<!--نحوهی راست به چپ سازی --> <div class="k-rtl"> <textarea id="editor" rows="10" cols="30" style="height: 440px"></textarea> </div> @section JavaScript { <script type="text/javascript"> $(function () { $("#editor").kendoEditor({ tools: [ "bold", "italic", "underline", "strikethrough", "justifyLeft", "justifyCenter", "justifyRight", "justifyFull", "insertUnorderedList", "insertOrderedList", "indent", "outdent", "createLink", "unlink", "insertImage", "insertFile", "subscript", "superscript", "createTable", "addRowAbove", "addRowBelow", "addColumnLeft", "addColumnRight", "deleteRow", "deleteColumn", "viewHtml", "formatting", "cleanFormatting", "fontName", "fontSize", "foreColor", "backColor", "print" ], imageBrowser: { messages: { dropFilesHere: "فایلهای خود را به اینجا کشیده و رها کنید" }, transport: { read: { url: "@Url.Action("GetFilesList", "KendoEditorImages")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET', cache: false }, destroy: { url: "@Url.Action("DestroyFile", "KendoEditorImages")", type: "POST" }, create: { url: "@Url.Action("CreateFolder", "KendoEditorImages")", type: "POST" }, thumbnailUrl: "@Url.Action("GetThumbnail", "KendoEditorImages")", uploadUrl: "@Url.Action("UploadFile", "KendoEditorImages")", imageUrl: "@Url.Action("GetFile", "KendoEditorImages")?path={0}" } }, fileBrowser: { messages: { dropFilesHere: "فایلهای خود را به اینجا کشیده و رها کنید" }, transport: { read: { url: "@Url.Action("GetFilesList", "KendoEditorFiles")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET', cache: false }, destroy: { url: "@Url.Action("DestroyFile", "KendoEditorFiles")", type: "POST" }, create: { url: "@Url.Action("CreateFolder", "KendoEditorFiles")", type: "POST" }, uploadUrl: "@Url.Action("UploadFile", "KendoEditorFiles")", fileUrl: "@Url.Action("GetFile", "KendoEditorFiles")?path={0}" } } }); }); </script> }
منهای قسمت thumbnailUrl، عملکرد قسمتهای مختلف افزودن فایل و تصویر این ادیتور یکسان هستند. به همین جهت میتوان برای مثال کنترلی مانند KendoEditorFilesController را ایجاد و سپس در کنترلر KendoEditorImagesController از آن ارث بری کرد و متد دریافت و نمایش بند انگشتی تصاویر را افزود. به این ترتیب دیگر نیازی به تکرار کدهای مشترک بین این دو قسمت نخواهد بود.
نمایش لیست پوشهها و تصویر در ابتدای باز شدن صفحهی درج تصویر
با کلیک بر روی دکمهی نمایش لیست تصاویر، صفحه دیالوگی مانند شکل زیر ظاهر خواهد شد:
تنظیمات خواندن این فایلها، از قسمت read مربوط به imageBrowser دریافت میشود که cache آن نیز به false تنظیم شدهاست تا در این بین مرورگر اطلاعات را کش نکند. این مورد در حین حذف فایلها و پوشهها مهم است. زیرا اگر cache:false تنظیم نشده باشد، حذف یک فایل یا پوشه در سمت کاربر تاثیری نخواهد داشت.
imageBrowser: { transport: { read: { url: "@Url.Action("GetFilesList", "KendoEditorImages")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET', cache: false } } },
namespace KendoUI13.Controllers { public class KendoEditorFilesController : Controller { //مسیر پوشه فایلها protected string FilesFolder = "~/files"; protected string KendoFileType = "f"; protected string KendoDirType = "d"; [HttpGet] public ActionResult GetFilesList(string path) { path = GetSafeDirPath(path); var imagesList = new DirectoryInfo(path) .GetFiles() .Select(fileInfo => new KendoFile { Name = fileInfo.Name, Size = fileInfo.Length, Type = KendoFileType }).ToList(); var foldersList = new DirectoryInfo(path) .GetDirectories() .Select(directoryInfo => new KendoFile { Name = directoryInfo.Name, Type = KendoDirType }).ToList(); return new ContentResult { Content = JsonConvert.SerializeObject(imagesList.Union(foldersList), new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }), ContentType = "application/json", ContentEncoding = Encoding.UTF8 }; } protected string GetSafeDirPath(string path) { // path = مسیر زیر پوشهی وارد شده if (string.IsNullOrWhiteSpace(path)) { return Server.MapPath(FilesFolder); } //تمیز سازی امنیتی path = Path.GetDirectoryName(path); path = Path.Combine(Server.MapPath(FilesFolder), path); return path; } } }
namespace KendoUI13.Models { public class KendoFile { public string Name { set; get; } public string Type { set; get; } public long Size { set; get; } } }
- علت استفاده از CamelCasePropertyNamesContractResolver در حین بازگشت JSON نهایی، تبدیل خواص دات نتی، به نامهای سازگار با JavaScript است. برای مثال به صورت خودکار Name را تبدیل به name میکند.
- پارامتر path در ابتدای کار خالی است. اما کاربر میتواند در بین پوشههای باز شدهی توسط مرورگر تصاویر Kendo UI حرکت کند. به همین جهت مقدار آن باید هربار بررسی شده و بر این اساس لیست فایلها و پوشههای جاری بازگشت داده شوند.
مدیریت حذف تصاویر و پوشهها
همانطور که در شکل فوق نیز مشخص است، با انتخاب یک پوشه یا فایل، دکمهای با آیکن ضربدر جهت فراهم آوردن امکان حذف، ظاهر میشود. این دکمه متصل است به قسمت destroy تنظیمات ادیتور:
imageBrowser: { transport: { destroy: { url: "@Url.Action("DestroyFile", "KendoEditorImages")", type: "POST" } } },
namespace KendoUI13.Controllers { public class KendoEditorFilesController : Controller { //مسیر پوشه فایلها protected string FilesFolder = "~/files"; protected string KendoFileType = "f"; protected string KendoDirType = "d"; [HttpPost] public ActionResult DestroyFile(string name, string path) { //تمیز سازی امنیتی name = Path.GetFileName(name); path = GetSafeDirPath(path); var pathToDelete = Path.Combine(path, name); var attr = System.IO.File.GetAttributes(pathToDelete); if ((attr & FileAttributes.Directory) == FileAttributes.Directory) { Directory.Delete(pathToDelete, recursive: true); } else { System.IO.File.Delete(pathToDelete); } return Json(new object[0]); } } }
- پارامتر name دریافتی مساوی است با نام فایل انتخاب شده و path مشخص میکند که در کدام پوشه قرار داریم.
- چون در اینجا امکان حذف یک پوشه یا فایل وجود دارد، حتما نیاز است بررسی کنیم، مسیر دریافتی پوشهاست یا فایل و سپس بر این اساس جهت حذف آنها اقدام صورت گیرد.
مدیریت ایجاد یک پوشهی جدید
تنظیمات قسمت create مرورگر تصاویر، مرتبط است به زمانیکه کاربر با کلیک بر روی دکمهی +، درخواست ایجاد یک پوشهی جدید را کردهاست:
imageBrowser: { transport: { create: { url: "@Url.Action("CreateFolder", "KendoEditorImages")", type: "POST" } } },
namespace KendoUI13.Controllers { public class KendoEditorFilesController : Controller { //مسیر پوشه فایلها protected string FilesFolder = "~/files"; protected string KendoFileType = "f"; protected string KendoDirType = "d"; [HttpPost] public ActionResult CreateFolder(string name, string path) { //تمیز سازی امنیتی name = Path.GetFileName(name); path = GetSafeDirPath(path); var dirToCreate = Path.Combine(path, name); Directory.CreateDirectory(dirToCreate); return KendoFile(new KendoFile { Name = name, Type = KendoDirType }); } protected ActionResult KendoFile(KendoFile file) { return new ContentResult { Content = JsonConvert.SerializeObject(file, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }), ContentType = "application/json", ContentEncoding = Encoding.UTF8 }; } } }
- پس از ایجاد پوشه، باید نام آنرا با فرمت KendoFile به صورت JSON بازگشت داد. همچنین در اینجا Type را نیز باید به d (پوشه) تنظیم کرد.
مدیریت قسمت ارسال فایل و تصویر
زمانیکه کاربر بر روی دکمهی upload file یا بارگذاری تصاویر در اینجا کلیک میکند، اطلاعات فایل آپلودی به مسیر uploadUrl ارسال میگردد.
imageBrowser: { transport: { thumbnailUrl: "@Url.Action("GetThumbnail", "KendoEditorImages")", uploadUrl: "@Url.Action("UploadFile", "KendoEditorImages")", imageUrl: "@Url.Action("GetFile", "KendoEditorImages")?path={0}" } },
در ادامه کدهای مدیریت سمت سرور قسمت آپلود این ادیتور را مشاهده میکنید:
namespace KendoUI13.Controllers { public class KendoEditorFilesController : Controller { //مسیر پوشه فایلها protected string FilesFolder = "~/files"; protected string KendoFileType = "f"; protected string KendoDirType = "d"; [HttpPost] public ActionResult UploadFile(HttpPostedFileBase file, string path) { //تمیز سازی امنیتی var name = Path.GetFileName(file.FileName); path = GetSafeDirPath(path); var pathToSave = Path.Combine(path, name); file.SaveAs(pathToSave); return KendoFile(new KendoFile { Name = name, Size = file.ContentLength, Type = KendoFileType }); } } }
- پس از ذخیره سازی اطلاعات فایل، نیاز است اطلاعات فایل نهایی را با فرمت KendoFile به صورت JSON بازگشت دهیم.
ارث بری از KendoEditorFilesController جهت تکمیل قسمت مدیریت تصاویر
تا اینجا کدهایی را که ملاحظه کردید، برای هر دو قسمت ارسال تصویر و فایل کاربرد دارند. قسمت ارسال تصاویر برای تکمیل نیاز به متد دریافت تصاویر به صورت بند انگشتی نیز دارد که به صورت ذیل قابل تعریف است و چون از کلاس پایه KendoEditorFilesController ارث بری کردهاست، این کنترلر به صورت خودکار حاوی اکشن متدهای کلاس پایه نیز خواهد بود.
using System.Web.Mvc; namespace KendoUI13.Controllers { public class KendoEditorImagesController : KendoEditorFilesController { public KendoEditorImagesController() { // بازنویسی مسیر پوشهی فایلها FilesFolder = "~/images"; } [HttpGet] [OutputCache(Duration = 3600, VaryByParam = "path")] public ActionResult GetThumbnail(string path) { //todo: create thumb/ resize image path = GetSafeFileAndDirPath(path); return File(path, "image/png"); } } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
یکی از ابزارهای آنلاین طراحی CSS3
راه اندازی StimulSoft Report در ASP.NET MVC
فایلهای گزارشی که در استودیو خود استیمول طراحی میشن از فارسی پشتیبانی کرده و دارای خصوصیت Right to left برای true شدن میباشند.
همچین در صورتی که مشکل در نمایش داخل خود صفحه وب است در بالا کد css آن ذکر شده است.
دریافت اطلاعات از سرور، توسط Axios
- ابتدا به پوشهی sample-22-backend ای که در قسمت قبل ایجاد کردیم، مراجعه کرده و فایل dotnet_run.bat آنرا اجرا کنید، تا endpointهای REST Api آن، قابل دسترسی شوند. برای مثال باید بتوان به مسیر https://localhost:5001/api/posts در مرورگر دسترسی یافت (و یا همانطور که عنوان شد، از آدرس https://jsonplaceholder.typicode.com/posts نیز میتوانید استفاده کنید؛ چون ساختار یکسانی دارند).
-سپس در برنامهی React ای که در قسمت قبل ایجاد کردیم، فایل app.js آنرا گشوده و ابتدا کتابخانهی Axios را import میکنیم:
import axios from "axios";
componentDidMount() { const promise = axios.get("https://localhost:5001/api/posts"); console.log(promise); }
تنظیمات CORS مخصوص React در برنامههای ASP.NET Core 3x
همانطور که مشاهده میکنید، پس از ذخیره سازی تغییرات، با اجرای برنامه، این Promise در حالت pending قرار گرفته و همچنین پس از پایان آن، حاوی نتیجهی عملیات نیز میباشد که در اینجا rejected است. علت شکست عملیات را در سطر بعدی آن ملاحظه میکنید که عنوان کردهاست «CORS policy» مناسبی در سمت سرور، برای این درخواست وجود ندارد؛ چرا؟ چون برنامهی React ما در مسیر http://localhost:3000/ اجرا میشود و برنامهی Web API در مسیر دیگری https://localhost:5001/ که شمارهی پورت ایندو یکی نیست. به همین جهت عنوان میکند که نیاز است در سمت سرور، هدرهای خاصی برای پردازش این نوع درخواستهای با Origin متفاوت وجود داشته باشد، تا مرورگر اجازهی دسترسی به آنرا بدهد. برای رفع این مشکل، برنامهی sample-22-backend را گشوده و تغییرات زیر را اعمال میکنیم:
ابتدا تنظیمات AddCors را با تعریف یک CORS policy جدید مخصوص آدرس http://localhost:3000، به متد ConfigureServices کلاس آغازین برنامه اضافه میکنیم:
public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddPolicy("ReactCorsPolicy", builder => builder .AllowAnyMethod() .AllowAnyHeader() .WithOrigins("http://localhost:3000") .AllowCredentials() .Build()); }); services.AddSingleton<IPostsDataSource, PostsDataSource>(); services.AddControllers(); }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); //app.UseAuthentication(); //app.UseAuthorization(); app.UseCors("ReactCorsPolicy"); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
اینبار Promise بازگشت داده شده، در حالت resolved قرار گرفتهاست که به معنای موفقیت آمیز بودن عملیات async است. وجود [[PromiseStatus]] به معنای یک internal property است که توسط dot notation قابل دسترسی نیست. در اینجا [[PromiseValue]] نیز یک internal property غیرقابل دسترسی است که نتیجهی عملیات (response دریافتی از سرور) در آن قرار میگیرد. برای مثال در data آن، آرایهی مطالب دریافتی از سرور، قابل مشاهدهاست و یا status=200 به معنای موفقیت آمیز بودن پردازش درخواست، از سمت سرور است.
البته زمانیکه درخواست افزودن رکورد جدیدی را به سمت سرور ارسال میکنیم، میتوان دو درخواست را در برگهی network ابزارهای توسعه دهندگان مرورگر، مشاهده کرد:
در اولین درخواست، Request Method: OPTIONS را داریم که دقیقا مرتبط است با بررسی CORS توسط مرورگر.
دریافت اطلاعات شیء response از یک Promise و نمایش آن
همانطور که عنوان شد، [[PromiseValue]] نیز یک internal property غیرقابل دسترسی است. بنابراین اکنون این سؤال مطرح میشود که چگونه میتوان به اطلاعات آن دسترسی یافت؟
این شیء Promise، دارای متدی است به نام then است که نتیجهی عملیات async را بازگشت میدهد. البته این روش قدیمی کار کردن با Promiseها است و ما از آن در اینجا استفاده نخواهیم کرد. در جاوا اسکریپت مدرن، میتوان از واژهی کلیدی await برای دسترسی به شیء response دریافتی از سرور، استفاده کرد:
async componentDidMount() { const promise = axios.get("https://localhost:5001/api/posts"); console.log(promise); const response = await promise; console.log(response); }
البته قطعه کد نوشته شده، صرفا جهت توضیح مراحل مختلف عملیات، به این صورت چند مرحلهای نوشته شد، وگرنه میتوان واژهی کلیدی await را پیش از فراخوانی متدهای Axios نیز قرار داد:
async componentDidMount() { const response = await axios.get("https://localhost:5001/api/posts"); console.log(response); }
class App extends Component { state = { posts: [] }; async componentDidMount() { const { data: posts } = await axios.get("https://localhost:5001/api/posts"); this.setState({ posts }); // = { posts: posts } }
ایجاد یک مطلب جدید توسط Axios
در برنامهی React ای ایجاد شده، یک دکمهی Add نیز برای افزودن مطلبی جدید درنظر گرفته شدهاست. در یک برنامهی واقعیتر، معمولا فرمی وجود دارد و نتیجهی آن در حین submit، به سمت سرور ارسال میشود. در اینجا این سناریو را شبیه سازی خواهیم کرد:
const apiEndpoint = "https://localhost:5001/api/posts"; class App extends Component { state = { posts: [] }; async componentDidMount() { const { data: posts } = await axios.get(apiEndpoint); this.setState({ posts }); } handleAdd = async () => { const newPost = { title: "new Title ...", body: "new Body ...", userId: 1 }; const { data: post } = await axios.post(apiEndpoint, newPost); console.log(post); const posts = [post, ...this.state.posts]; this.setState({ posts }); };
- چون قرار است از آدرس https://localhost:5001/api/posts در قسمتهای مختلف برنامه استفاده کنیم، فعلا آنرا به صورت یک ثابت تعریف کرده و در متدهای get و post استفاده کردیم.
- در متد منتسب به خاصیت handleAdd، یک شیء جدید post را با ساختاری مشابه آن ایجاد کردهایم. این شیء جدید، دارای Id نیست؛ چون قرار است از سمت سرور پس از ثبت در بانک اطلاعاتی دریافت شود.
- سپس این شیء جدید را توسط متد post کتابخانهی Axios، به سمت سرور ارسال کردهایم. این متد نیز یک Promise را باز میگرداند. به همین جهت از واژهی کلیدی await برای دریافت نتیجهی واقعی آن استفاده شدهاست. همچنین هر زمانیکه await داریم، نیاز به ذکر واژهی کلیدی async نیز هست. اینبار این واژه باید پیش از قسمت تعریف پارامتر متد قرار گیرد و نه پیش از نام handleAdd؛ چون handleAdd در واقع یک خاصیت است که متدی به آن انتساب داده شدهاست.
- نتیجهی دریافتی از متد axios.post را اینبار به post، بجای posts تغییر نام دادهایم و همانطور که در تصویر زیر مشاهده میکنید، خاصیت id آن در سمت سرور مقدار دهی شدهاست:
- در آخر برای افزودن این رکورد، به مجموعهی رکوردهای موجود، از روش spread operator استفاده کردهایم تا ابتدا شیء post دریافتی از سمت سرور درج شود و سپس مابقی اعضای آرایهی posts موجود در state، در این آرایه گسترده شده و یک آرایهی جدید را تشکیل دهند. سپس این آرایهی جدید را جهت به روز رسانی state و در نتیجهی آن، به روز رسانی UI، به متد setState ارسال کردهایم، که نتیجهی آن درج این رکورد جدید، در ابتدای لیست است:
به روز رسانی اطلاعات در سمت سرور
در اینجا پیاده سازی متد put را مشاهده میکنید:
handleUpdate = async post => { post.title = "Updated"; const { data: updatedPost } = await axios.put( `${apiEndpoint}/${post.id}`, post ); console.log(updatedPost); const posts = [...this.state.posts]; const index = posts.indexOf(post); posts[index] = { ...post }; this.setState({ posts }); };
- اکنون امضای متد axios.put هرچند مانند متد post است، اما متد Update تعریف شدهی در سمت API سرور، یک چنین مسیری را نیاز دارد api/Posts/{id}. به همین جهت ذکر id مطلب، در URL نهایی نیز ضروری است.
- در اینجا نیز از واژههای await و async برای دریافت نتیجهی واقعی عملیات put و همچنین عملیات گذاری این متد به صورت async، استفاده شدهاست.
- در آخر، ابتدا آرایهی posts موجود در state را clone میکنیم. چون میخواهیم در آن، در ایندکسی که شیء post جاری قرار دارد، مقدار به روز رسانی شدهی آنرا قرار دهیم. سپس این آرایهی جدید را جهت به روز رسانی state و در نتیجهی آن، به روز رسانی UI، به متد setState ارسال کردهایم:
حذف اطلاعات در سمت سرور
برای حذف اطلاعات در سمت سرور، نیاز است یک HTTP Delete را به آن ارسال کنیم که اینکار را میتوان توسط متد axios.delete انجام داد. URL ای را که دریافت میکند، شبیه به URL ای است که برای حالت put ایجاد کردیم:
handleDelete = async post => { await axios.delete(`${apiEndpoint}/${post.id}`); const posts = this.state.posts.filter(item => item.id !== post.id); this.setState({ posts }); };
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-22-backend-part-02.zip و sample-22-frontend-part-02.zip
انتشار VS2010
1- بدون داشتن سریال اصلی و غیر trial مطالب خوانده شده در حد اطلاعات عمومی است.
2- یک روش ورود این سریال اصلی وارد کردن آن به فایل setup.sdb است (پیش از شروع به نصب).
3- یک روش دیگر وارد کردن این سریال اصلی مراجعه به کنترل پنل ویندوز (پس از نصب نسخه مدت دار موجود)، add/remove programs و سپس کلیک بر روی دکمه change/remove برنامه VS2010 است. در اینجا در صفحهی maintenance میتونید سریال برنامه رو در هر زمانی عوض کنید.