دریافت و نصب افزونهی SQL Server مخصوص VSCode
برای افزودن این افزونه، ابتدا در برگهی Extensions، عبارت mssql را جستجو کرده و سپس آنرا نصب کنید:
پس از نصب آن، مرحلهی بعد، ایجاد یک فایل خالی با پسوند sql است.
انجام اینکار ضروری است و شبیه به حالت نصب افزونهی #C میباشد. به این ترتیب وابستگیهای اصلی آن دریافت، نصب و فعال خواهند شد. این ابزارها نیز سورس باز بوده و موتور SQL Formatter، اجرای SQL و Intellisense آنرا فراهم میکند و چون مبتنی بر NET Core. تهیه شدهاست، چندسکویی است.
تا اینجا مزیتی را که به دست خواهیم آورد Syntax highlighting و Intellisense جهت درج واژههای کلیدی عبارات SQL است:
و یا اگر بر روی فایل sql جاری کلیک راست کنیم، گزینهی Format Document آن سبب میشود تا کدهای SQL نوشته شده، با فرمتی استاندارد، مرتب و یکدست شوند:
بنابراین اگر علاقمندید تا فایلها و عبارات SQL خود را فرمت کنید، این افزونهی سبک وزن چندسکویی، یک چنین قابلیت توکاری را به همراه دارد.
همچنین اگر علاقمندید به یک کتابخانهی سورس باز چندسکویی SQL Formatter و SQL Parser دات نتی دسترسی داشته باشید، کدهای Microsoft/sqltoolsservice در دسترس هستند.
اتصال به SQL Server و کار با آن
پس از نصب مقدماتی افزونهی mssql، دکمههای ctrl+shift+p (و یا F1) را فشرده و عبارت sql را جستجو کنید:
در اینجا سایر قابلیتهای این افزونهی نصب شده را میتوان مشاهده کرد. در لیست ظاهر شده، گزینهی Connect را انتخاب کنید. بلافاصله گزینهی انتخاب پروفایل ظاهر میشود. چون هنوز پروفایلی را تعریف نکردهایم، گزینهی Create connection profile را انتخاب خواهیم کرد:
در ادامه باید نام سرور را وارد کرد. یا میتوانید نام سرور کامل SQL خود را وارد کنید و یا اگر با LocalDB کار میکنید نیز امکان اتصال به آن با تایپlocaldb\MSSQLLocalDB وجود دارد:
سپس نام بانک اطلاعاتی را که میخواهیم به آن متصل شویم ذکر میکنیم:
در مرحلهی بعد، باید نوع اعتبارسنجی اتصال مشخص شود:
چون در ویندوز هستیم، میتوان گزینهی Integrated را نیز انتخاب کرد (یا همان Windows Authentication).
در آخر، جهت تکمیل کار و دخیرهی این اطلاعات وارد شده، میتوان نام پروفایل دلخواهی را وارد کرد:
اکنون کار اتصال به این بانک اطلاعاتی انجام شده و اگر به status bar دقت کنید، نمایش میدهد که در حال به روز رسانی اطلاعات intellisense است.
برای نمونه اینبار دیگر intellisense ظاهر شده منحصر به درج واژههای کلیدی SQL نیست. بلکه شامل تمام اشیاء بانک اطلاعاتی که به آن متصل شدهایم نیز میباشد:
در ادامه برای اجرا این کوئری میتوان دکمههای Ctrl+Shift+E را فشرد و یا ctrl+shift+p (و یا F1) را فشرده و در منوی ظاهر شده، گزینهی execute query را انتخاب کنید (این گزینه بر روی منوی کلیک راست ظاهر شدهی بر روی فایل sql جاری نیز قرار دارد):
نگاهی به محل ذخیره سازی اطلاعات اتصال به بانک اطلاعاتی
پروفایلی را که در قسمت قبل ایجاد کردیم، در منوی File->Preferences->Settings قابل مشاهده است:
// Place your settings in this file to overwrite the default settings { "workbench.colorTheme": "Default Light+", "files.autoSave": "afterDelay", "typescript.check.tscVersion": false, "terminal.integrated.shell.windows": "cmd.exe", "workbench.iconTheme": "material-icon-theme", "vsicons.dontShowNewVersionMessage": true, "mssql.connections": [ { "server": "(localdb)\\MSSQLLocalDB", "database": "TestASPNETCoreIdentityDb", "authenticationType": "Integrated", "profileName": "testLocalDB", "password": "" } ] }
برای مثال پروفایلی را که تعریف کردیم، در دفعات بعدی انتخاب گزینهی Connect، به صورت ذیل ظاهر میشود:
تهیهی خروجی از کوئری اجرا شده
اگر به نوار ابزار سمت راست نتیجهی کوئری اجرا شده دقت کنید، سه دکمهی تهیهی خروجی با فرمتهای csv، json و اکسل نیز در اینجا قرار داده شدهاست:
برای مثال اگر گزینهی json آنرا انتخاب کنید، بلافاصله نام فایلی را پرسیده و سپس این نتیجه را با فرمت JSON نمایش میدهد:
ضمن اینکه حتی میتوان سطرها و سلولهای خاصی را نیز از این خروجی انتخاب کرد و سپس با کلیک بر روی آنها، تنها از این انتخاب، یک خروجی ویژه را تهیه نمود:
مشاهدهی ساختار اشیاء
اگر بر روی هر کدام از اجزای یک کوئری SQL متصل به بانک اطلاعاتی، کلیک راست کنیم، گزینهی Go to definition نیز ظاهر میشود:
با انتخاب آن، بلافاصله عبارت کامل CREATE TABLE [dbo].[AppRoles] ظاهر میشود که در اینجا میتوان ساختار این جدول را به صورت یک عبارت SQL مشاهده کرد.
تغییر تنظیمات افزونهی MSSql
در منوی File->Preferences->Settings با جستجوی mssql میتوان تنظیمات پیش فرض این افزونه را یافت. برای مثال اگر میخواهید تا SQL Formatter آن به صورت خودکار تمام واژههای کلیدی را با حروف بزرگ نمایش دهد، گزینهی mssql.format.keywordCasing را انتخاب کنید. در کنار آن آیکن قلم ویرایش ظاهر میشود. با کلیک بر روی آن، منوی انتخاب uppercase را خواهیم داشت:
پس از این تغییر، اکنون بر روی صفحه کلیک راست کرده و گزینهی Format Document را انتخاب کنید. در این حالت علاوه بر تغییر فرمت سند SQL جاری، تمام واژههای کلیدی آن نیز uppercase خواهند شد.
یک سینتکس جدید برای توابع
var myFunction = function(arg) { return arg.toUpperCase(); };
var myFunction = (arg) => arg.toUpperCase();
var digits = [1,2,3,4,5,6]; var even = digits.filter( x => x%2 === 0); // فیلتر بر اساس یک شرط var evenSquares = even.map( x => x*x ); console.log(even, evenSquares); //[2,4,6] [4,16,36]
- توابع arrow زمانیکه داخل بدنهی آنها بیش از یک عبارت قرار گیرد لازم است که به طور صریح از کلید واژه return استفاده شود.
- برای برگشت یک شیء خالی باید از سینتکس زیر استفاده کنیم:
const emptyObject = () => {}; emptyObject(); // ? در این حالت یک باگ به حساب میآید //باید دقت شود که اکولادها را در بین پرانتزها قرار دهیم تا تابع به درستی کار کند const emptyObject = () => ({}); emptyObject(); // {}
- تمامی ویژگیهایی را که برای پارمترها گفته شد، میتوان برای توابع arrow بکار برد.
function () { return arguments[0]; } (...args) => args[0]
let NotGood = () => {}; let wontWork = new NotGood();
this لکسیکال (lexical) یا نحوی
$('.current-time').each(function () { setInterval(function () { $(this).text(Date.now()); }, 1000); });
$('.current-time').each(function () { var self = this; setInterval(function () { $(self).text(Date.now()); }, 1000); });
$('.current-time').each(function () { setInterval(function () { $(this).text(Date.now()); }.bind(this), 1000); });
$('.current-time').each(function () { setInterval(() => $(this).text(Date.now()), 1000); });
UserDialogs.Init(this); // Before Forms.Init
containerBuilder.RegisterInstance(UserDialogs.Instance);
public IUserDialogs UserDialogs { get; set; } public async Task Login() { if (string.IsNullOrWhiteSpace(UserName) || string.IsNullOrWhiteSpace(Password)) await UserDialogs.AlertAsync(message: "Please provide UserName and Password!", title: ")-:", okText: "Ok!"); }
using (UserDialogs.Loading("Logging in...", maskType: MaskType.Black)) { // Login implementation ... await Task.Delay(TimeSpan.FromSeconds(3)); }
await NavigationService.NavigateAsync("/Login", animated: false);
await NavigationService.NavigateAsync("/Nav/HelloWorld");
3- ممکن است بخواهید هنگام رفتن از صفحهای به صفحه دیگر، پارامتر نیز ارسال کنید. اگر برای مثال صفحه اول لیست محصولات را نمایش میدهد و با زدن روی هر محصول قرار است به صفحهای برویم که جزئیات آن محصول را ببینیم، بهتر است Id آن محصول به صورت پارامتر به صفحه دوم ارسال شود. برای این کار داریم:
await NavigationService.NavigateAsync("ProductDetail", new NavigationParameters { { "productId", productId } });
حال سؤال این است که در صفحه جزئیات یک محصول، چگونه productId را بگیریم؟ فرض کنید دو صفحه ProductsList و ProductDetail را داریم. هر صفحه دارای View و View Model است. در ViewModel مربوط به ProductDetail، یعنی ProductDetailViewModel که از BitViewModelBase ارث بری کردهاست، میتوانیم متد OnNavigatedToAsync را override کنیم. در آنجا به پارامترهای ارسال شده دسترسی داریم:
public async override Task OnNavigatedToAsync(INavigationParameters parameters) { await base.OnNavigatedToAsync(parameters); Guid productId = parameters.GetValue<Guid>("productId"); }
هر ViewModel علاوه بر OnNavigatedTo می تواند دارای OnNavigatedFrom هم باشد که زمانیکه داریم از صفحه مربوطه خارج میشویم، فراخوانی میشود.
4- برای نمایش صفحه به صورت Popup کافی است بجای اینکه View ما یک Content Page باشد، یک PopupPage باشد (برای درک بهتر، فایل IntroView.xaml را در فولدر Views باز کنید).
حتی میتوانید Animation مربوط به باز شدن پاپ آپ را هم کاملا Customize کنید. مثلا زمان باز شدن، از سمت راست صفحه وارد شود و زمان خارج شدن، Fade out شود. باز کردن Popup در Navigation Page معنی نمیدهد، پس با Nav/ در اینجا کاری نداریم. در مثال ما، بعد از لاگین میخواهیم یک صفحه Intro شامل هشدارها و راهنماییهای اولیه را در قالب Popup به کاربر نمایش دهیم. Popupها میتوانند همچون Content Pageها، دارای View Model باشند و مواردی چون OnNavigatedTo، ارسال پارامتر و هر آنچه که گفته شد، در مورد آنها نیز صدق میکند.
5- برای Master/Detail کافی است بجای Nav/HelloWorld/ از MasterDetail/Nav/HelloWorld/ استفاده کنید. این عمل باعث میشود HelloWorld در داخل Navigation Page و Navigation Page داخل Master Detail باز شود. از این سادهتر امکان ندارد!
برای تغییر UI مربوط به Master که از سمت چپ باز میشود، فایل XamAppMasterDetailView.xaml را تغییر دهید.
در قسمت بعدی به جزئیات Binding خواهیم پرداخت.
برنامه نویسی شیء گرا
در این بخش میخواهیم به بررسی یکسری از ویژگیها و نکات ریز برنامه نویسی شیء گرا در جاوا اسکریپت بپردازیم که یک برنامه نویس حرفهای جاوا اسکریپت حتما باید بر آنها واقف باشد تا بتواند کتابخانهها و Framework های موثرتر و بهینهتری را ایجاد کند. لازم به ذکر است که در این مجموعه مقالات، پیادهسازی اشیاء و شیوهی کد نویسی، بر اساس استاندارد ECMAScript 5 یا ES5 انجام خواهد شد. بنابراین از قابلیتهای جدیدی که در ES6 اضافه شدهاست، صحبت نخواهیم کرد. پس از پایان این مجموعه مقالات و پس از آگاهی کامل از قابلیتهای جاوا اسکریپت، در مجموعه مقالاتی به بررسی قابلیتهای جدید ES6 خواهیم پرداخت که مرتبط به مقالات جاری است.
همانطور که قبلا اشاره شد، در زبانهای برنامه نویسی شیء گرا، مفهومی به نام کلاس وجود دارد که ساختاری را جهت ایجاد اشیاء معرفی میکند و میتوانیم اشیاء مختلفی را از این کلاسها ایجاد نماییم. اما در جاوا اسکریپت مفهوم کلاس وجود ندارد و فقط میتوانیم از اشیاء استفاده کنیم که نسبت به زبانهای مبتنی بر کلاس متفاوت میباشد.
بر اساس تعریفی که از اشیاء در استاندارد ECMAScript صورت گرفته است، هرشیء، شامل مجموعهای از ویژگیهاست، که هر یک از آنها میتواند حاوی یک مقدار پایه، شیء و یا تابع باشد. به عبارت دیگر هر شیء شامل آرایهای از مقادیر است. هر ویژگی ( Property ) یا تابع (که در برنامه نویسی شیء گرا متد نیز نامیده میشود) توسط نام خود شناسایی میشوند که به یک مقدار دادهای نگاشت یا Map شدهاند. به همین دلیل میتوان هر شیء را به عنوان یک Hash Table تصور کرد که دادهها را به صورت یک زوج کلید مقدار یا key-value pairs نگهداری مینماید. در اینصورت نام ویژگیها و متدها به عنوان key و مقدار آنها به عنوان value در نظر گرفته میشوند.
مفهوم شیء
همانطور که قبلا اشاره شد، جهت تعریف اشیاء میتوان از دو روش استفاده نمود. در روش اول، ایجاد شیء با استفاده از شیء Object و در روش دوم، با استفاده از Object Literal Notation انجام خواهد شد. روش دوم جدیدتر و بین برنامه نویسان جاوا اسکریپت محبوبتر است. مثال دیگری را جهت یادآوری در این مورد ذکر میکنم:
var person = new Object(); person.firstName = "Meysam"; person.birth = new Date(1982, 11, 8); person.getAge = function () { var now = new Date(); return now.getFullYear() - this.birth.getFullYear(); } alert(person.firstName + ": " + person.getAge()); // Meysam: 34
var person = { firstName: "Meysam", birth: new Date(1982, 11, 8), getAge: function () { var now = new Date(); return now.getFullYear() - this.birth.getFullYear(); } }; alert(person.firstName + ": " + person.getAge()); // Meysam: 34
انواع Property ها
در ECMAScript 5 ، صفاتی برای Property ها معرفی شده است که از طریق Attribute های داخلی به Property ها اختصاص مییابد. این Attribute ها توسط موتور جاوا اسکریپت بر روی Property ها پیاده سازی میشوند و به صورت مستقیم قابل دسترسی نمیباشند. در طی فرآیند آموزش این مطالب، Attribute های داخلی را در [[]] قرار میدهیم، مثل [[Enumarable]] ، تا از سایر دستورات تفکیک شوند. به صورت کلی دو نوع ویژگی داریم که شامل Data Properties و Accessor Properties میباشند که به شرح آنها میپردازیم.
Data Properties
Data Property ها، 4 صفت یا Attribute را توصیف میکنند که عبارتند از:
[[Configurable]]
مشخص میکند یک Property اجازه حذف، تعریف مجدد و یا تغییر نوع را دارد یا خیر. بصورت پیش فرض، زمانی که یک شیء بصورت مستقیم ساخته میشود، مقدار این ویژگی True میباشد.
[[Enumarable]]
مشخص میکند که آیا امکان پیمایش یک Property توسط حلقه for-in وجود دارد یا خیر. بصورت پیش فرض، زمانیکه یک شیء بصورت مستقیم ساخته میشود، مقدار این ویژگی True میباشد.
[[Writable]]
مشخص میکند که آیا مقدار یک Property قابل تغییر میباشد یا خیر. بصورت پیش فرض، زمانیکه یک شیء بصورت مستقیم ساخته میشود، مقدار این ویژگی True میباشد.
[[Value]]
شامل مقدار واقعی یک Property و محل مقداردهی یا برگرداندن مقدار Property ها میباشد. مقدار پیش فرض آن نیز undefined میباشد.
زمانیکه یک Property به صورت عادی به یک شیء اضافه میشود، مانند مثالهای قبلی، سه Attribute اول به true تنظیم میشوند و [[Value]] با مقدار اولیه Property تنظیم میگردد. در این حالت آن Property ، قابل بروزرسانی و پیمایش میباشد. جهت تغییر ساختار یک Property و تنظیم Attribute های آن، باید آن Property را با استفاده از متد defineProperty() تعریف نماییم . شکل کلی تعریف Property با استفاده از این متد به صورت زیر میباشد:
Object.defineProperty(obj, prop, descriptor)
var person = {}; Object.defineProperty(person, "name", { writable: false, value:"Meysam" }); alert(person.name); // Meysam person.name = "Arash"; alert(person.name); // Meysam
var person = {}; Object.defineProperty(person, "name", { configurable: false, value: "Meysam" }); alert(person.name); // Meysam delete person.name; alert(person.name); // Meysam
لازم به ذکر است که میتوانید متد defineProperty() را چندین بار برای یک Property فراخوانی نموده و در هر مرحله صفات متفاوتی را تنظیم و یا صفات قبلی را تغییر دهید.
علاوه بر متد فوق، متد دیگری به نام defineProperties() وجود دارد که میتوان چند Property را بصورت همزمان تعریف و صفات آن را تنظیم نمود. شکل کلی این متد به صورت زیر است:
Object.defineProperties(obj, props)
آرگومان props یک شیء میباشد که ویژگیهای آن، نام همان Property هایی هستند که باید به obj اضافه شوند. همچنین هر ویژگی خود یک شیء میباشد که میتوان صفات آن ویژگی را تنظیم نمود. به مثال زیر توجه کنید:
var person = {}; Object.defineProperties(person, { "name": { configurable: false, value: "Meysam" }, "age": { writable:false, value:34 } });
Accessor Properties
این صفات شامل توابع getter و setter میباشند که یک یا هر دوی آنها میتوانند برای یک Property تنظیم شوند. زمانی که مقداری را از یک Property میخوانید، تابع getter فراخوانی میشود و مقدار Property مربوطه را بر میگرداند. این تابع میتواند قبل از برگرداندن مقدار، پردازش هایی را بر روی آن Property انجام داده و یک نتیجهی معتبر را برگرداند. زمانیکه Property را مقداردهی مینمایید، تابع setter فراخوانی میشود و Property را با مقدار جدید تنظیم مینماید. این تابع میتواند قبل از مقداردهی به Property ، دادهی مورد نظر را اعتبارسنجی نماید تا از ورود مقادیر نامعتبر جلوگیری کند. Accessor Properties شامل 2 صفت زیر میباشد:
[[Get]]
یک تابع میباشد و زمانی فراخوانی میگردد که مقدار یک Property را بخوانیم و مقدار پیش فرض آن undefined میباشد.
[[Set]]
یک تابع میباشد و زمانی فراخوانی میگردد که یک Property را مقداردهی نماییم و مقدار پیش فرض آن undefined میباشد. این تابع شامل یک آرگومان ورودی است که حاوی مقدار ارسالی به Property است.
مثال زیر یک پیاده سازی ساده از شیء تاریخ شمسی میباشد که هنوز از لحاظ طراحی دارای نواقصی هست و در ادامه کارآیی و کد آن را بهبود میبخشیم.
var date = { _year: 1, _month: 1, _day: 1, isLeap: function () { switch (this.year % 33) { case 1: case 5: case 9: case 13: case 17: case 22: case 26: case 30: return true; default: return false; } } }; Object.defineProperties(date, { "year": { "get": function () { return this._year; }, "set": function (newValue) { if (newValue < 1 || newValue > 9999) throw new Error("Year must be between 1 and 9999"); this._year = newValue; } }, "month": { "get": function () { return this._month; }, "set": function (newValue) { if (newValue < 1 || newValue > 12) throw new Error("Month must be between 1 and 12"); this._month = newValue; } }, "day": { "get": function () { return this._day; }, "set": function (newValue) { if (newValue < 1 || newValue > 31) throw new Error("Day must be between 1 and 31"); if (this.month === 12 && !this.isLeap() && newValue > 29) throw new Error("Day must be between 1 and 29"); if (this.month > 6 && newValue > 30) throw new Error("Day must be between 1 and 30"); this._day = newValue; } } });
دقت داشته باشید که لازم نیست حتما accessor های getter و setter با هم برای یک Property تنظیم شوند و شما میتوانید فقط یکی از آنها را برای Property به کار ببرید. اگر فقط تابع getter به یک Property اختصاص یابد، آن Property فقط خواندنی میشود و امکان تغییر مقدار آن وجود ندارد. در این صورت هر دستوری که اقدام به تغییر Property نماید، بیتاثیر خواهد بود. همچنین اگر فقط تابع setter به یک Property اختصاص یابد، آن Property فقط نوشتنی میشود و امکان خواندن مقدار آن وجود ندارد. در این صورت هر دستوری که اقدام به خواندن Property نماید، مقدار undefined برای آن برگردانده میشود.
نکتهی دیگری که باید به آن توجه کنید این است که اگر یک Property با استفاده از متد defineProperty() تعریف گردد، Attribute هایی که مقداردهی نشدهاند، مثل [[Configurable]] ، [[Enumarable]] و [[Writable]] با false مقداردهی میگردند و [[Value]] ، [[Get]] و [[Set]] مقدار undefined را بر میگردانند. در مبحث بعدی، در مورد این نکته مثالی ارائه شده است.
خواندن Attribute های مربوط به یک Property
با استفاده از متد getOwnPropertyDescriptor() میتوان، Attribute های اختصاص داده شده به Property ها را خواند و از مقدار آنها مطلع شد. این متد شامل 2 آرگومان میباشد، که آرگومان اول، شیء ای است که میخواهیم Attribute آن را بخوانیم و آرگومان دوم، نام Attribute میباشد. خروجی متد getOwnPropertyDescriptor() یک شیء از نوع PropertyDescriptor میباشد که ویژگیهای آن، همان Attribute هایی هستند که برای یک Property تنظیم شدهاند. به مثال زیر جهت خواندن Attribute های شیء تاریخ شمسی توجه کنید:
var descriptor = Object.getOwnPropertyDescriptor(date, "_year"); alert(descriptor.value); // 1 alert(descriptor.configurable); // true alert(typeof descriptor.get); // undefined descriptor = Object.getOwnPropertyDescriptor(date, "year"); alert(descriptor.value); // undefined alert(descriptor.configurable); // false alert(typeof descriptor.get); // function
برپایی پیشنیازها
در اینجا نیز از همان برنامهای که در قسمت 30، برای بررسی مثالهای React hooks ایجاد کردیم، استفاده خواهیم کرد. فقط در آن، کتابخانهی Axios را نیز نصب میکنید. به همین جهت در ریشهی پروژهی React این قسمت، دستور زیر را در خط فرمان صادر کنید:
> npm install --save axios
در ادامه میخواهیم در برنامهی React خود، لیست مطالب برنامهی backend را از سرور دریافت کرده و نمایش دهیم. همچنین یک search box را به همراه دکمههای search و clear نیز به آن اضافه کنیم.
دریافت اطلاعات اولیه از سرور، درون useEffect Hook
پس از نصب پیشنیازها و راه اندازی برنامهی backend، در ابتدا فایل src\config.json را جهت درج مشخصات آدرس REST Api آن، ایجاد میکنیم:
{ "apiUrl": "https://localhost:5001/api" }
import React from "react"; import {apiUrl} from "../../config.json"; export default function App() { return <></>; }
import App from "./components/part03/Search";
import axios from "axios"; import React, { useEffect, useState } from "react"; import { apiUrl } from "../../config.json"; export default function App() { useEffect(() => { axios .get(apiUrl + "/posts/search?query=") .then(response => console.log(response.data)); }); return <></>; }
اکنون میخواهیم این اطلاعات دریافتی را در برنامهی خود نیز نمایش دهیم. به همین جهت نیاز است تا response.data را درون state کامپوننت جاری قرار داده و در حین رندر کامپوننت، با تشکیل حلقهای بر روی آن، اطلاعات نهایی را نمایش دهیم. بنابراین نیاز به useState Hook خواهیم داشت که ابتدا آنرا import کرده و سپس آنرا تعریف و در قسمت then، فراخوانی میکنیم:
import axios from "axios"; import React, { useEffect, useState } from "react"; import { apiUrl } from "../../config.json"; export default function App() { const [results, setResults] = useState([]); useEffect(() => { axios.get(apiUrl + "/posts/search?query=").then(response => { console.log(response.data); setResults(response.data); }); });
همانطور که مشاهده میکنید، یک حلقهی بی پایان در اینجا رخ دادهاست! برای پایان آن، مجبور خواهیم شد ابتدا کنسول اجرایی برنامهی React را به صورت دستی خاتمه داده و سپس مرورگر را نیز refresh کنیم تا این حلقه، خاتمه پیدا کند.
علت این مشکل را در قسمت 30 بررسی کردیم؛ effect method تابع useEffect (همان متد در برگیرندهی قطعه کدهای axios.get در اینجا)، پس از هربار رندر کامپوننت، یکبار دیگر نیز اجرا میشود. یعنی این متد، هر دو حالت componentDidMount و componentDidUpdate کامپوننتهای کلاسی را با هم پوشش میدهد و چون در اینجا setState را با فراخوانی متد setResults داریم، یعنی درخواست رندر مجدد کامپوننت انجام شدهاست و پس از آن، مجددا effect method فراخوانی میشود و ... این حلقه هیچگاه خاتمه نخواهد یافت. به همین جهت مرورگر و برنامه، هر دو با هم هنگ میکنند!
در این برنامه فعلا میخواهیم که فقط در حالت componentDidMount، کار درخواست اطلاعات از backend صورت گیرد. به همین جهت پارامتر دوم متد useEffect را با یک آرایهی خالی مقدار دهی میکنیم:
useEffect(() => { // ... }, []);
//... export default function App() { // ... return ( <> <table className="table"> <thead> <tr> <th>Title</th> </tr> </thead> <tbody> {results.map(post => ( <tr key={post.id}> <td>{post.title}</td> </tr> ))} </tbody> </table> </> ); }
استفاده ازAsync/Await برای دریافت اطلاعات، درون یک useEffect Hook
اکنون میخواهیم درون effect method یک useEffect Hook، روش قدیمی استفادهی از callbackها و متد then را برای دریافت اطلاعات، با روش جدیدتر async/await که در قسمت 23 آنرا بیشتر بررسی کردیم، جایگزین کنیم.
useEffect(async () => { const { data } = await axios.get(apiUrl + "/posts/search?query="); console.log(data); setResults(data); }, []);
Warning: An effect function must not return anything besides a function, which is used for clean-up. It looks like you wrote useEffect(async () => ...) or returned a Promise.
برای رفع این مشکل، روش توصیه شده، ایجاد یک تابع مجزای async و سپس فراخوانی آن درون effect function است:
useEffect(() => { getResults(); }, []); const getResults = async () => { const { data } = await axios.get(apiUrl + "/posts/search?query="); console.log(data); setResults(data); };
پیاده سازی componentDidUpdate با یک useEffect Hook، جهت انجام جستجوهای پویا
تا اینجا با اضافه کردن پارامتر دومی به متد useEffect، رویداد componentDidUpdate آنرا از کار انداختیم، تا برنامه با هربار فراخوانی setState و اجرای مجدد effect function، در یک حلقهی بینهایت وارد نشود. اکنون این سؤال مطرح میشود که اگر یک textbox را برای جستجوی در عناوین نمایش داده شده، در بالای جدول آن قرار دهیم، نیاز است با هربار تغییر ورودی آن، کار فراخوانی مجدد effect function صورت گیرد، تا بتوان نتایج جدیدتری را از سرور دریافت و به کاربر نشان داد؛ این مشکل را چگونه باید حل کرد؟
برای دریافت عبارت وارد شدهی توسط کاربر و جستجو بر اساس آن، ابتدا متغیر state و متد تنظیم آنرا با استفاده از useState Hook و یک مقدار اولیهی دلخواه تنظیم میکنیم:
export default function App() { // ... const [query, setQuery] = useState("Title");
<input type="text" name="query" className="form-control my-3" placeholder="Search..." onChange={event => setQuery(event.target.value)} value={query} />
اکنون که متغیر query دارای مقدار شدهاست، میتوان از آن در متد axios.get، به نحو زیر و با ارسال یک کوئری استرینگ به سمت سرور، استفاده کرد:
const { data } = await axios.get( `${apiUrl}/posts/search?query=${encodeURIComponent(query)}` );
تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، با تایپ در textbox جستجو، تغییری در نتایج حاصل نمیشود؛ چون effect function تعریف شده که سبب اجرای مجدد axios.get میشود، طوری تنظیم شدهاست که فقط یکبار، آنهم پس از رندر اولیهی کامپوننت، اجرا شود. برای رفع این مشکل، با مقدار دهی آرایهای که به عنوان پارامتر دوم متد useEffect تعریف شده، میتوان اجرای مجدد effect function آنرا وابستهی به تغییرات متغیر query در state کامپوننت کرد:
useEffect(() => { getResults(); }, [query]);
دریافت اطلاعات جستجو، تنها با ارسال اطلاعات یک فرم به سمت سرور
تا اینجا کاربر با هر حرفی که درون textbox جستجو وارد میکند، یک کوئری، به سمت سرور ارسال خواهد شد. برای کاهش آن میتوان یک دکمهی جستجو را در کنار این textbox قرار داد تا تنها پس از کلیک بر روی آن، این جستجو صورت گیرد.
برای پیاده سازی این قابلیت، ابتدا وابستگی به query را از متد useEffect حذف میکنیم، تا دیگر با تغییر اطلاعات textbox، متد callback آن اجرا نشود (پارامتر دوم آنرا مجددا به یک آرایهی خالی تنظیم میکنیم). سپس یک دکمه را که از نوع button است و رویداد onClick آن به getResults اشاره میکند، در بالای جدول نتایج مطالب، قرار میدهیم:
<button className="btn btn-primary" type="button" onClick={getResults} > Search </button>
<form onSubmit={handleSearch}> <div className="input-group my-3"> <label htmlFor="query" className="form-control-label sr-only"></label> <input type="text" id="query" name="query" className="form-control" placeholder="Search ..." onChange={event => setQuery(event.target.value)} value={query} /> <div className="input-group-append"> <button className="btn btn-primary" type="submit"> Search </button> </div> </div> </form>
const handleSearch = event => { event.preventDefault(); getResults(); };
افزودن قابلیت پاک کردن textbox جستجو و معرفی useRef Hook
در ادامه میخواهیم یک دکمهی جدید را در کنار دکمهی Search، اضافه کنیم تا با کلیک کاربر بر روی آن، نه فقط محتوای وارد شدهی در textbox پاک شود، بلکه focus نیز به آن منتقل گردد. برای پاک کردن textbox، فقط کافی است متد setQuery را با یک رشتهی خالی ارسالی به آن فراخوانی کنیم. اما برای انتقال focus به textbox، نیاز به داشتن ارجاع مستقیمی به آن المان وجود دارد که با مفهوم آن در قسمت 18 آشنا شدیم: «برای دسترسی به یک المان DOM در React، باید یک reference را به آن نسبت داد. برای این منظور یک خاصیت جدید را در سطح کلاس کامپوننت ایجاد کرده و آنرا با React.RefObject مقدار دهی اولیه کرده و سپس ویژگی ref المان مدنظر را به این RefObject تنظیم میکنیم». برای انجام یک چنین کاری در اینجا، Hook ویژهای به نام useRef معرفی شدهاست. بنابراین برای پیاده سازی این نیازمندیها، ابتدا دکمهی Clear را در کنار دکمهی Search قرار میدهیم:
<button type="button" onClick={handleClearSearch} className="btn btn-info" > Clear </button>
import React, { useEffect, useRef, useState } from "react"; // ... export default function App() { // ... const searchInputRef = useRef(); const handleClearSearch = () => { setQuery(""); searchInputRef.current.focus(); };
البته این searchInputRef برای اینکه دقیقا به textbox تعریف شده اشاره کند، باید آنرا به ویژگی ref المان، انتساب داد:
<input type="text" id="query" name="query" className="form-control" placeholder="Search ..." onChange={event => setQuery(event.target.value)} value={query} ref={searchInputRef} />
نمایش «لطفا منتظر بمانید» در حین دریافت اطلاعات از سرور
البته در اینجا با هر بار کلیک بر روی دکمهی جستجو، نتیجهی نهایی به سرعت نمایش داده میشود؛ اما اگر سرعت اتصال کاربر کمتر باشد، با یک وقفه این امر رخ میدهد. به همین جهت بهتر است یک پیام «لطفا منتظر بمانید» را در این حین به او نمایش دهیم. به همین جهت در ابتدا state مرتبطی را به کامپوننت اضافه میکنیم:
const [loading, setLoading] = useState(false);
const getResults = async () => { setLoading(true); const { data } = await axios.get( `${apiUrl}/posts/search?query=${encodeURIComponent(query)}` ); console.log(data); setResults(data); setLoading(false); };
{loading ? ( <div className="alert alert-info">Loading results...</div> ) : ( <table className="table"> <thead> <tr> <th>Title</th> </tr> </thead> <tbody> {results.map(post => ( <tr key={post.id}> <td>{post.title}</td> </tr> ))} </tbody> </table> )}
برای آزمایش آن میتوان سرعت اتصال را در برگهی شبکهی ابزارهای توسعه دهندگان مرورگر، تغییر داد:
مدیریت خطاها در حین اعمال async
آخرین امکانی را که به این مطلب اضافه خواهیم کرد، مدیریت خطاهای اعمال async است که با try/catch صورت میگیرد:
// ... export default function App() { // ... const [error, setError] = useState(null); // ... const getResults = async () => { setLoading(true); try { const { data } = await axios.get( `${apiUrl}/posts/search?query=${encodeURIComponent(query)}` ); console.log(data); setResults(data); } catch (err) { setError(err); } setLoading(false); };
{error && <div className="alert alert-warning">{error.message}</div>}
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-30-part-03-frontend.zip و sample-30-part-03-backend.zip
معرفی برخی عملگرها
در مقالات قبلی مقدماتی را جهت ورود به برنامه نویسی شیء گرا در جاوا اسکریپت مطرح کردیم و در اینجا نیز به معرفی برخی عملگرها میپردازیم که در برنامه نویسی شیء گرا نقشی اساسی را ایفا میکنند.
عملگر typeof
از آنجائیکه جاوا اسکریپت دارای نوع دادهای ضعیف یا Loosely Typed میباشد، باید در بکارگیری متغیرها و یا آرگومانهای ورودی توابع، دقت لازم را داشته باشیم تا خطایی در اجرای کد یا محاسبات به وجود نیاید. بنابراین به راهکارهایی نیاز داریم تا بتوانیم نوع دادهای یک متغیر را تشخیص دهیم و قبل از بکارگیری آنها صحت و اعتبار دادههای ورودی را بررسی کنیم. با استفاده از عملگر typeof میتوانیم نوع دادهای یک متغیر را تشخیص دهیم که برای هر نوع دادهای مقادیر زیر را بر میگرداند:
· برای متغیرهایی که شامل مقدار undefined میباشند مقدار "undefined"
· برای متغیرهای منطقی یا Boolean مقدار "boolean"
· برای متغیرهای رشتهای یا String مقدار "string"
· برای متغیرهای عددی و مقادیر NaN و Infinity مقدار "number"
· برای تابع مقدار "function"
· برای اشیا و مقادیر null مقدار "object"
var x; var n = 12; var obj = {}; var fn = function () { }; var a = new Array(); alert(typeof x); // "undefined" alert(typeof n); // "number" alert(typeof obj); // "object" alert(typeof fn); // "function" alert(typeof a); // "object"
عملگر instanceof
عملگر typeof بهترین روش جهت تشخیص نوع دادهای متغیرهایی است که دارای نوع دادهای پایه یا Primitive Type هستند. اما جهت تشخیص نوع دادهای اشیاء و به صورت کلی انواع ارجاعی، این عملگر فقط مقدار "object" را برمیگرداند و اشارهای به ماهیت واقعی آن Object ندارد. برای این منظور میتوانیم از عملگر instanceof استفاده نماییم تا بررسی کنیم یک نوع ارجاعی از جنس چه نوع Object ی میباشد. شکل کلی استفاده از این عملگر به صورت زیر است:
result = variable instanceof constructor
اگر variable ، از جنس نوع ارجاعی تعیین شده در بخش سازنده یا constructor باشد، عملگر instanceof مقدار true را بر میگرداند. به مثال زیر توجه کنید:
var a = new Array(); alert(a instanceof Array); // true alert(a instanceof Object); // true alert(a instanceof Date); // false
عملگر in
همانطور که قبلا اشاره شد، جهت دسترسی به اعضای یک شیء، میتوان با آن شیء همانند یک آرایه رفتار نمود. به عبارتی دیگر میتوان نام یک ویژگی یا تابع را در [] قرار داد تا به مقدار آن دسترسی داشت. بنابراین میتوان همانند یک آرایه و با استفاده از یک حلقهی for-in تمامی اعضای یک شیء را پیمایش نمود. در واقع عملگر in در این حلقه بررسی میکند چه ویژگیها و توابعی در یک شیء وجود دارند و تمامی آنها را بر میگرداند. به مثال زیر توجه کنید:
var person = { name: "Meysam", age: 33, sayInfo: function () { alert(name + ":" + age); } }; for (var i in person) alert(i + " => " + person[i]);
خروجی :
name => Meysam age => 33 sayInfo => function() { alert(name + ":" + age); }
کاربرد دیگر عملگر in بررسی وجود یک ویژگی یا تابع در یک شیء میباشد. اگر ویژگی یا تابع مورد نظر در شیء وجود داشته باشد، مقدار true را بر میگرداند. به مثال زیر توجه کنید:
alert("name" in person); // true alert("sayInfo" in person); // true alert("birth" in person); // false
عملگر delete
از عملگر delete جهت حذف یک ویژگی و یا یک تابع از یک شیء استفاده میشود. به مثال زیر توجه کنید:
var person = { name: "Meysam", age: 33, sayInfo: function () { alert(name + ":" + age); } }; alert("sayInfo" in person); // true delete person.sayInfo; alert("sayInfo" in person); // false
ویژگی constructor
پس از عملگرهای فوق، یکی از پرکاربردترین ویژگیهایی که برای اشیاء وجود دارد، ویژگی constructor میباشد. در واقع این ویژگی نیز یکی از راهکارهای بررسی صحت و اعتبار متغیرها، آرگومانها و اشیا میباشد. ویژگی constructor ، به تابع سازندهی یک شیء اشاره میکند و آن سازنده را به عنوان خروجی بر میگرداند. دقت داشته باشید که خروجی این ویژگی، خود تابع سازنده میباشد و یک مقدار رشتهای نیست. به مثال زیر توجه کنید:
var obj = {}; var a = new Array(); var x = 10; alert(obj.constructor); alert(obj.constructor === Object); alert(typeof obj.constructor); alert(a.constructor); alert(x.constructor);
خروجی :
function Object() { [native code] } true function function Array() { [native code] } function Number() { [native code] }
در اینجا دیگر آمادهی ورود به برنامه نویسی شیء گرا در جاوا اسکریپت میباشیم که در مقالات بعدی به آن خواهیم پرداخت و همچنین با جزئیات بیشتری اشیاء را تشریح مینماییم.
بوت استرپ (نگارش 3) چیست؟
تازههای بوت استرپ 3 کدامند؟
- بوت استرپ 3 جهت کار با صفحههای نمایش کوچک دستگاههای موبایل به شدت بهینه سازی شده است و به همین جهت به آن mobile-first CSS framework نیز میگویند.
- در نگارش 2 بوت استرپ، حداقل دو نوع گرید واکنشگرا و غیر واکنشگرا قابل تعریف بودند. در نگارش سوم آن، تنها یک نوع گرید جدید واکنشگرا در این فریم ورک وجود دارد که میتواند چهار نوع سایز از بزرگ تا کوچک را شامل شود.
- بوت استرپ 3 با IE7 به قبل و همچنین فایرفاکس 3.6 و پایینتر دیگر سازگار نیست. البته برای پشتیبانی از IE8، نیاز به اندکی تغییرات نیز وجود خواهد داشت که در قسمتهای بعد این جزئیات را بیشتر بررسی خواهیم کرد. به عبارت دیگر بدون این تغییرات، بوت استرپ 3 در حالت پیش فرض با IE9 به بعد سازگار است.
- در بوت استرپ 3 برخلاف نگارش قبلی آن که لیستی از آیکنهای خود را در قالب چند فایل PNG image sprite که آیکنها را به صورت فشرده در کنار هم قرار داده بود، اینبار تنها از Font icons استفاده میکند. به این ترتیب تغییر اندازه این آیکنها با توجه به برداری بودن نمایش قلمها و همچنین قابلیت اعمال رنگ به آنها نیز بسیار سادهتر میگردد.
سؤال: آیا نیاز است از یک فریم ورک CSS واکنشگرا استفاده شود؟
در سالهای قبل، عموما طراحی وب بر اساس تهیه یا خرید یک سری قالبهای از پیش آماده شده، شکیل صورت میگرفتهاست. این قالبها به سرعت با برنامه، یکپارچه شده و حداکثر قلم یا رنگهای آنهارا اندکی تغییر میدادیم و یا اینکه خودمان کل این مسیر را از صفر طی میکردیم. این پروسه سفارشی، بسیار سنگین بوده و مشکل مهم آن، عدم امکان استفاده مجدد از طراحیهای انجام شده میباشد که نهایتا در دراز مدت هزینهی بالایی را برای ما به همراه خواهند داشت. اما با استفاده از فریم ورکهای CSS واکنشگرا به این مزایا خواهیم رسید:
- قسمت عمدهای از کار پیشتر برای شما انجام شده است.
برای مثال نیازی نیست تا حتما برای طرحبندی صفحه، سیستم گرید خاص خودتان را طراحی کنید و یا اینکه مانند سالهای دور، به استفاده از HTML tables پناه ببرید.
- قابلیت سفارشی سازی بسیار بالایی دارند.
برای مثال با استفاده از فناوریهایی مانند less میتوان بوت استرپ را تا حد بسیار زیادی سفارشی سازی کرد. به این ترتیب دیگر یک سایت بوت استرپ، شبیه به بوت استرپ به نظر نخواهد رسید! شاید عدهای عنوان کنند که تمام سایتهای بوت استرپ یک شکل هستند، اما واقعیت این است که این سایتها تنها از قابلیتهای سفارشی سازی بوت استرپ و less استفاده نکردهاند.
دریافت بوت استرپ 3
سایت رسمی دریافت بوت استرپ، آدرس ذیل میباشد:
البته ما از این نگارش خام استفاده نخواهیم کرد و نیاز است برای کارهای سایتهای فارسی، از نگارش راست به چپ آن استفاده کنیم. بنابراین اگر از ویژوال استودیو استفاده میکنید، میتوانید به یکی از دو بسته نیوگت ذیل مراجعه نمائید:
https://www.nuget.org/packages/Twitter.Bootstrap.RTL.Less/3.0.0
https://www.nuget.org/api/v2/package/Twitter.Bootstrap.RTL.Less/3.0.0
اگر بوت استرپ اصل را از سایت اصلی آن دریافت کنید، شامل تعداد فایلها و پوشههای بسیار بیشتری است نسبت به نمونه RTL فوق. اما فایلهای نهایی آن که مورد استفاده قرار خواهند گرفت، درون پوشه dist یا توزیع آن قرار گرفتهاند و آنچنان تفاوتی با نگارش RTL ندارند. فقط در نگارش اصل، فایلهای min و فشرده شده نیز همراه این بسته هستند که در نگارش RTL لحاظ نشدهاند. این موضوع در آینده به نفع ما خواهد بود. از این لحاظ که اگر از سیستم bundling & minification مربوط بهASP.NET استفاده کنید (جهت تولید خودکار فایلهای min در زمان اجرا)، این سیستم به صورت پیش فرض از فایلهای min موجود استفاده میکند و ممکن است مدتی سردرگم باشید که چرا تغییراتی را که به فایل CSS بوت استرپ اعمال کردهام، در سایت اعمال نمیشوند. به علاوه امکان اعمال تغییرات و حتی دیباگ فایلهای غیرفشرده خصوصا جاوا اسکریپتی آن نیز بسیار سادهتر و مفهومتر است.
جهت مطالعه مباحث تکمیلی در مورد نحوه فشرده سازی فایلهای CSS یا JS میتوانید به مقالات ذیل، در سایت جاری مراجعه نمائید:
علاوه بر اینها در نگارش سوم بوت استرپ، تعدادی فایل CSS جدید به نام قالب یا theme نیز اضافه شدهاند که همراه نسخه RTL نیست. برای مثال اگر به پوشه bootstrap-3.0.0.zip\bootstrap-3.0.0\dist\css مراجعه کنید، فایل bootstrap-theme.css نیز قابل مشاهده است. به این ترتیب قالبی و لایهای بر روی مقادیر پیش فرض موجود در فایل bootstrap.css اعمال خواهند شد؛ برای مثال اعمال طراحی تخت یا flat مدرن آن به دکمهها و عناصر دیگر این مجموعه.
شروع یک فایل HTML با بوت استرپ
تا اینجا فرض بر این است که فایلهای بوت استرپ را دریافت کردهاید. در ادامه قصد داریم، نحوه معرفی این فایلها را در یک فایل ساده HTML بررسی کنیم.
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Website</title> <link href="Content/css/bootstrap-rtl.css" rel="stylesheet"> <link href="Content/css/custom.css" rel="stylesheet"> </head> <body> </body> </html>
اگر سایت شما از تعاریف CSS سفارشی دیگری نیز استفاده میکند، تعاریف آنها باید پس از بوت استرپ، ذکر گردند.
افزودن اسکریپتهای بوت استرپ 3
برای کار با اسکریپتهای بوت استرپ 3 نیاز است ابتدا jQuery را به صورت جداگانه دریافت کنیم. در حال حاضر اگر به سایت جیکوئری مراجعه کنید با دو نگارش 1.x و 2.x این کتابخانه مواجه خواهید شد. اگر نیاز به پشتیبانی از IE 8 را در محل کار خود دارید، باید از نگارش 1.x استفاده کنید. نگارش آخر 1.x کتابخانه جیکوئری را از طریق CDN آن همواره میتوان مورد استفاده قرار داد:
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
پس از تعریف جیکوئری، تعریف اسکریپتهای بوت استرپ قرار میگیرد (چون وابسته است به جیکوئری). فایل bootstrap-rtl.js شامل تمام زیر فایلهای مورد نیاز نیز میباشد:
<script src="Scripts/bootstrap-rtl.js"></script>
این فایل 4 کیلوبایتی را نیز باید به تعاریف اسکریپتهای مورد نیاز، اضافه کرد:
<script src="Scripts/respond.min.js"></script>
تا اینجا ساختار صفحه HTML تهیه شده جهت استفاده از امکانات بوت استرپ 3، شکل زیر را خواهد داشت:
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Website</title> <link href="Content/css/bootstrap-rtl.css" rel="stylesheet"> <link href="Content/css/custom.css" rel="stylesheet"> <script src="Scripts/respond.min.js"></script> </head> <body> <script src="http://code.jquery.com/jquery-latest.min.js"></script> <script src="Scripts/bootstrap-rtl.js"></script> </body> </html>
فایلهای نهایی این قسمت را از اینجا نیز میتوانید دریافت کنید:
bs3-sample01.zip
تهیه فیلتر سفارشی SiteAuthorize
برای بررسی اعمال Ajaxایی، نیاز است فیلتر پیش فرض Authorize سفارشی شود:
using System; using System.Net; using System.Web.Mvc; namespace MvcApplication28.Helpers { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] public sealed class SiteAuthorizeAttribute : AuthorizeAttribute { protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { if (filterContext.HttpContext.Request.IsAuthenticated) { throw new UnauthorizedAccessException(); //to avoid multiple redirects } else { handleAjaxRequest(filterContext); base.HandleUnauthorizedRequest(filterContext); } } private static void handleAjaxRequest(AuthorizationContext filterContext) { var ctx = filterContext.HttpContext; if (!ctx.Request.IsAjaxRequest()) return; ctx.Response.StatusCode = (int)HttpStatusCode.Forbidden; ctx.Response.End(); } } }
using System.Web.Mvc; using MvcApplication28.Helpers; namespace MvcApplication28.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } [SiteAuthorize] [HttpPost] public ActionResult SaveData(string data) { if(string.IsNullOrWhiteSpace(data)) return Content("NOk!"); return Content("Ok!"); } } }
@{ ViewBag.Title = "Index"; var postUrl = this.Url.Action(actionName: "SaveData", controllerName: "Home"); } <h2> Index</h2> @using (Html.BeginForm(actionName: "SaveData", controllerName: "Home", method: FormMethod.Post, htmlAttributes: new { id = "form1" })) { @Html.TextBox(name: "data") <br /> <span id="btnSave">Save Data</span> } @section Scripts { <script type="text/javascript"> $(document).ready(function () { $("#btnSave").click(function (event) { $.ajax({ type: "POST", url: "@postUrl", data: $("#form1").serialize(), // controller is returning a simple text, not json complete: function (xhr, status) { var data = xhr.responseText; if (xhr.status == 403) { window.location = "/login"; } } }); }); }); </script> }
نکته تکمیلی:
در متد handleAjaxRequest، میتوان یک JavaScriptResult را نیز بازگشت داد تا همان کدهای مرتبط با window.location را به صورت خودکار به صفحه تزریق کند:
filterContext.Result = new JavaScriptResult { Script="window.location = '" + redirectToUrl + "'"};