خلاصهی جدول زیر یک جمله است: به مهاجم امکان دیباگ برنامه را ندهید!
تنظیمات نا امن | تنظیمات امن |
<configuration> <system.web> <sessionState cookieless="UseUri"> | <configuration> <system.web> <sessionState cookieless="UseCookies"> |
<configuration> <system.web> <httpCookies httpOnlyCookies="false"> | <configuration> <system.web> <httpCookies httpOnlyCookies="true"> |
<configuration> <system.web> <customErrors mode="Off"> | <configuration> <system.web> <customErrors mode="RemoteOnly"> |
<configuration> <system.web> <trace enabled="true" localOnly="false"> | <configuration> <system.web> <trace enabled="false" localOnly="true"> |
<configuration> <system.web> <compilation debug="true"> | <configuration> <system.web> <compilation debug="false"> |
مرور مثال این قسمت
تا اینجا در مثالی که بررسی کردیم، لیست اتاقها توسط کامپوننت IndividualRoom.razor و لیست خدمات رفاهی یک هتل توسط کامپوننت IndividualAmenity.razor در کامپوننت والد DemoHotel.razor، نمایش داده شدهاند:
دکمههای حذف و ویرایش هر اتاق نیز در کامپوننت EditDeleteButton.razor قرار دارند که توسط کامپوننت IndividualRoom.razor مورد استفاده قرار میگیرند.
اکنون میخواهیم با کلیک بر روی دکمهی حذف کامپوننت EditDeleteButton، یک modal بوت استرپی جهت دریافت تائیدیهی عملیات، نمایش داده شود و در صورت تائید آن، اتاق انتخابی از لیست اتاقهای کامپوننت DemoHotel حذف گردد.
بنابراین در ابتدا کامپوننت EditDeleteButton، به کامپوننت IndividualRoom خبر درخواست حذف یک اتاق را میدهد. سپس کامپوننت IndividualRoom، یک مودال دریافت تائیدیهی حذف را نمایش میدهد. پس از تائید حذف توسط کاربر، این رویداد به کامپوننت DemoHotel، جهت حذف اتاق انتخابی از لیست اتاقها، اطلاع رسانی خواهد شد.
ایجاد کامپوننت مودال دریافت تائید
در ابتدا، فایل جدید Pages\LearnBlazor\LearnBlazorComponents\Confirmation.razor را ایجاد کرده و به صورت زیر تکمیل میکنیم:
@if (ShowModal) { <div class="modal-backdrop show"></div> <div class="modal fade show" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true" style="display: block;"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title"> @Title </h5> <button @onclick="OnCancelClicked" type="button" class="close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> @ChildContent </div> <div class="modal-footer"> <button @onclick="OnCancelClicked" type="button" class="btn btn-secondary">@CancelButtonLabel</button> <button @onclick="OnConfirmClicked" type="button" class="btn btn-primary">@OkButtonLabel</button> </div> </div> </div> </div> } @code { private bool ShowModal; [Parameter] public string Title { get; set; } = "Confirm"; [Parameter] public string CancelButtonLabel { get; set; } = "Cancel"; [Parameter] public string OkButtonLabel { get; set; } = "Ok"; [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public EventCallback OnConfirm { get; set; } [Parameter] public EventCallback OnCancel { get; set; } public void Show() => ShowModal = true; public void Hide() => ShowModal = false; private async Task OnConfirmClicked() { ShowModal = false; await OnConfirm.InvokeAsync(); } private async Task OnCancelClicked() { ShowModal = false; await OnCancel.InvokeAsync(); } }
- در اینجا در ابتدا تگها و کلاسهای مرتبط با نمایش یک modal استاندارد بوت استرپی را مشاهده میکنید.
- اگر فیلد خصوصی ShowModal به false تنظیم شود، چون کل محتوای این کامپوننت از DOM حذف خواهد شد (اثر if@ تعریف شده)، سبب مخفی شدن و عدم نمایش آن میگردد.
- این کامپوننت عنوان و برچسبهای دکمههای خودش را به صورت پارامتر دریافت میکند.
- برای اینکه بتوان محتوای نمایشی این کامپوننت را پویا کرد، از یک RenderFragment استفاده کردهایم:
[Parameter] public RenderFragment ChildContent { get; set; }
- همچنین میخواهیم به کامپوننت فراخوان این امکان را بدهیم تا بتواند به صورت مستقل، سبب نمایش یا مخفی شدن وهلهای از این کامپوننت شود. به همین جهت دو متد عمومی Show و Hide نیز تعریف شدهاند.
هدایت درخواست Delete به کامپوننت نمایش مشخصات اتاق
با توجه به اینکه دکمههای حذف و ویرایش هر اتاق، در کامپوننت Pages\LearnBlazor\LearnBlazorComponents\EditDeleteButton.razor قرار دارند، به آن مراجعه کرده و امکان انتشار این رخداد را به فراخوان آن، با تعریف رویداد OnDelete میدهیم:
@if (IsAdmin) { <input type="button" class="btn btn-danger" value="Delete" @onclick="OnDelete" /> <input type="button" class="btn btn-success" value="Edit" /> } @code { [Parameter] public bool IsAdmin { get; set; } [Parameter] public EventCallback OnDelete { get; set; } }
کامپوننت Pages\LearnBlazor\LearnBlazorComponents\IndividualRoom.razor که نمایش دهندهی جزئیات هر اتاق است، با مدیریت رویداد OnDelete کامپوننت EditDeleteButton، از درخواست حذف اتاق جاری مطلع میشود:
<EditDeleteButton IsAdmin="true" OnDelete="OnDeleteClicked"></EditDeleteButton> <Confirmation @ref="Confirmation1" OnCancel="OnCancelClicked" OnConfirm="@(() => OnDeleteSelectedRoom.InvokeAsync(Room))"> <div> Do you want to delete `@Room.Name`? </div> </Confirmation>
- سپس نیاز است زمانیکه OnDelete کامپوننت EditDeleteButton رخداد، این modal دریافت تائید را نمایش دهیم. به همین جهت باید بتوانیم متد عمومی Show آنرا فراخوانی کنیم. بنابراین از ref@ برای دسترسی به وهلهای از این کامپوننت تعریف شده استفاده کردهایم تا توسط شیء Confirmation1، بتوانیم متد عمومی Show را در رویدادگردان منتسب به OnDelete فراخوانی کنیم.
- همچنین دو رویداد OnCancel و OnConfirm کامپوننت دریافت تائید را به متد خصوصی OnCancelClicked و رویداد جدید OnDeleteSelectedRoom متصل کردهایم. یعنی زمانیکه کاربر بر روی دکمهی OK مودال ظاهر شده کلیک میکند، Room جاری، از طریق رویداد OnDeleteSelectedRoom به فراخوان کامپوننت IndividualRoom ارسال میشود تا دقیقا بداند که چه اتاقی را بایدحذف کند:
@code { Confirmation Confirmation1; [Parameter] public BlazorRoom Room { get; set; } [Parameter] public EventCallback<BlazorRoom> OnDeleteSelectedRoom { get; set; } void OnDeleteClicked() { Confirmation1.Show(); } void OnCancelClicked() { // Confirmation1.Hide(); } // ... }
حذف اتاق انتخابی در کامپوننت نمایش لیست اتاقها
مرحلهی آخر این مثال، بسیار سادهاست. در حلقهای که هر اتاق را توسط کامپوننت IndividualRoom نمایش میدهد، به رویداد OnDeleteSelectedRoom گوش فرا داده و selectedRoom یا همان BlazorRoom ارسالی را، دریافت و از لیست Rooms کامپوننت جاری حذف میکنیم. این حذف شدن، بلافاصله سبب رندر مجدد UI و حذف آن از رابط کاربری نیز خواهد شد:
@foreach (var room in Rooms) { <IndividualRoom OnRoomCheckBoxSelection="RoomSelectionCounterChanged" Room="room" OnDeleteSelectedRoom="@(selectedRoom => Rooms.Remove(selectedRoom))"> </IndividualRoom> }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-12.zip
مدتها از کلاس DelegateCommand معرفی شده در این آدرس استفاده میکردم. این کلاس یک مشکل جزئی دارد و آن هم عدم بررسی مجدد قسمت canExecute به صورت خودکار هست.
خلاصهای برای کسانی که بار اول هست با این مباحث برخورد میکنند؛ یا MVVM به زبان بسیار ساده:
در برنامه نویسی متداول سیستم مایکروسافتی، در هر سیستمی که ایجاد کرده و در هر فناوری که ارائه داده از زمان VB6 تا امروز، شما روی یک دکمه مثلا دوبار کلیک میکنید و در فایل اصطلاحا code behind این فرم و در روال رخدادگردان آن شروع به کد نویسی خواهید کرد. این مورد تقریبا در همه جا صادق است؛ از WinForms تا WPF تا Silverlight تا حتی ASP.NET Webforms . به عمد هم این طراحی صورت گرفته تا برنامه نویسها در این محیطها زیاد احساس غریبی نکنند. اما این روش یک مشکل مهم دارد و آن هم «توهم» جداسازی رابط کاربر از کدهای برنامه است. به ظاهر یک فایل فرم وجود دارد و یک فایل جدای code behind ؛ اما در عمل هر دوی اینها یک partial class یا به عبارتی «یک کلاس» بیشتر نیستند. «فکر میکنیم» که از هم جدا شدند اما واقعا یکی هستند. شما در code behind صفحه به صورت مستقیم با عناصر رابط کاربری سروکار دارید و کدهای شما به این عناصر گره خوردهاند.
شاید بپرسید که چه اهمیتی دارد؟
مشکل اول: امکان نوشتن آزمونها واحد برای این متدها وجود ندارد یا بسیار سخت است. این متدها فقط با وجود فرم و رابط کاربری متناظر با آنها هست که معنا پیدا میکنند و تک تک عناصر آنها وهله سازی میشوند.
مشکل دوم: کد نوشته فقط برای همین فرم جاری آن قابل استفاده است؛ چون به صورت صریح به عناصر موجود در فرم اشاره میکند. نمیتونید این فایل code behind رو بردارید بدون هیچ تغییری برای فرم دیگری استفاده کنید.
مشکل سوم: نمیتونید طراحی فرم رو بدید به یک نفر، کد نویسی اون رو به شخصی دیگر. چون ایندو لازم و ملزوم یکدیگرند.
این سیستم کد نویسی دهه 90 است.
چند سالی است که طراحان سعی کردهاند این سیستم رو دور بزنند و روشهایی رو ارائه بدن که در آنها فرمهای برنامه و فایلهای پیاده سازی کنندهی منطق آن هیچگونه ارتباط مستقیمی باهم نداشته باشند؛ به هم گره نخورده باشند؛ ارجاعی به هیچیک از عناصر بصری فرم را در خود نداشته باشند. به همین دلیل ASP.NET MVC به وجود آمده و در همان سالها مثلا MVVM .
سؤال:
الان که رابط کاربری از فایل پیاده سازی کننده منطق آن جدا شده و دیگر Code behind هم نیست (همان partial class های متداول)، این فایلها چطور متوجه میشوند که مثلا روی یک فرم، شیءایی قرار گرفته؟ از کجا متوجه خواهند شد که روی دکمهای کلیک شده؟ اینها که ارجاعی از فرم را در درون خود ندارند.
در الگوی MVVM این سیم کشی توسط امکانات قوی Binding موجود در WPF میسر میشود. در ASP.NET MVC چیزی شبیه به آن به نام Model binder و همان مکانیزمهای استاندارد HTTP این کار رو میکنه. در MVVM شما بجای code behind خواهید داشت ViewModel (اسم جدید آن). در ASP.NET MVC این اسم شده Controller. بنابراین اگر این اسامی رو شنیدید زیاد تعجب نکنید. اینها همان Code behind قدیمی هستند اما ... بدون داشتن ارجاعی از رابط کاربری در خود که ... اطلاعات موجود در فرم به نحوی به آنها Bind و ارسال میشوند.
این سیم کشیها هم نامرئی هستند. یعنی فایل ViewModel یا فایل Controller نمیدونند که دقیقا از چه کنترلی در چه فرمی این اطلاعات دریافت شده.
این ایده هم جدید نیست. شاید بد نباشه به دوران طلایی Win32 برگردیم. همان توابع معروف PostMessage و SendMessage را به خاطر دارید؟ شما در یک ترد میتونید با مثلا PostMessage شیءایی رو به یک فرم که در حال گوش فرا دادن به تغییرات است ارسال کنید (این سیم کشی هم نامرئی است). بنابراین پیاده سازی این الگوها حتی در Win32 و کلیه فریم ورکهای ساخته شده بر پایه آنها مانند VCL ، VB6 ، WinForms و غیره ... «از روز اول» وجود داشته و میتونستند بعد از 10 سال نیان بگن که اون روشهای RAD ایی رو که ما پیشنهاد دادیم، میشد خیلی بهتر از همان ابتدا، طور دیگری پیاده سازی بشه.
ادامه بحث!
این سیم کشی یا اصطلاحا Binding ، در مورد رخدادها هم در WPF وجود داره و اینبار به نام Commands معرفی شدهاست. به این معنا که بجای اینکه بنویسید:
<Button Click="btnClick_Event">Last</Button>
بنویسید:
<Button Command="{Binding GoLast}">Last</Button>
حالا باید مکانیزمی وجود داشته باشه تا این پیغام رو به ViewModel برنامه برساند. اینکار با پیاده سازی اینترفیس ICommand قابل انجام است که معرفی یک کلاس عمومی از پیاده سازی آنرا در ابتدای بحث مشاهده نمودید.
در یک DelegateCommand، توسط متد منتسب به executeAction، مشخص خواهیم کرد که اگر این سیم کشی برقرار شد (که ما دقیقا نمیدانیم و نمیخواهیم که بدانیم از کجا و کدام فرم دقیقا)، لطفا این اعمال را انجام بده و توسط متد منتسب به canExecute به سیستم Binding خواهیم گفت که آیا مجاز هستی این اعمال را انجام دهی یا خیر. اگر این متد false برگرداند، مثلا دکمه یاد شده به صورت خودکار غیرفعال میشود.
اما مشکل کلاس DelegateCommand ذکر شده هم دقیقا همینجا است. این دکمه تا ابد غیرفعال خواهد ماند. در WPF کلاسی وجود دارد به نام CommandManager که حاوی متدی استاتیکی است به نام InvalidateRequerySuggested. اگر این متد به صورت دستی فراخوانی شود، یکبار دیگر کلیه متدهای منتسب به تمام canExecute های تعریف شده، به صورت خودکار اجرا میشوند و اینجا است که میتوان دکمهای را که باید مجددا بر اساس شرایط جاری تغییر وضعیت پیدا کند، فعال کرد. بنابراین فراخوانی متد InvalidateRequerySuggested یک راه حل کلی رفع نقیصهی ذکر شده است.
راه حل دومی هم برای حل این مشکل وجود دارد. میتوان از رخدادگردان CommandManager.RequerySuggested استفاده کرد. روال منتسب به این رخدادگردان هر زمانی که احساس کند تغییری در UI رخ داده، فراخوانی میشود. بنابراین پیاده سازی بهبود یافته کلاس DelegateCommand به صورت زیر خواهد بود:
using System;
using System.Windows.Input;
namespace MvvmHelpers
{
// Ref.
// - http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/
// - http://joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/
public class DelegateCommand<T> : ICommand
{
readonly Func<T, bool> _canExecute;
bool _canExecuteCache;
readonly Action<T> _executeAction;
public DelegateCommand(Action<T> executeAction, Func<T, bool> canExecute = null)
{
if (executeAction == null)
throw new ArgumentNullException("executeAction");
_executeAction = executeAction;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { if (_canExecute != null) CommandManager.RequerySuggested += value; }
remove { if (_canExecute != null) CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_executeAction((T)parameter);
}
}
}
استفاده از آن هم در ViewModel ساده است. یکبار خاصیتی به این نام تعریف میشود. سپس در سازنده کلاس مقدار دهی شده و متدهای متناظر آن تعریف خواهند شد:
public DelegateCommand<string> GoLast { set; get; }
//in ctor
GoLast = new DelegateCommand<string>(goLast, canGoLast);
private bool canGoLast(string data)
{
//ex.
return ListViewGuiData.CurrentPage != ListViewGuiData.TotalPage - 1;
}
private void goLast(string data)
{
//do something
}
مزیت کلاس DelegateCommand جدید هم این است که مثلا متد canGoLast فوق، به صورت خودکار با به روز رسانی UI ، فراخوانی و تعیین اعتبار مجدد میشود.
در اینجا نیز برای بررسی ویژگیهای جاوا اسکریپت مدرن، یک پروژهی جدید React را ایجاد میکنیم.
> create-react-app sample-03 > cd sample-03 > npm start
همچنین چون در این قسمت خروجی UI نخواهیم داشت، تمام خروجی را در کنسول developer tools مرورگر خود میتوانید مشاهده کنید (فشردن دکمهی F12).
متد Array.map
در برنامههای مبتنی بر React، از متد Array.map برای رندر لیستها استفاده میشود و نمونههای بیشتری از آنرا در قسمتهای بعدی مشاهده خواهید کرد.
فرض کنید آرایهای از رنگها را داریم. اکنون میخواهیم لیستی را به صورت <li>color</li> به ازای هر آیتم آن، تشکیل دهیم:
const colors = ["red", "green", "blue"];
const items = colors.map(function(color) { return "<li>" + color + "</li>"; }); console.log(items);
این مثال را توسط arrow functions نیز میتوان بازنویسی کرد:
const items2 = colors.map(color => "<li>" + color + "</li>"); console.log(items2);
یک مرحلهی دیگر هم میتوانیم این قطعه کد را زیباتر کنیم؛ جمع زدن رشتهها در ES6 معادل بهتری پیدا کردهاست که template literals نام دارد:
const items3 = colors.map(color => `<li>${color}</li>`); console.log(items3);
Object Destructuring
فرض کنید شیء آدرس را به صورت زیر تعریف کردهایم:
const address = { street: "street 1", city: "city 1", country: "country 1" };
const street1 = address.street; const city1 = address.city; const country1 = address.country;
const { street, city, country } = address;
در اینجا باید نام متغیرهای تعریف شده با نام خواص شیء آدرس یکی باشند. همچنین ذکر تمامی این متغیرها نیز ضرورتی ندارد و برای مثال اگر فقط نیاز به street بود، میتوان تنها آنرا ذکر کرد.
اگر خواستیم نام متغیر دیگری را بجای نام خواص شیء آدرس انتخاب کنیم، میتوان از یک نام مستعار ذکر شدهی پس از : استفاده کرد:
const { street: st } = address; console.log(st);
Spread Operator
فرض کنید دو آرایهی زیر را داریم:
const first = [1, 2, 3]; const second = [4, 5, 6];
const combined = first.concat(second); console.log(combined);
در ES6 با استفاده از عملگر ... که spread نیز نام دارد، میتوان قطعه کد فوق را به صورت زیر بازنویسی کرد:
const combined2 = [...first, ...second]; console.log(combined2);
شاید اینطور به نظر برسد که بین دو راه حل ارائه شده آنچنانی تفاوتی نیست. اما مزیت قطعه کد دوم، سهولت افزودن المانهای جدید، به هر قسمتی از آرایه است:
const combined2 = [...first, "a", ...second, "b"]; console.log(combined2);
کاربرد دیگر عملگر spread امکان clone سادهی یک آرایهاست:
const clone = [...first]; console.log(clone);
به علاوه امکان اعمال آن به اشیاء نیز وجود دارد:
const firstObject = { name: "User 1" }; const secondObject = { job: "Job 1" }; const combinedObject = { ...firstObject, ...secondObject, location: "Here" }; console.log(combinedObject);
{name: "User 1", job: "Job 1", location: "Here"}
و امکان clone اشیاء توسط آن هم وجود دارد:
const clonedObject = { ...firstObject }; console.log(clonedObject);
کلاسها در ES 6
قطعه کد کلاسیک زیر را که کار ایجاد اشیاء را در جاوا اسکریپت انجام میدهد، در نظر بگیرید:
const person = { name: "User 1", walk() { console.log("walk"); } }; const person2 = { name: "User 2", walk() { console.log("walk"); } };
class Person { constructor(name) { this.name = name; } walk() { console.log("walk"); } }
باید دقت داشت که class Person تنها یک قالب است و const person ای که پیشتر تعریف شد، یک شیء. برای اینکه از روی قالب تعریف شدهی Person، یک شیء را ایجاد کنیم، به صورت زیر توسط واژهی کلیدی new عمل میشود:
const person3 = new Person("User 3"); console.log(person3.name); person3.walk();
یک نکته: در جاوا اسکریپت، کلاسها نیز شیء هستند! از این جهت که کلاسها در جاوا اسکریپت صرفا یک بیان نحوی زیبای تابع constructor هستند و توابع در جاوا اسکریپت نیز شیء میباشند!
ارث بری کلاسها در ES6
فرض کنید میخواهیم کلاس Teacher را به نحو زیر تعریف کنیم:
class Teacher { teach() { console.log("teach"); } }
class Teacher extends Person { teach() { console.log("teach"); } }
علت اینجا است که کلاس Teacher، نه فقط متد walk کلاس Person را به ارث بردهاست، بلکه سازندهی آنرا نیز به ارث میبرد:
const teacher = new Teacher("User 4");
console.log(teacher.name); teacher.teach(); teacher.walk();
در ادامه فرض کنید علاوه بر ذکر نام، نیاز به ذکر مدرک معلم نیز در سازندهی کلاس وجود دارد:
class Teacher extends Person { constructor(name, degree) {}
Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
class Teacher extends Person { constructor(name, degree) { super(name); this.degree = degree; } teach() { console.log("teach"); } }
const teacher = new Teacher("User 4", "MSc");
در برنامههای React، هر زمانیکه یک کامپوننت جدید تعریف میشود، کلاس آن، از کلاس پایهی کامپوننت، ارث بری خواهد کرد. به این ترتیب میتوان به تمام امکانات این کلاس پایه، بدون نیاز به تکرار آنها در کلاسهای مشتق شدهی از آن، دسترسی یافت.
ماژولها در ES 6
تا اینجا اگر مثالها را دنبال کرده باشید، تمام آنها را داخل همان فایل index.js درج کردهایم. به این ترتیب کم کم دارد مدیریت این فایل از دست خارج میشود. امکان تقسیم کدهای index.js به چندین فایل، مفهوم ماژولها را در ES6 تشکیل میدهد. برای این منظور قصد داریم هر کلاس تعریف شده را به یک فایل جداگانه که ماژول نامیده میشود، منتقل کنیم. از کلاس Person شروع میکنیم و آنرا به فایل جدید person.js و کلاس Teacher را به فایل جدید teacher.js منتقل میکنیم.
البته اگر از افزونههای VSCode استفاده میکنید، اگر کرسر را بر روی نام کلاس قرار دهید، یک آیکن لامپ مانند ظاهر میشود. با کلیک بر روی آن، منویی که شامل گزینهی move to a new file هست، برای انجام سادهتر این عملیات (ایجاد یک فایل جدید js، سپس انتخاب و cut کردن کل کلاس و در آخر کپی کردن آن در این فایل جدید) پیشبینی شدهاست.
هرچند این عملیات تا به اینجا خاتمه یافته به نظر میرسد، اما نیاز به اصلاحات زیر را نیز دارد:
- هنگام کار با ماژولها، اشیاء تعریف شدهی در آن به صورت پیشفرض، خصوصی و private هستند و خارج از آنها قابل دسترسی نمیباشند. به این معنا که class Teacher ما که اکنون در یک ماژول جدید قرار گرفتهاست، توسط سایر قسمتهای برنامه قابل مشاهده و دسترسی نیست.
- برای public تعریف کردن یک کلاس تعریف شدهی در یک ماژول، نیاز است آنرا export کنیم. انجام این کار نیز سادهاست. فقط کافی است واژهی کلیدی export را به پیش از class اضافه کنیم:
export class Teacher extends Person {
برای رفع این مشکل، باید این وابستگی را import کرد:
import { Person } from "./Person"; export class Teacher extends Person {
- مرحلهی آخر، اصلاح فایل index.js است؛ چون اکنون تعاریف Person و Teacher را نمیشناسد.
import { Person } from "./Person"; import { Teacher } from "./Teacher";
Exportهای پیشفرض و نامدار در ES6
اشیاء تعریف شدهی در یک ماژول، به صورت پیشفرض private هستند؛ مگر اینکه export شوند. برای مثال export class Teacher و یا export function xyz. به اینها named exports گویند. حال اگر ماژول ما تنها یک شیء عمومی شده را داشت (کلاسها هم شیء هستند!)، میتوان از واژهی کلیدی default نیز در اینجا استفاده کرد:
export default class Teacher extends Person {
import Teacher from "./Teacher";
در ادامه اگر یک export نامدار دیگر را به این ماژول اضافه کنیم (مانند تابع testTeacher):
import { Person } from "./Person"; export function testTeacher() { console.log("Test Teacher"); } export default class Teacher extends Person {
import Teacher, { testTeacher } from "./Teacher";
import React, { Component } from 'react';
یک نکته: اگر در VSCode داخل {}، دکمههای ctrl+space را فشار دهید، میتوانید منوی exportهای ماژول تعریف شده را مشاهده کنید.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-03.zip
StyleCop 4.3.1.3
به روز رسانی جدید StyleCop و یا همان ابزار استاندارد سازی و یکنواخت کردن ظاهر کدهای نوشته شده که پیشتر در تیمهای داخلی مایکروسافت مورد استفاده قرار میگرفت، از آدرس زیر قابل دریافت است.
همچنین نگارش جدید StyleCop for ReSharper را نیز میتوانید از آدرس زیر دریافت نمائید.
- جزئیات نحوه پیاده سازی یک Storage Provider برای ASP.NET Identity
- تشریح اینترفیس هایی که باید پیاده سازی شوند، و نحوه استفاده از آنها در ASP.NET Identity
- ایجاد یک دیتابیس MySQL روی Windows Azure
- نحوه استفاده از یک ابزار کلاینت (MySQL Workbench) برای مدیریت دیتابیس مذکور
- نحوه جایگزینی پیاده سازی سفارشی با نسخه پیش فرض در یک اپلیکیشن ASP.NET MVC
پیاده سازی یک Storage Provider سفارشی برای ASP.NET Identity
- <UserStore<TUser
- IdentityUser
- <RoleStore<TRole
- IdentityRole
Roles
در مخزن پیش فرض ASP.NET Identity EntityFramework کلاسهای بیشتری برای موجودیتها مشاهده میکنید.
- IdentityUserClaim
- IdentityUserLogin
- IdentityUserRole
public Task<IList<Claim>> GetClaimsAsync(IdentityUser user) { ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id); return Task.FromResult<IList<Claim>>(identity.Claims.ToList()); }
public class IdentityUser : IUser { public IdentityUser(){...} public IdentityUser(string userName) (){...} public string Id { get; set; } public string UserName { get; set; } public string PasswordHash { get; set; } public string SecurityStamp { get; set; } }
public class UserStore : IUserStore<IdentityUser>, IUserClaimStore<IdentityUser>, IUserLoginStore<IdentityUser>, IUserRoleStore<IdentityUser>, IUserPasswordStore<IdentityUser> { public UserStore(){...} public Task CreateAsync(IdentityUser user){...} public Task<IdentityUser> FindByIdAsync(string userId){...} ... }
public class IdentityRole : IRole { public IdentityRole(){...} public IdentityRole(string roleName) (){...} public string Id { get; set; } public string Name { get; set; } }
public class RoleStore : IRoleStore<IdentityRole> { public RoleStore(){...} public Task CreateAsync(IdentityRole role){...} public Task<IdentityRole> FindByIdAsync(string roleId){...} .... }
- MySQLDatabase: این کلاس اتصال دیتابیس MySql و کوئریها را کپسوله میکند. کلاسهای UserStore و RoleStore توسط نمونه ای از این کلاس وهله سازی میشوند.
- RoleTable: این کلاس جدول Roles و عملیات CRUD مربوط به آن را کپسوله میکند.
- UserClaimsTable: این کلاس جدول UserClaims و عملیات CRUD مربوط به آن را کپسوله میکند.
- UserLoginsTable: این کلاس جدول UserLogins و عملیات CRUD مربوط به آن را کپسوله میکند.
- UserRolesTable: این کلاس جدول UserRoles و عملیات CRUD مربوطه به آن را کپسوله میکند.
- UserTable: این کلاس جدول Users و عملیات CRUD مربوط به آن را کپسوله میکند.
ایجاد یک دیتابیس MySQL روی Windows Azure
در ویزارد Choose Add-on به سمت پایین اسکرول کنید و گزینه ClearDB MySQL Database را انتخاب کنید. سپس به مرحله بعد بروید.
4. راهکار Free بصورت پیش فرض انتخاب شده، همین گزینه را انتخاب کنید و نام دیتابیس را به IdentityMySQLDatabase تغییر دهید. نزدیکترین ناحیه (region) به خود را انتخاب کنید و به مرحله بعد بروید.
5. روی علامت checkmark کلیک کنید تا دیتابیس شما ایجاد شود. پس از آنکه دیتابیس شما ساخته شد میتوانید از قسمت ADD-ONS آن را مدیریت کنید.
6. همانطور که در تصویر بالا میبینید، میتوانید اطلاعات اتصال دیتابیس (connection info) را از پایین صفحه دریافت کنید.
7. اطلاعات اتصال را با کلیک کردن روی دکمه مجاور کپی کنید تا بعدا در اپلیکیشن MVC خود از آن استفاده کنیم.
ایجاد جداول ASP.NET Identity در یک دیتابیس MySQL
CREATE TABLE `IdentityMySQLDatabase`.`users` ( `Id` VARCHAR(45) NOT NULL, `UserName` VARCHAR(45) NULL, `PasswordHash` VARCHAR(100) NULL, `SecurityStamp` VARCHAR(45) NULL, PRIMARY KEY (`id`)); CREATE TABLE `IdentityMySQLDatabase`.`roles` ( `Id` VARCHAR(45) NOT NULL, `Name` VARCHAR(45) NULL, PRIMARY KEY (`Id`)); CREATE TABLE `IdentityMySQLDatabase`.`userclaims` ( `Id` INT NOT NULL AUTO_INCREMENT, `UserId` VARCHAR(45) NULL, `ClaimType` VARCHAR(100) NULL, `ClaimValue` VARCHAR(100) NULL, PRIMARY KEY (`Id`), FOREIGN KEY (`UserId`) REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade); CREATE TABLE `IdentityMySQLDatabase`.`userlogins` ( `UserId` VARCHAR(45) NOT NULL, `ProviderKey` VARCHAR(100) NULL, `LoginProvider` VARCHAR(100) NULL, FOREIGN KEY (`UserId`) REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade); CREATE TABLE `IdentityMySQLDatabase`.`userroles` ( `UserId` VARCHAR(45) NOT NULL, `RoleId` VARCHAR(45) NOT NULL, PRIMARY KEY (`UserId`, `RoleId`), FOREIGN KEY (`UserId`) REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade on update cascade, FOREIGN KEY (`RoleId`) REFERENCES `IdentityMySQLDatabase`.`roles` (`Id`) on delete cascade on update cascade);
ایجاد یک اپلیکیشن ASP.NET MVC و پیکربندی آن برای استفاده از MySQL Provider
6. در پنجره New ASP.NET Project قالب MVC را انتخاب کنید و تنظیمات پیش فرض را بپذیرید.
7. در پنجره Solution Explorer روی پروژه IdentityMySQLDemo کلیک راست کرده و Manage NuGet Packages را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "Identity.EntityFramework" را وارد کنید. در لیست نتایج این پکیج را انتخاب کرده و آن را حذف (Uninstall) کنید. پیغامی مبنی بر حذف وابستگیها باید دریافت کنید که مربوط به پکیج EntityFramework است، گزینه Yes را انتخاب کنید. از آنجا که کاری با پیاده سازی فرض نخواهیم داشت، این پکیجها را حذف میکنیم.
8. روی پروژه IdentityMySQLDemo کلیک راست کرده و Add, Reference, Solution, Projects را انتخاب کنید. در دیالوگ باز شده پروژه AspNet.Identity.MySQL را انتخاب کرده و OK کنید.
9. در پروژه IdentityMySQLDemo پوشه Models را پیدا کرده و کلاس IdentityModels.cs را حذف کنید.
10. در پروژه IdentityMySQLDemo تمام ارجاعات ";using Microsoft.AspNet.Identity.EntityFramework" را با ";using AspNet.Identity.MySQL" جایگزین کنید.
11. در پروژه IdentityMySQLDemo تمام ارجاعات به کلاس "ApplicationUser" را با "IdentityUser" جایگزین کنید.
12. کنترلر Account را باز کنید و متد سازنده آنرا مطابق لیست زیر تغییر دهید.
public AccountController() : this(new UserManager<IdentityUser>(new UserStore(new MySQLDatabase()))) { }
13. فایل web.config را باز کنید و رشته اتصال DefaultConnection را مطابق لیست زیر تغییر دهید.
<add name="DefaultConnection" connectionString="Database=IdentityMySQLDatabase;Data Source=<DataSource>;User Id=<UserID>;Password=<Password>" providerName="MySql.Data.MySqlClient" />
مقادیر <DataSource>, <UserId> و <Password> را با اطلاعات دیتابیس خود جایگزین کنید.
اجرای اپلیکیشن و اتصال به دیتابیس MySQL
5. در این مرحله کاربر جدید باید ایجاد شده و وارد سایت شود.
6. به ابزار MySQL Workbench بروید و محتوای جداول IdentityMySQLDatabase را بررسی کنید. جدول users را باز کنید و اطلاعات کاربر جدید را بررسی نمایید.
برای ساده نگاه داشتن این مقاله از بررسی تمام کدهای لازم خودداری شده، اما اگر مراحل را دنبال کنید و سورس کد نمونه را دریافت و بررسی کنید خواهید دید که پیاده سازی تامین کنندگان سفارشی برای ASP.NET Identity کار نسبتا ساده ای است.
معرفی پروژه فروشگاهی Iris Store
...... Uncaught TypeError: Cannot read property 'settings' of undefined
در کامنتها به راه حل اشاره شده است:
$('#username').on('shown', function() { var $innerForm = $(this).data('editable').input.$input.closest('form'); var $outerForm = $innerForm.parents('form').eq(0); $innerForm.data('validator', $outerForm.data('validator')); });
- partial view هم مانند یک view معمولی باید custom tools اش به razor generator تنظیم شود. بعد از آن کار کردن با آن معمولی و مانند قبل خواهد بود.
@BundleConfig.AddStyles("~/Content/css", "~/Content/bootstrap.min.css", "~/Content/Site.css") @BundleConfig.AddScripts("~/Scripts/js", "~/Scripts/jquery-1.10.2.min.js", "~/Scripts/bootstrap.min.js", "~/Scripts/modernizr-2.6.2.js", "~/Scripts/jquery.bootstrap-modal-confirm.js") @RenderSection("JavaScript", required: false)