اشتراکها
نظرات مطالب
معرفی Xamarin و مزیتهای استفاده از آن
حدود یک نرم افزار ساده 14 مگ هستش. بعد هم چه مشکلی با Linking هست؟
Linker اسمبلی هایی که توی اپلیکیشن استفاده نشده اند رو حذف میکنه پس اساسا مشکلی توی کار نرم افزار ایجاد نمیکنه
نظرات مطالب
رمزگذاری رویههای ذخیره شده در SQL Server
مطلب بسیار خوبی بود و باعث یادآوری بسیاری نکات برای بنده شد و
البته رمزگشایی SP ها هم کار چندان سختی نیست و نرم افزارهای زیادی جهت این امر میتوان در اینترنت پیدا نمود.
سایت سازنده نرم افزار
sql-decryptor
البته رمزگشایی SP ها هم کار چندان سختی نیست و نرم افزارهای زیادی جهت این امر میتوان در اینترنت پیدا نمود.
سایت سازنده نرم افزار
sql-decryptor
نظرات مطالب
فایلهای chm و مشکل فارسی - قسمت دوم
سلام
فکر کنم یک نگاهی هم به نرم افزار MGTEK Help Producer کنید بد نباشد.
از قابلیت این نرم افزار این است که به Word اضافه می شود فارسی را پشتیبانی می کند و نیز انواع فرمت خروجی دارد از جمله Chm
فکر کنم یک نگاهی هم به نرم افزار MGTEK Help Producer کنید بد نباشد.
از قابلیت این نرم افزار این است که به Word اضافه می شود فارسی را پشتیبانی می کند و نیز انواع فرمت خروجی دارد از جمله Chm
نظرات مطالب
مدل EAV چیست؟
سلام
سوال اولم اینه که چه نیازی به کد محصول در جدول مقدارها میباشد ؟ مگه کد محصول تو جدول ویژگیها نیست ؟! و از اون طرفی هر ویژگی یه کد منحصربه فرد داره (attribute_id) که این کد نیز در جدول ویژگیها وجود دارد . پس با Join کردن موجودیت و ویژگی توسط کد موجودیت و Join کردن نتیجه با جدول مقدار توسط کد ویژگی میتوان به تمام مقادیر یک موجودیت دست یافت
از اونجایی که فکر کردم سوالم مربوط به این مورد هست میپرسم . در نرم افزارهای حسابداری ما تفصیلیها رو گروه بندی میکنیم مثل گروه اشخاص ، کالا ، سهامداران ، بانک و ...
و هر کدوم از این گروهها دارای مقادیر خاصی هستند که در گروه دیگر وجود ندارد مثل کد فنی کالا و ...
حالا سوالم اینه که به نظر شما برای این مورد از این روش مشه استفاده کرد ؟
یعنی:
یک جدول برای تفصیلی ها
یک جدول برای ویژگیهای تفصیلی
یک جدول برای مقادیر ویژگی ها
یک جدول برای گروههای تفصیلی
یک جدول هم برای ارتباط گروه تفصیلی با خود تفصیل (جهت مشخص کردن تفصیلیهای موجود در یک گروه)
با تشکر
نظرات مطالب
هزینه استفاده از دات نت فریم ورک چقدر است؟
اینجوری که من این متن رو خوندم فکر نکنم منظورشون نادیده گرفتن زبانهای دیگر بود.آقا کوروش درباره منابع هم باید در نظر داشته باشی که خیلی ها نمیتونن از منابع انگلیسی بخوبی استفاده کنن.در ضمن من خودم برای مطالبی که مربوط به زبان جاوا بود به انگلیسی سرچ کردم ولی خب نتایج اصلا قابل مقایسه باجستجو در زبانهای دات نتی نبود.پس فقط استفاده از ویژوال استودیو در ایران رایج نیست.اینکه زبانهای دیگر به جایگاه اصلی خود نرسیدن فقط در ایران صادق نیست.اون هم بخاطر باگ ها و عدم راحتی کار با این نرم افزارها در مقایسه با دات نت هست. در ضمن من نسخه اکسپرس ویژوال استودیو رو با آی دی ای مثل نت بینز مقایسه کردم.باز هم به نظر من ویژوال استودیو سر تر هست.البته من در سطح خیلی حرفه ای از ویژوال استودیو استفاده نکردم ولی خب مشکلاتی در نت بینز وجود داره که هیچوقت در ویژوال استودیو دیده نشده.
الان یک لحظه قیمت ویژوال استودیو در سرچ کردم:نسخه استاندارد 178$-نسخه حرفه ای=600$-نسخه تیم سیستم=2000$-من که مطمعنا 178$ برای ویژوال استودیو میدادم.در لینک زیر هم میتونین تفاوتهای نسخه های متفاوت رو ببینید:
http://www.hallogram.com/vstudio/compare.html
بااحترام
الان یک لحظه قیمت ویژوال استودیو در سرچ کردم:نسخه استاندارد 178$-نسخه حرفه ای=600$-نسخه تیم سیستم=2000$-من که مطمعنا 178$ برای ویژوال استودیو میدادم.در لینک زیر هم میتونین تفاوتهای نسخه های متفاوت رو ببینید:
http://www.hallogram.com/vstudio/compare.html
بااحترام
نظرات مطالب
هزینه استفاده از دات نت فریم ورک چقدر است؟
اینجوری که من این متن رو خوندم فکر نکنم منظورشون نادیده گرفتن زبانهای دیگر بود.آقا کوروش درباره منابع هم باید در نظر داشته باشی که خیلی ها نمیتونن از منابع انگلیسی بخوبی استفاده کنن.در ضمن من خودم برای مطالبی که مربوط به زبان جاوا بود به انگلیسی سرچ کردم ولی خب نتایج اصلا قابل مقایسه باجستجو در زبانهای دات نتی نبود.پس فقط استفاده از ویژوال استودیو در ایران رایج نیست.اینکه زبانهای دیگر به جایگاه اصلی خود نرسیدن فقط در ایران صادق نیست.اون هم بخاطر باگ ها و عدم راحتی کار با این نرم افزارها در مقایسه با دات نت هست. در ضمن من نسخه اکسپرس ویژوال استودیو رو با آی دی ای مثل نت بینز مقایسه کردم.باز هم به نظر من ویژوال استودیو سر تر هست.البته من در سطح خیلی حرفه ای از ویژوال استودیو استفاده نکردم ولی خب مشکلاتی در نت بینز وجود داره که هیچوقت در ویژوال استودیو دیده نشده.
الان یک لحظه قیمت ویژوال استودیو در سرچ کردم:نسخه استاندارد 178$-نسخه حرفه ای=600$-نسخه تیم سیستم=2000$-من که مطمعنا 178$ برای ویژوال استودیو میدادم.در لینک زیر هم میتونین تفاوتهای نسخه های متفاوت رو ببینید:
http://www.hallogram.com/vstudio/compare.html
بااحترام
الان یک لحظه قیمت ویژوال استودیو در سرچ کردم:نسخه استاندارد 178$-نسخه حرفه ای=600$-نسخه تیم سیستم=2000$-من که مطمعنا 178$ برای ویژوال استودیو میدادم.در لینک زیر هم میتونین تفاوتهای نسخه های متفاوت رو ببینید:
http://www.hallogram.com/vstudio/compare.html
بااحترام
نظرات مطالب
معرفی JSON Web Token
خیلی ممنون از مطلب مفیدتون.
روش token base authentication روش بسیار خوبی برای جایگزین شدن با روشهای پیشین است، به خصوص در برنامههای تک صفحه ای. سوالی که برای بنده مطرح شد. بنده در پروژه ای token و اطلاعات تکمیلی کاربر را در Local Storage ذخیره میکنم. حال اینکه وقتی token به کاربر داده شد، اگر اطلاعات را کپی کنم و در مرورگر دیگری این اطلاعات را وارد کنم من را تایید صلاحیت میکنه. در صورتی که این نکته میتونه خیلی خطر ساز باشه، چون token که به کاربر اعطا میشه کلید refresh token را هم به همراه خودش داره و در نهایت کاربر تا هر زمانی که بخواد میتونه لاگین شده باقی بمونه. ممنون میشم اگر راه حلی پیشنهاد بدید به بنده.
یک سوالی در مورد عملکرد برنامه تلگرام هم دیده بودم، در تلگرام هم فکر میکنم از token استفاده میشه. اما خیلی جالبه که شما میتونید sessionهای فعالتون را ببینید که از چند دستگاه لاگین شده اید. آیا اگر اطلاعات token را در دیتابیس نگه دارم میتونم مثل تلگرام این وضعیت را هم کنترل کنم؟ مثلا آیا میتونم از سمت سرور یک token که تایید صلاحیت هست را expire کنم؟
تست واحد چیست؟
تست واحد ابزاری است برای مشاهده چگونگی عملکرد یک متد که توسط خود برنامه نویس نوشته میشود. به این صورت که پارامترهای ورودی، برای یک متد ساخته شده و آن متد فراخوانی و خروجی متد بسته به حالت مطلوب بررسی میشود. چنانچه خروجی مورد نظر مطلوب باشد تست واحد با موفقیت انجام میشود.
اهمیت انجام تست واحد چیست؟
درستی یک متد، مهمترین مسئله برای بررسی است و بارها مشاهده شده، استثناهایی رخ میدهند که توان تولید را به دلیل فرسایش تکراری رخداد میکاهند. نوشتن تست واحد منجر به این میشود چناچه بعدها تغییری در بیزنس متد ایجاد شود و ورودی و خروجیها تغییر نکند، صحت این تغییر بیزنس، توسط تست بررسی مشود؛ حتی میتوان این تستها را در build پروژه قرار داد و در ابتدای اجرای یک Solution تمامی تستها اجرا و درستی بخش به بخش اعضا چک شوند.
شروع تست واحد:
یک پروژهی ساده را داریم برای تعریف حسابهای بانکی شامل نام مشتری، مبلغ سپرده، وضعیت و 3 متد واریز به حساب و برداشت از حساب و تغییر وضعیت حساب که به صورت زیر است:
تابع اصلی نیز به صورت زیر است:
به Solution، یک پروژه از نوع تست واحد اضافه میکنیم.
در این پروژه ابتدا Reference ایی از پروژهای که مورد تست هست میگیریم. سپس در کلاس تست مربوطه شروع به نوشتن متدی برای انواع تست متدهای پروژه اصلی میکنیم.
توجه داشته باشید که Data Annotationهای بالای کلاس تست و متدهای تست، در تعیین نوع نگاه کامپایلر به این بلوکها موثر است و باید این مسئله به درستی رعایت شود. همچنین در صورت نیاز میتوان از کلاس StartUp برای شروع تست استفاده کرد که عمدتا برای تعریف آن از نام ClassInit استفاده میشود و در بالای آن از [ClassInitialize] استفاده میشود.
در Library تست واحد میتوان به دو صورت چگونگی صحت عملکرد یک تست را بررسی کرد: با استفاده از Assert و با استفاده از ExpectedException، که در زیر به هر دو صورت آن میپردازیم.
همانطور که مشاهده میشود ابتدا در قسمت Arrange، خوراک تست آماده میشود. سپس در قسمت Act، فعالیتهایی که زیر ذره بین تست هستند صورت میپذیرند و سپس در قسمت Assert درستی مقادیر با مقادیر مورد انتظار ما مطابقت داده میشوند.
برای بررسی خطاهای تعیین شده هنگام نوشتن یک متد نیز میتوان به صورت زیر عمل کرد:
همانطور که مشخص است نام متد تست باید کامل و شفاف به صورتی انتخاب شود که بیانگر رخداد درون متد تست باشد. در این متدها Assert مورد انتظار با DataAnnotation که پیش از این توضیح داده شد کنترل گردیده است و بدین صورت کار میکند که وقتی Act انجام میشود، متد بررسی میکند تا آن Assert رخ بدهد.
استفاده از Library Moq در تست واحد
ابتدا باید به این توضیح بپردازیم که این کتابخانه چه کاری میکند و چه امکانی را برای انجام تست واحد فراهم میکند.
در پروژههای بزرگ و زمانی که ارتباطات بین لایهای زیادی موجود است و اصول SOLID رعایت میشود، شما در یک لایه برای ارایه فعالیتها و خدمات متدهایتان با Interfaceهای لایههای دیگر در ارتباط هستید و برای نوشتن تست واحد متدهایتان، مشکلی بزرگ دارید که نمیتوانید به این لایهها دسترسی داشته باشید و ماهیت تست واحد را زیر سوال میبرید. Library Moq این امکان را به شما میدهد که از این Interfaceها یک تصویر مجازی بسازید و همانند Snap Shot با آن کار کنید؛ بدون اینکه در لایههای دیگر بروید و ماهیت تست واحد را زیر سوال ببرید.
برای استفاده از متدهایی که در این Interfaceها موجود است شما باید یک شیء از نوع Mock<> از آنها بسازید و سپس با استفاده از متد Setup به صورت مجازی متد مورد نظر را فراخوانی کنید و مقدار بازگشتی مورد انتظار را با Return معرفی کنید، سپس از آن استفاده کنید.
همچنین برای دسترسی به خود شیء از Property ایی با نام Objet از موجودیت mock شده استفاده میکنیم.
برای شناسایی بهتر اینکه از چه اینترفیس هایی باید Mock<> بسازید، میتوانید به متد سازنده کلاسی که معرف لایه ایست که برای آن تست واحد مینویسید، مراجعه کنید.
نحوه اجرای یک تست واحد با استفاده از Moq با توجه به توضیحات بالا به صورت زیر است:
پروژه مورد بررسی لایه Service برای تعریف واحدهای سازمانی است که با الگوریتم DDD و CQRS پیاده سازی شده است.
ابتدا به Constructor خود لایه سرویس نگاه میکنیم تا بتوانید شناسایی کنید از چه Interface هایی باید Mock<> کنیم.
مشاهده میکنید که 4 Interface استفاده شده و در متد سازنده نیز مقدار دهی شده اند. پس 4 Mock نیاز داریم. در پروژه تست به صورت زیر و در ClassInitialize عمل میکنیم.
از خود لایه سرویس با نام OrganizationService یک آبجکت میگیریم و 4 واسط دیگر به صورت Mock شده تعریف میشوند. همچنین در کلاس بارگذار از همان نوع مقدار دهی میگردند تا در اجرای تمامی متدهای تست، در دست کامپایلر باشند. همچنین برای new کردن خود سرویس از mock.obectها که حاوی مقدار اصلی است استفاده میکنیم.
خود متد اصلی به صورت زیر است:
متدهای تست این متد نیز به صورت زیر هستند:
همانطور که مشاهده میشود ابتدا یک Guid به عنوان آی دی نوع واحد سازمانی گرفته میشود و همان آی دی برای تعریف کامند حذف به آن ارسال میشود. سپس یک نوع واحد سازمانی دلخواه تستی ساخته میشود و همچنین یک لیست خالی از واحدهای سازمانی که برای چک شدن توسط خود متد Handle استفاده شدهاست ساخته میشود. در اینجا این متد خالی است تا شرط غلط شود و عمل حذف به درستی صورت پذیرد.
برای اعمالی که در Handle انجام میشود و متدهایی که از Interfaceها صدا زده میشوند Setup میکنیم و آنهایی را که Return دارند به object هایی که مورد انتظار خودمان هست نسبت میدهیم.
در Setup اول میگوییم که آن Guid مربوط به "خوشه" است. در Setup بعدی برای عمل Remove کدی مینویسیم و چون عمل حذف Return ندارد میتواند، این خط به کل حذف شود! به طور کلی Setup هایی که Return ندارند میتوانند حذف شوند.
در Setup بعدی از Interface دیگر متد FindBy که قرار است چک کند این نوع واحد سازمانی برای تعریف واحد سازمانی استفاده شده است، در Return به آن یک لیست خالی اختصاص میدهیم تا نشان دهیم لیست خالی برگشته است.
عملیات Act را وارد Try میکنیم تا اگر به هر دلیل انجام نشد، Assert ما باشد.
دو حالت رخداد استثناء که در متد اصلی تست شده است در دو متد تست به طور جداگانه تست گردیده است:
متد DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitTypeId_NotExist همانطور که از نامش معلوم است بررسی میکند که نوع واحد سازمانی که ID آن برای حذف ارسال میشود در Database وجود دارد و اگر نباشد Exception مطلوب ما باید داده شود.
در متد DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitType_Exist_but_UsedForDefineOrganizationUnit بررسی میشود که از این نوع واحد سازمانی برای تعریف واحد سازمانی استفاده شده است یا نه و صحت این مورد با الگوی Specification صورت گرفته است. استثنای مطلوب ما Assert و شرط درستی این متد تست، میباشد.
تست واحد ابزاری است برای مشاهده چگونگی عملکرد یک متد که توسط خود برنامه نویس نوشته میشود. به این صورت که پارامترهای ورودی، برای یک متد ساخته شده و آن متد فراخوانی و خروجی متد بسته به حالت مطلوب بررسی میشود. چنانچه خروجی مورد نظر مطلوب باشد تست واحد با موفقیت انجام میشود.
اهمیت انجام تست واحد چیست؟
درستی یک متد، مهمترین مسئله برای بررسی است و بارها مشاهده شده، استثناهایی رخ میدهند که توان تولید را به دلیل فرسایش تکراری رخداد میکاهند. نوشتن تست واحد منجر به این میشود چناچه بعدها تغییری در بیزنس متد ایجاد شود و ورودی و خروجیها تغییر نکند، صحت این تغییر بیزنس، توسط تست بررسی مشود؛ حتی میتوان این تستها را در build پروژه قرار داد و در ابتدای اجرای یک Solution تمامی تستها اجرا و درستی بخش به بخش اعضا چک شوند.
شروع تست واحد:
یک پروژهی ساده را داریم برای تعریف حسابهای بانکی شامل نام مشتری، مبلغ سپرده، وضعیت و 3 متد واریز به حساب و برداشت از حساب و تغییر وضعیت حساب که به صورت زیر است:
/// <summary> /// حساب بانکی /// </summary> public class Account { /// <summary> /// مشتری /// </summary> public string Customer { get; set; } /// <summary> /// موجودی حساب /// </summary> public float Balance { get; set; } /// <summary> /// وضعیت /// </summary> public bool Active { get; set; } public Account(string customer, float balance) { Customer = customer; Balance = balance; Active = true; } /// <summary> /// افزایش موجودی / واریز به حساب /// </summary> /// <param name="amount">مبلغ واریز</param> public void Credit(float amount) { if (!Active) throw new Exception("این حساب مسدود است."); if (amount < 0) throw new ArgumentOutOfRangeException("amount"); Balance += amount; } /// <summary> /// کاهش موجودی / برداشت از حساب /// </summary> /// <param name="amount">مبلغ برداشت</param> public void Debit(float amount) { if (!Active) throw new Exception("این حساب مسدود است."); if (amount < 0) throw new ArgumentOutOfRangeException("amount"); if (Balance < amount) throw new ArgumentOutOfRangeException("amount"); Balance -= amount; } /// <summary> /// انسداد / رفع انسداد /// </summary> public void ChangeStateAccount() { Active = !Active; } }
class Program { static void Main(string[] args) { var account = new Account("Ali",1000); account.Credit(4000); account.Debit(2000); Console.WriteLine("Current balance is ${0}", account.Balance); Console.ReadKey(); } }
در این پروژه ابتدا Reference ایی از پروژهای که مورد تست هست میگیریم. سپس در کلاس تست مربوطه شروع به نوشتن متدی برای انواع تست متدهای پروژه اصلی میکنیم.
توجه داشته باشید که Data Annotationهای بالای کلاس تست و متدهای تست، در تعیین نوع نگاه کامپایلر به این بلوکها موثر است و باید این مسئله به درستی رعایت شود. همچنین در صورت نیاز میتوان از کلاس StartUp برای شروع تست استفاده کرد که عمدتا برای تعریف آن از نام ClassInit استفاده میشود و در بالای آن از [ClassInitialize] استفاده میشود.
در Library تست واحد میتوان به دو صورت چگونگی صحت عملکرد یک تست را بررسی کرد: با استفاده از Assert و با استفاده از ExpectedException، که در زیر به هر دو صورت آن میپردازیم.
[TestClass] public class UnitTest { /// <summary> /// تعریف حساب جدید و بررسی تمامی فرآیندهای معمول روی حساب /// </summary> [TestMethod] public void Create_New_Account_And_Check_The_Process() { //Arrange var account = new Account("Hassan", 4000); var account2 = new Account("Ali", 10000); //Act account.Credit(5000); account2.Debit(3000); account.ChangeStateAccount(); account2.Active = false; account2.ChangeStateAccount(); //Assert Assert.AreEqual(account.Balance,9000); Assert.AreEqual(account2.Balance,7000); Assert.IsTrue(account2.Active); Assert.AreEqual(account.Active,false); }
برای بررسی خطاهای تعیین شده هنگام نوشتن یک متد نیز میتوان به صورت زیر عمل کرد:
/// <summary> /// زمانی که کاربر بخواهد به یک حساب مسدود واریز کند باید جلوی آن گرفته شود. /// </summary> [TestMethod] [ExpectedException(typeof (Exception))] public void When_Deactive_Account_Wants_To_add_Credit_Should_Throw_Exception() { //Arrange var account = new Account("Hassan", 4000) {Active = false}; //Act account.Credit(4000); //Assert //Assert is handled with ExpectedException } [TestMethod] [ExpectedException(typeof (ArgumentOutOfRangeException))] public void When_Customer_Wants_To_Debit_More_Than_Balance_Should_Throw_ArgumentOutOfRangeException() { //Arrange var account = new Account("Hassan", 4000); //Act account.Debit(5000); //Assert //Assert is handled with ArgumentOutOfRangeException }
استفاده از Library Moq در تست واحد
ابتدا باید به این توضیح بپردازیم که این کتابخانه چه کاری میکند و چه امکانی را برای انجام تست واحد فراهم میکند.
در پروژههای بزرگ و زمانی که ارتباطات بین لایهای زیادی موجود است و اصول SOLID رعایت میشود، شما در یک لایه برای ارایه فعالیتها و خدمات متدهایتان با Interfaceهای لایههای دیگر در ارتباط هستید و برای نوشتن تست واحد متدهایتان، مشکلی بزرگ دارید که نمیتوانید به این لایهها دسترسی داشته باشید و ماهیت تست واحد را زیر سوال میبرید. Library Moq این امکان را به شما میدهد که از این Interfaceها یک تصویر مجازی بسازید و همانند Snap Shot با آن کار کنید؛ بدون اینکه در لایههای دیگر بروید و ماهیت تست واحد را زیر سوال ببرید.
برای استفاده از متدهایی که در این Interfaceها موجود است شما باید یک شیء از نوع Mock<> از آنها بسازید و سپس با استفاده از متد Setup به صورت مجازی متد مورد نظر را فراخوانی کنید و مقدار بازگشتی مورد انتظار را با Return معرفی کنید، سپس از آن استفاده کنید.
همچنین برای دسترسی به خود شیء از Property ایی با نام Objet از موجودیت mock شده استفاده میکنیم.
برای شناسایی بهتر اینکه از چه اینترفیس هایی باید Mock<> بسازید، میتوانید به متد سازنده کلاسی که معرف لایه ایست که برای آن تست واحد مینویسید، مراجعه کنید.
نحوه اجرای یک تست واحد با استفاده از Moq با توجه به توضیحات بالا به صورت زیر است:
پروژه مورد بررسی لایه Service برای تعریف واحدهای سازمانی است که با الگوریتم DDD و CQRS پیاده سازی شده است.
ابتدا به Constructor خود لایه سرویس نگاه میکنیم تا بتوانید شناسایی کنید از چه Interface هایی باید Mock<> کنیم.
public class OrganizationalService : ICommandHandler<CreateUnitTypeCommand>, ICommandHandler<DeleteUnitTypeCommand>, { private readonly IUnitOfWork _unitOfWork; private readonly IUnitTypeRepository _unitTypeRepository; private readonly IOrganizationUnitRepository _organizationUnitRepository; private readonly IOrganizationUnitDomainService _organizationUnitDomainService; public OrganizationalService(IUnitOfWork unitOfWork, IUnitTypeRepository unitTypeRepository, IOrganizationUnitRepository organizationUnitRepository, IOrganizationUnitDomainService organizationUnitDomainService) { _unitOfWork = unitOfWork; _unitTypeRepository = unitTypeRepository; _organizationUnitRepository = organizationUnitRepository; _organizationUnitDomainService = organizationUnitDomainService; }
[TestClass] public class OrganizationServiceTest { private static OrganizationalService _organizationalService; private static Mock<IUnitTypeRepository> _mockUnitTypeRepository; private static Mock<IUnitOfWork> _mockUnitOfWork; private static Mock<IOrganizationUnitRepository> _mockOrganizationUnitRepository; private static Mock<IOrganizationUnitDomainService> _mockOrganizationUnitDomainService; [ClassInitialize] public static void ClassInit(TestContext context) { TestBootstrapper.ConfigureDependencies(); _mockUnitOfWork = new Mock<IUnitOfWork>(); _mockUnitTypeRepository = new Mock<IUnitTypeRepository>(); _mockOrganizationUnitRepository = new Mock<IOrganizationUnitRepository>(); _mockOrganizationUnitDomainService=new Mock<IOrganizationUnitDomainService>(); _organizationalService = new OrganizationalService(_mockUnitOfWork.Object, _mockUnitTypeRepository.Object, _mockOrganizationUnitRepository.Object,_mockOrganizationUnitDomainService.Object); }
خود متد اصلی به صورت زیر است:
/// <summary> /// یک نوع واحد سازمانی را حذف مینماید /// </summary> /// <param name="command"></param> public void Handle(DeleteUnitTypeCommand command) { var unitType = _unitTypeRepository.FindBy(command.UnitTypeId); if (unitType == null) throw new DeleteEntityNotFoundException(); ICanDeleteUnitTypeSpecification canDeleteUnitType = new CanDeleteUnitTypeSpecification(_organizationUnitRepository); if (canDeleteUnitType.IsSatisfiedBy(unitType)) throw new UnitTypeIsUnderUsingException(unitType.Title); _unitTypeRepository.Remove(unitType); }
/// <summary> /// کامند حذف نوع واحد سازمانی باید به درستی حذف کند. /// </summary> [TestMethod] public void DeleteUnitTypeCommand_Should_Delete_UnitType() { //Arrange var unitTypeId=new Guid(); var deleteUnitTypeCommand = new DeleteUnitTypeCommand { UnitTypeId = unitTypeId }; var unitType = new UnitType("خوشه"); var org = new List<OrganizationUnit>(); _mockUnitTypeRepository.Setup(d => d.FindBy(deleteUnitTypeCommand.UnitTypeId)).Returns(unitType); _mockUnitTypeRepository.Setup(x => x.Remove(unitType)); _mockOrganizationUnitRepository.Setup(z => z.FindBy(unitType)).Returns(org); try { //Act _organizationalService.Handle(deleteUnitTypeCommand); } catch (Exception ex) { //Assert Assert.Fail(ex.Message); } }
برای اعمالی که در Handle انجام میشود و متدهایی که از Interfaceها صدا زده میشوند Setup میکنیم و آنهایی را که Return دارند به object هایی که مورد انتظار خودمان هست نسبت میدهیم.
در Setup اول میگوییم که آن Guid مربوط به "خوشه" است. در Setup بعدی برای عمل Remove کدی مینویسیم و چون عمل حذف Return ندارد میتواند، این خط به کل حذف شود! به طور کلی Setup هایی که Return ندارند میتوانند حذف شوند.
در Setup بعدی از Interface دیگر متد FindBy که قرار است چک کند این نوع واحد سازمانی برای تعریف واحد سازمانی استفاده شده است، در Return به آن یک لیست خالی اختصاص میدهیم تا نشان دهیم لیست خالی برگشته است.
عملیات Act را وارد Try میکنیم تا اگر به هر دلیل انجام نشد، Assert ما باشد.
دو حالت رخداد استثناء که در متد اصلی تست شده است در دو متد تست به طور جداگانه تست گردیده است:
/// <summary> /// کامند حذف یک نوع واحد سازمانی باید پیش از حذف بررسی کند که این شناسه داده شده برای حذف موجود باشد. /// </summary> [TestMethod] [ExpectedException(typeof(DeleteEntityNotFoundException))] public void DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitTypeId_NotExist() { //Arrange var unitTypeId = new Guid(); var deleteUnitTypeCommand = new DeleteUnitTypeCommand(); var unitType = new UnitType("خوشه"); var org = new List<OrganizationUnit>(); _mockUnitTypeRepository.Setup(d => d.FindBy(unitTypeId)).Returns(unitType); _mockUnitTypeRepository.Setup(x => x.Remove(unitType)); _mockOrganizationUnitRepository.Setup(z => z.FindBy(unitType)).Returns(org); //Act _organizationalService.Handle(deleteUnitTypeCommand); } /// <summary> /// کامند حذف یک نوع واحد سازمانی نباید اجرا شود وقتی که نوع واحد برای تعریف واحدهای سازمان استفاده شده است. /// </summary> [TestMethod] [ExpectedException(typeof(UnitTypeIsUnderUsingException))] public void DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitType_Exist_but_UsedForDefineOrganizationUnit() { //Arrange var unitTypeId = new Guid(); var deleteUnitTypeCommand = new DeleteUnitTypeCommand { UnitTypeId = unitTypeId }; var unitType = new UnitType("خوشه"); var org = new List<OrganizationUnit>() { new OrganizationUnit("مدیریت یک", unitType, null), new OrganizationUnit("مدیریت دو", unitType, null) }; _mockUnitTypeRepository.Setup(d => d.FindBy(deleteUnitTypeCommand.UnitTypeId)).Returns(unitType); _mockUnitTypeRepository.Setup(x => x.Remove(unitType)); _mockOrganizationUnitRepository.Setup(z => z.FindBy(unitType)).Returns(org); //Act _organizationalService.Handle(deleteUnitTypeCommand); }
در متد DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitType_Exist_but_UsedForDefineOrganizationUnit بررسی میشود که از این نوع واحد سازمانی برای تعریف واحد سازمانی استفاده شده است یا نه و صحت این مورد با الگوی Specification صورت گرفته است. استثنای مطلوب ما Assert و شرط درستی این متد تست، میباشد.
جهت مقاصد امنیتی، اعتبارسنجی کاربران و یا تحت نظر قرار دادن مسیرها، نیاز است بتوان بررسی کرد که آیا پیمایش یک مسیر، مجاز است یا خیر؟ برای پیاده سازی یک چنین ویژگیهایی در Angular، مفهوم Route Guards یا محافظهای مسیرها پیش بینی شدهاست که شامل چندین نوع محافظ میشوند:
- canActivate : جهت محافظت دسترسی به یک مسیر
- canActivateChild: برای محافظت دسترسی به یک Child Route
- canDeactivate : برای جلوگیری کردن از ترک مسیر جاری و هدایت به مسیری دیگر (برای مثال جهت نمایش پیام «هنوز اطلاع تغییر یافته را ذخیره نکردهاید»)
- canLoad : برای جلوگیری از مسیریابی غیرهمزمان (async routing) که در قسمت بعدی بررسی خواهد شد
- resolve: برای پیش واکشی اطلاعات، پیش از نمایش مسیر (که آنرا در قسمت چهارم این سری بررسی کردیم)
لزوم استفادهی از محافظهای مسیرها
گاهی از اوقات میخواهیم دسترسی به یک مسیر را محدود به کاربران وارد شدهی به سیستم کنیم و یا مسیرهایی را داشته باشیم که تنها توسط گروه خاصی از کاربران قابل دسترسی باشند. همچنین در بسیاری از اوقات نیاز است به کاربران اخطارهایی را پیش از ترک یک مسیر نمایش دهیم. برای مثال پیش از ترک صفحهی ویرایش اطلاعاتی که دارای اطلاعات ذخیره نشدهاست، بهتر است پیامی را جهت یادآوری این مساله نمایش دهیم. برای پیاده سازی هر کدام از این قابلیتها از یک محافظ مسیر ویژه استفاده میشود.
ترتیب اجرای محافظهای مسیرها
مسیریاب سیستم، ابتدا محافظ canDeactivate را اجرا میکند تا مشخص شود که آیا کاربر میتواند مسیر جاری را ترک کند یا خیر؟ سپس اگر مسیریابی تعریف شده غیرهمزمان باشد، محافظ canLoad اجرا میشود. پس از آن محافظ canActivateChild بررسی میشود. در ادامه محافظ canActivate اجرا میگردد. در پایان کار بررسی محافظهای موجود، کار بررسی محافظ resolve، جهت پیش واکشی اطلاعات مسیر درخواستی، انجام خواهد شد.
در اینجا اگر یکی از محافظها مقدار false را برگرداند، پردازش مابقی آنها لغو خواهد شد و کار هدایت کاربر به مسیر درخواستی، خاتمه مییابد.
مراحل ساخت و اعمال یک محافظ مسیر
ساخت و اعمال یک محافظ مسیر شامل سه مرحله است:
الف) یک محافظ مسیر عموما به صورت یک سرویس جدید پیاده سازی میشود:
در اینجا برای اینکه این سرویس به صورت یک محافظ مسیر عمل کند، نیاز است نوع محافظ مدنظر را نیز پیاده سازی نماید؛ مانند CanActivate در اینجا. پس از آن باید متد مرتبط با این اینترفیس که در اینجا canActivate است، پیاده سازی شود. اگر این متد false را برگرداند، سبب لغو هدایت کاربر به آن مسیر خواهد شد و این متد میتواند خروجی پیچیدهتری مانند یک Observable را نیز داشته باشد. اگر یک چنین نوع خروجی درنظر گرفته شود، فراخوان آن، تا پایان کار این Observable صبر خواهد کرد.
ب) از آنجائیکه محافظها، سرویس هستند، نیاز است تعریف کلاس آنها را در قسمت providers ماژول مرتبط نیز ذکر کنیم تا در برنامه قابل دسترسی شوند. باید دقت داشت که برخلاف سایر سرویسها، امکان تعریف محافظها صرفا در سطح یک ماژول مسیر است و نه در سطح یک کامپوننت. به این ترتیب مسیریاب میتواند به آن، در طی هدایت کاربر به مسیر درخواستی، دسترسی پیدا کند.
ج) پس از آن برای فعالسازی یک محافظ مسیر، آنرا به عنوان یک خاصیت جدید، به تنظیمات مسیریابی اضافه خواهیم کرد. نام این خاصیت دقیقا مساوی با نوع محافظی است که تعریف شدهاست. برای مثال اگر محافظ تعریف شده از نوع CanActivate است، نام خاصیتی که ذکر خواهد شد، canActivate میباشد. مقدار آن نیز میتواند آرایهای از سرویسهایی از این نوع باشد.
امکان به اشتراک گذاشتن یک محافظ بین چندین مسیر نیز وجود دارد. فرض کنید میخواهیم تمام مسیرهای مربوط به محصولات را محافظت کنیم. در این حالت میتوان محافظ را به تک تک Child routes موجود اعمال کرد و یا میتوان محافظ را به والد آنها نیز اعمال کنیم تا به صورت خودکار سبب محافظت از فرزندان آن نیز شویم.
یک مثال: ساخت محافظ canActivate
جهت بررسی شرط یا شرایطی پیش از فعال سازی یک مسیر درخواستی، از محافظهایی از نوع canActivate میتوان استفاده کرد. این نوع محافظها عموما جهت اعتبارسنجی کاربران و محدود سازی دسترسی آنها به قسمتهای مختلف برنامه استفاده میشوند. این نوع محافظها حتی با تغییر پارامترهای مسیریابی نیز فعال شده و بررسی میشوند.
در ادامهی مثال این سری میخواهیم کاربران را پیش از دسترسی به قسمتهای مختلف مرتبط با محصولات، وادار به لاگین کنیم. برای این منظور دستور ذیل را اجرا کنید:
به این ترتیب تغییرات ذیل در ماژول کاربران رخ خواهند داد:
در اینجا قالب ابتدایی کلاس سرویس AuthGuard ایجاد میشود (در فایل auth.guard.ts) و همچنین اگر به سطر آخر آن دقت کنید، این سرویس را به قسمت providers ماژول کاربران (در فایل user.module.ts) نیز افزودهاست.
در ادامه کدهای این محافظ را به صورت ذیل تکمیل کنید:
خاصیت redirectUrl نیز به کلاس سرویسAuthService ، جهت به اشتراک گذاری اطلاعات، اضافه شدهاست:
توضیحات:
این سرویس چون از نوع CanActivate است، این اینترفیس را پیاده سازی کردهاست و همچنین متد canActivate آنرا نیز به همراه دارد:
در اینجا از ActivatedRouteSnapshot میتوان اطلاعات مسیرجاری، مانند پارامترهای آنرا بدست آورد. پارامتر RouterStateSnapshot نیز وضعیت مسیریابی را بازگشت میدهد. برای مثال state.url، حاوی آدرس کامل مسیر درخواستی به صورت یک رشته است که از آن در اینجا جهت حفظ و به اشتراک گذاری مسیر اولیهی درخواستی استفاده شدهاست. خاصیت route.url حاوی آرایهای از URL segments است.
یک نکته: هرچند در اینجا میتوان به پارامتر id مسیر، مانند route.params['id'] در صورت نیاز دسترسی یافت، اما امکان دسترسی به اطلاعات از پیش واکشی شده مانند route.data['product'] وجود ندارد. علت آنرا نیز در قسمت «ترتیب اجرای محافظهای مسیرها» ابتدای بحث جاری، بررسی کردیم: محافظ resolve در انتهای کار پردازش تمام محافظهای موجود فراخوانی میشود.
در متد canActivate میخواهیم بررسی کنیم که آیا کاربر، لاگین کردهاست یا خیر؟ اگر بله، تنها کافی است true را بازگشت دهیم تا کار این محافظ پایان یابد. در غیراینصورت false را بازگشت داده و همچنین سبب هدایت کاربر به صفحهی لاگین میشویم.
به همین منظور سرویس AuthService را به سازندهی این کلاس تزریق کردهایم تا بتوانیم به متد isLoggedIn آن دسترسی پیدا کنیم (این سرویس را در قسمت دوم این سری تکمیل کردیم).
این متد نیز به صورت ذیل تعریف شدهاست:
در اینجا استفادهی از ! سبب بازگشت true، در صورت نال نبودن شیء کاربر جاری وارد شدهی به سیستم میشود.
در ادامه برای استفادهی از این محافظ مسیر، به فایل src\app\product\product-routing.module.ts مراجعه کرده و آنرا به نحو ذیل اعمال خواهیم کرد:
در قسمت ششم، کار گروه بندی مسیرها را انجام دادیم. اکنون در اینجا نمونهای از استفادهی از آنرا مشاهده میکنید. بجای اینکه AuthGuard را به تک تک مسیرهای فرزند تعریف شدهی محصولات، اعمال کنیم، آنرا به والد این مسیر اعمال کردهایم تا به صورت خودکار به تمام فرزندان آن نیز اعمال شود.
اکنون برنامه را با دستور ng s -o ساخته و اجرا کنید. سپس بر روی لینک لیست محصولات و یا افزودن یک محصول جدید کلیک کنید. بلافاصله صفحهی لاگین را مشاهده خواهید کرد.
به خاطر سپاری و بازیابی مسیر درخواستی کاربر پس از لاگین
در اینجا اگر کاربر بر روی لینک افزودن یک محصول جدید کلیک کند، صفحهی لاگین را مشاهده خواهد کرد. اما پس از لاگین، همواره به مسیر لیست محصولات هدایت میشود و در این حالت مسیر درخواستی اولیه فراموش خواهد شد. برای رفع این مشکل نیاز است آدرس درخواستی کاربر را نیز ذخیره و بازیابی کرد. به همین جهت خاصیت this.authService.redirectUrl = url را در متد checkLoggedIn محافظ تعریف شده مقدار دهی کردیم. در اینجا از سرویس Auth، برای به اشتراک گذاری اطلاعات با محافظهای مسیر استفاده کردهایم. طول عمر یک سرویس، singleton است. بنابراین تنها یک وهله از آن در طول عمر برنامه وجود خواهد داشت. به این ترتیب با ذخیرهی اطلاعاتی در آن، این اطلاعات در تمام برنامه قابل دسترسی خواهد شد.
با توجه به این نکته، اکنون به فایل src\app\user\login\login.component.ts مراجعه کرده و قسمت this.router.navigate آنرا به صورت ذیل بهبود خواهیم بخشید:
در اینجا بررسی میشود که آیا پیشتر خاصیت redirectUrl پس از لاگین مقدار دهی شدهاست یا خیر؟ اگر بله، از متد navigateByUrl جهت هدایت به آن مسیر استفاده خواهد شد.
در ادامه برای آزمایش آن، پس از اجرای برنامه، صفحهی افزودن یک محصول جدید را درخواست دهید. سپس لاگین کنید. اکنون مشاهده خواهید کرد که برنامه مسیر درخواستی پیش از لاگین را به خاطر سپردهاست.
بررسی محافظ canActivateChild
این محافظ نیز شبیه به محافظ canActivate است؛ با این تفاوت که تنها زمانی فعالسازی خواهد شد که فرزند یک مسیر قرار است نمایش داده شود و نه خود مسیر اصلی.
محافظ canActivateChild با تغییر قسمت child یک مسیر فعالسازی میشود؛ حتی اگر این تغییر در حد تغییر پارامترهای آن مسیر باشد. اما باید درنظر داشت که اگر تنها قسمت child یک مسیر تغییر کند، دیگر محافظ canActivate مجددا اجرا نخواهد شد.
یک مثال: اگر کاربر در حال مشاهدهی صفحهی لیست محصولات باشد و بر روی لینک مشاهدهی یک محصول کلیک کند، تنها قسمت child مسیر تغییر میکند. در این حالت canActivate مسیر اصلی دیگر اجرا نخواهد شد؛ اما تمام محافظهای canActivateChild مرتبط مجددا اجرا خواهند شد.
بررسی محافظ canDeactivate
محافظ canDeactivate پیش از ترک یک مسیر، فعالسازی و بررسی میشود. عموما از آن جهت بررسی وضعیت اطلاعات ذخیره نشده و اطلاع رسانی به کاربر، پیش از ترک مسیر جاری استفاده استفاده میگردد. این محافظ با هر تغییری در آدرس جاری مسیر، بررسی میشود. بدیهی است این تغییر صرفا درون یک برنامهی Angular معنا پیدا میکند و نه هدایت به سایتی دیگر.
در حال حاضر در مثال جاری این سری، اگر کاربر، تغییری را در صفحهی ویرایش اطلاعات ایجاد کند و بدون کلیک بر روی دکمهی Save به صفحهی دیگری مراجعه کند، این اطلاعات تغییر یافته، از دست خواهند رفت. برای رفع این مشکل میتوان محافظ canDeactivate ایی را برای آن طراحی کرد. به همین جهت دستور ذیل را اجرا کنید:
تا سبب انجام تغییرات ذیل در ماژول محصولات شود:
در اینجا علاوه بر ایجاد قالب ابتدایی محافظ ProductEdit، سبب به روز رسانی قسمت providers ماژول محصولات نیز شدهاست.
امضای ابتدایی یک محافظ CanDeactivate به صورت ذیل است:
اینترفیس CanDeactivate جنریک بوده و پارامتر جنریک آن نوع کامپوننتی را که قرار است از این محافظ استفاده کند، مشخص میکند. سپس نوع پارامتر متد canDeactivate آن بر اساس نوع پارامتر جنریک، تعیین میگردد.
اکنون این محافظ نیاز دارد تا بداند که آیا کامپوننت ویرایش محصولات، دارای اطلاعات ذخیره نشدهای هست یا خیر؟ چون کامپوننت ویرایش محصولات، به عنوان پارامتر به متد canDeactivate آن ارسال شدهاست، بنابراین میتواند به خواص و متدهای عمومی آن کلاس نیز دسترسی پیدا کند. به همین جهت تغییرات ذیل را به کامپوننت ویرایش محصولات در فایل src\app\product\product-edit\product-edit.component.ts اعمال میکنیم:
در اینجا یک کپی از اصل محصول در حال ویرایش، برای مقایسهی آن با محصول جاری در حال ویرایش، نگهداری میشود. به این ترتیب خاصیت isDirty میتواند مشخص کند که آیا تغییری بر روی خواص این شیء صورت گرفتهاست یا خیر؟ استفاده از متد JSON.stringify، یکی از سادهترین روشهایی است که از آن میتوان جهت مقایسهی تمام خواص دو شیء استفاده کرد. البته چون در اینجا ترتیب خواص این دو شیء یکی است، این روش کار میکند.
برای اینکه این امر میسر شود، خاصیت product به حالت get/set دار تغییر یافتهاست تا بتوان کپی اولیهی محصول را جهت مقایسه، نگهداری کرد. استفاده از متد Object.assign سبب ایجاد یک کپی از شیء اولیه شده و به این صورت دو وهلهی غیرمشترک را خواهیم داشت. اگر value مستقیما به originalProduct انتساب داده میشد، در این حالت هر دوی currentProduct و originalProduct به یک شیء اشاره میکردند.
اکنون میتوان از این خاصیت جدید کامپوننت ویرایش محصولات، در محافظ ترک صفحهی آن استفاده کرد:
در اینجا اگر فرم، تغییر یافته و هنوز ذخیره نشده باشد، خاصیت isDirty برقرار شده و سبب نمایش یک دیالوگ confirm میشود. اگر کاربر آنرا تائید کند، آنگاه مسیر درخواستی جدید فعال میشود. در غیراینصورت، هدایت به مسیر جدید لغو خواهد شد.
در آخر برای استفادهی از این محافظ جدید، باید آنرا به تنظیمات مسیریابی برنامه اضافه کنیم. به همین جهت به فایل src\app\product\product-routing.module.ts مراجعه کرده و این محافظ را به والد مسیریابی ویرایش یک محصول اضافه میکنیم:
با افزودن canDeactivate به والد ویرایش محصولات، از هر دو child route تعریف شده محافظت میکند.
برای آزمایش آن، به صفحهی ویرایش یکی از محصولات مراجعه کرده و تغییری را ایجاد کنید. سپس درخواست مشاهدهی صفحهی دیگری را با کلیک بر روی یکی از لینکهای منوی برنامه ارائه دهید. بلافاصله دیالوگ confirm ظاهر خواهد شد (تصویر فوق).
مشکل! در همین حالت بر روی دکمهی Ok کلیک کنید تا اطلاعات ذخیره نشده را از دست داده و به مسیر دیگری هدایت شویم. مجددا همین پروسه را تکرار کنید. اینبار اگر بر روی دکمهی Save کلیک کنید، باز هم دیالوگ confirm ظاهر میشود. علت اینجا است که شیء محصول اصلی و جاری، پس از ذخیره سازی به حالت اولیه بازگشت داده نشدهاند. برای این منظور متد reset را به کامپوننت ویرایش اطلاعات اضافه کرده:
و سپس آنرا به متد onSaveComplete، اضافه میکنیم:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-routing-lab-08.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng s -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
- canActivate : جهت محافظت دسترسی به یک مسیر
- canActivateChild: برای محافظت دسترسی به یک Child Route
- canDeactivate : برای جلوگیری کردن از ترک مسیر جاری و هدایت به مسیری دیگر (برای مثال جهت نمایش پیام «هنوز اطلاع تغییر یافته را ذخیره نکردهاید»)
- canLoad : برای جلوگیری از مسیریابی غیرهمزمان (async routing) که در قسمت بعدی بررسی خواهد شد
- resolve: برای پیش واکشی اطلاعات، پیش از نمایش مسیر (که آنرا در قسمت چهارم این سری بررسی کردیم)
لزوم استفادهی از محافظهای مسیرها
گاهی از اوقات میخواهیم دسترسی به یک مسیر را محدود به کاربران وارد شدهی به سیستم کنیم و یا مسیرهایی را داشته باشیم که تنها توسط گروه خاصی از کاربران قابل دسترسی باشند. همچنین در بسیاری از اوقات نیاز است به کاربران اخطارهایی را پیش از ترک یک مسیر نمایش دهیم. برای مثال پیش از ترک صفحهی ویرایش اطلاعاتی که دارای اطلاعات ذخیره نشدهاست، بهتر است پیامی را جهت یادآوری این مساله نمایش دهیم. برای پیاده سازی هر کدام از این قابلیتها از یک محافظ مسیر ویژه استفاده میشود.
ترتیب اجرای محافظهای مسیرها
مسیریاب سیستم، ابتدا محافظ canDeactivate را اجرا میکند تا مشخص شود که آیا کاربر میتواند مسیر جاری را ترک کند یا خیر؟ سپس اگر مسیریابی تعریف شده غیرهمزمان باشد، محافظ canLoad اجرا میشود. پس از آن محافظ canActivateChild بررسی میشود. در ادامه محافظ canActivate اجرا میگردد. در پایان کار بررسی محافظهای موجود، کار بررسی محافظ resolve، جهت پیش واکشی اطلاعات مسیر درخواستی، انجام خواهد شد.
در اینجا اگر یکی از محافظها مقدار false را برگرداند، پردازش مابقی آنها لغو خواهد شد و کار هدایت کاربر به مسیر درخواستی، خاتمه مییابد.
مراحل ساخت و اعمال یک محافظ مسیر
ساخت و اعمال یک محافظ مسیر شامل سه مرحله است:
الف) یک محافظ مسیر عموما به صورت یک سرویس جدید پیاده سازی میشود:
import { Injectable } from '@angular/core'; import { CanActivate } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { } }
ب) از آنجائیکه محافظها، سرویس هستند، نیاز است تعریف کلاس آنها را در قسمت providers ماژول مرتبط نیز ذکر کنیم تا در برنامه قابل دسترسی شوند. باید دقت داشت که برخلاف سایر سرویسها، امکان تعریف محافظها صرفا در سطح یک ماژول مسیر است و نه در سطح یک کامپوننت. به این ترتیب مسیریاب میتواند به آن، در طی هدایت کاربر به مسیر درخواستی، دسترسی پیدا کند.
ج) پس از آن برای فعالسازی یک محافظ مسیر، آنرا به عنوان یک خاصیت جدید، به تنظیمات مسیریابی اضافه خواهیم کرد. نام این خاصیت دقیقا مساوی با نوع محافظی است که تعریف شدهاست. برای مثال اگر محافظ تعریف شده از نوع CanActivate است، نام خاصیتی که ذکر خواهد شد، canActivate میباشد. مقدار آن نیز میتواند آرایهای از سرویسهایی از این نوع باشد.
امکان به اشتراک گذاشتن یک محافظ بین چندین مسیر نیز وجود دارد. فرض کنید میخواهیم تمام مسیرهای مربوط به محصولات را محافظت کنیم. در این حالت میتوان محافظ را به تک تک Child routes موجود اعمال کرد و یا میتوان محافظ را به والد آنها نیز اعمال کنیم تا به صورت خودکار سبب محافظت از فرزندان آن نیز شویم.
یک مثال: ساخت محافظ canActivate
جهت بررسی شرط یا شرایطی پیش از فعال سازی یک مسیر درخواستی، از محافظهایی از نوع canActivate میتوان استفاده کرد. این نوع محافظها عموما جهت اعتبارسنجی کاربران و محدود سازی دسترسی آنها به قسمتهای مختلف برنامه استفاده میشوند. این نوع محافظها حتی با تغییر پارامترهای مسیریابی نیز فعال شده و بررسی میشوند.
در ادامهی مثال این سری میخواهیم کاربران را پیش از دسترسی به قسمتهای مختلف مرتبط با محصولات، وادار به لاگین کنیم. برای این منظور دستور ذیل را اجرا کنید:
>ng g guard user/auth -m user/user.module
installing guard create src\app\user\auth.guard.spec.ts create src\app\user\auth.guard.ts update src\app\user\user.module.ts
در ادامه کدهای این محافظ را به صورت ذیل تکمیل کنید:
import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, RouterStateSnapshot, CanActivate, Router } from '@angular/router'; import { AuthService } from './auth.service'; @Injectable() export class AuthGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { return this.checkLoggedIn(state.url); } checkLoggedIn(url: string): boolean { if (this.authService.isLoggedIn()) { return true; } this.authService.redirectUrl = url; this.router.navigate(['/login']); return false; } }
export class AuthService { currentUser: IUser; redirectUrl: string;
توضیحات:
این سرویس چون از نوع CanActivate است، این اینترفیس را پیاده سازی کردهاست و همچنین متد canActivate آنرا نیز به همراه دارد:
export class AuthGuard implements CanActivate { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
یک نکته: هرچند در اینجا میتوان به پارامتر id مسیر، مانند route.params['id'] در صورت نیاز دسترسی یافت، اما امکان دسترسی به اطلاعات از پیش واکشی شده مانند route.data['product'] وجود ندارد. علت آنرا نیز در قسمت «ترتیب اجرای محافظهای مسیرها» ابتدای بحث جاری، بررسی کردیم: محافظ resolve در انتهای کار پردازش تمام محافظهای موجود فراخوانی میشود.
در متد canActivate میخواهیم بررسی کنیم که آیا کاربر، لاگین کردهاست یا خیر؟ اگر بله، تنها کافی است true را بازگشت دهیم تا کار این محافظ پایان یابد. در غیراینصورت false را بازگشت داده و همچنین سبب هدایت کاربر به صفحهی لاگین میشویم.
به همین منظور سرویس AuthService را به سازندهی این کلاس تزریق کردهایم تا بتوانیم به متد isLoggedIn آن دسترسی پیدا کنیم (این سرویس را در قسمت دوم این سری تکمیل کردیم).
این متد نیز به صورت ذیل تعریف شدهاست:
isLoggedIn(): boolean { return !this.currentUser; }
در ادامه برای استفادهی از این محافظ مسیر، به فایل src\app\product\product-routing.module.ts مراجعه کرده و آنرا به نحو ذیل اعمال خواهیم کرد:
import { AuthGuard } from './../user/auth.guard'; const routes: Routes = [ { path: 'products', canActivate: [ AuthGuard ], children: [ ] } ];
اکنون برنامه را با دستور ng s -o ساخته و اجرا کنید. سپس بر روی لینک لیست محصولات و یا افزودن یک محصول جدید کلیک کنید. بلافاصله صفحهی لاگین را مشاهده خواهید کرد.
به خاطر سپاری و بازیابی مسیر درخواستی کاربر پس از لاگین
در اینجا اگر کاربر بر روی لینک افزودن یک محصول جدید کلیک کند، صفحهی لاگین را مشاهده خواهد کرد. اما پس از لاگین، همواره به مسیر لیست محصولات هدایت میشود و در این حالت مسیر درخواستی اولیه فراموش خواهد شد. برای رفع این مشکل نیاز است آدرس درخواستی کاربر را نیز ذخیره و بازیابی کرد. به همین جهت خاصیت this.authService.redirectUrl = url را در متد checkLoggedIn محافظ تعریف شده مقدار دهی کردیم. در اینجا از سرویس Auth، برای به اشتراک گذاری اطلاعات با محافظهای مسیر استفاده کردهایم. طول عمر یک سرویس، singleton است. بنابراین تنها یک وهله از آن در طول عمر برنامه وجود خواهد داشت. به این ترتیب با ذخیرهی اطلاعاتی در آن، این اطلاعات در تمام برنامه قابل دسترسی خواهد شد.
با توجه به این نکته، اکنون به فایل src\app\user\login\login.component.ts مراجعه کرده و قسمت this.router.navigate آنرا به صورت ذیل بهبود خواهیم بخشید:
if (this.authService.login(userName, password)) { if (this.authService.redirectUrl) { this.router.navigateByUrl(this.authService.redirectUrl); } else { this.router.navigate(['/products']); } }
در ادامه برای آزمایش آن، پس از اجرای برنامه، صفحهی افزودن یک محصول جدید را درخواست دهید. سپس لاگین کنید. اکنون مشاهده خواهید کرد که برنامه مسیر درخواستی پیش از لاگین را به خاطر سپردهاست.
بررسی محافظ canActivateChild
این محافظ نیز شبیه به محافظ canActivate است؛ با این تفاوت که تنها زمانی فعالسازی خواهد شد که فرزند یک مسیر قرار است نمایش داده شود و نه خود مسیر اصلی.
محافظ canActivateChild با تغییر قسمت child یک مسیر فعالسازی میشود؛ حتی اگر این تغییر در حد تغییر پارامترهای آن مسیر باشد. اما باید درنظر داشت که اگر تنها قسمت child یک مسیر تغییر کند، دیگر محافظ canActivate مجددا اجرا نخواهد شد.
یک مثال: اگر کاربر در حال مشاهدهی صفحهی لیست محصولات باشد و بر روی لینک مشاهدهی یک محصول کلیک کند، تنها قسمت child مسیر تغییر میکند. در این حالت canActivate مسیر اصلی دیگر اجرا نخواهد شد؛ اما تمام محافظهای canActivateChild مرتبط مجددا اجرا خواهند شد.
بررسی محافظ canDeactivate
محافظ canDeactivate پیش از ترک یک مسیر، فعالسازی و بررسی میشود. عموما از آن جهت بررسی وضعیت اطلاعات ذخیره نشده و اطلاع رسانی به کاربر، پیش از ترک مسیر جاری استفاده استفاده میگردد. این محافظ با هر تغییری در آدرس جاری مسیر، بررسی میشود. بدیهی است این تغییر صرفا درون یک برنامهی Angular معنا پیدا میکند و نه هدایت به سایتی دیگر.
در حال حاضر در مثال جاری این سری، اگر کاربر، تغییری را در صفحهی ویرایش اطلاعات ایجاد کند و بدون کلیک بر روی دکمهی Save به صفحهی دیگری مراجعه کند، این اطلاعات تغییر یافته، از دست خواهند رفت. برای رفع این مشکل میتوان محافظ canDeactivate ایی را برای آن طراحی کرد. به همین جهت دستور ذیل را اجرا کنید:
>ng g guard product/ProductEdit -m product/product.module
installing guard create src\app\product\product-edit.guard.spec.ts create src\app\product\product-edit.guard.ts update src\app\product\product.module.ts
امضای ابتدایی یک محافظ CanDeactivate به صورت ذیل است:
export class ProductEditGuard implements CanDeactivate<ProductEditComponent> { canDeactivate(component: ProductEditComponent): boolean {
اکنون این محافظ نیاز دارد تا بداند که آیا کامپوننت ویرایش محصولات، دارای اطلاعات ذخیره نشدهای هست یا خیر؟ چون کامپوننت ویرایش محصولات، به عنوان پارامتر به متد canDeactivate آن ارسال شدهاست، بنابراین میتواند به خواص و متدهای عمومی آن کلاس نیز دسترسی پیدا کند. به همین جهت تغییرات ذیل را به کامپوننت ویرایش محصولات در فایل src\app\product\product-edit\product-edit.component.ts اعمال میکنیم:
get product(): IProduct { return this.currentProduct; } set product(value: IProduct) { this.currentProduct = value; // Clone the object to retain a copy this.originalProduct = Object.assign({}, value); } get isDirty(): boolean { return JSON.stringify(this.originalProduct) !== JSON.stringify(this.currentProduct); }
برای اینکه این امر میسر شود، خاصیت product به حالت get/set دار تغییر یافتهاست تا بتوان کپی اولیهی محصول را جهت مقایسه، نگهداری کرد. استفاده از متد Object.assign سبب ایجاد یک کپی از شیء اولیه شده و به این صورت دو وهلهی غیرمشترک را خواهیم داشت. اگر value مستقیما به originalProduct انتساب داده میشد، در این حالت هر دوی currentProduct و originalProduct به یک شیء اشاره میکردند.
اکنون میتوان از این خاصیت جدید کامپوننت ویرایش محصولات، در محافظ ترک صفحهی آن استفاده کرد:
import { Injectable } from '@angular/core'; import { CanDeactivate } from '@angular/router'; import { ProductEditComponent } from './product-edit/product-edit.component'; @Injectable() export class ProductEditGuard implements CanDeactivate<ProductEditComponent> { canDeactivate(component: ProductEditComponent): boolean { if (component.isDirty) { let productName = component.product.productName || 'New Product'; return confirm(`Navigate away and lose all changes to ${productName}?`); } return true; } }
در آخر برای استفادهی از این محافظ جدید، باید آنرا به تنظیمات مسیریابی برنامه اضافه کنیم. به همین جهت به فایل src\app\product\product-routing.module.ts مراجعه کرده و این محافظ را به والد مسیریابی ویرایش یک محصول اضافه میکنیم:
import { ProductEditGuard } from './product-edit.guard'; const routes: Routes = [ { path: 'products', canActivate: [ AuthGuard ], children: [ { path: '', component: ProductListComponent }, { path: ':id', component: ProductDetailComponent, resolve: { product: ProductResolverService } }, { path: ':id/edit', component: ProductEditComponent, resolve: { product: ProductResolverService }, canDeactivate: [ ProductEditGuard ], children: [ { path: '', redirectTo: 'info', pathMatch: 'full' }, { path: 'info', component: ProductEditInfoComponent }, { path: 'tags', component: ProductEditTagsComponent } ] } ] } ];
برای آزمایش آن، به صفحهی ویرایش یکی از محصولات مراجعه کرده و تغییری را ایجاد کنید. سپس درخواست مشاهدهی صفحهی دیگری را با کلیک بر روی یکی از لینکهای منوی برنامه ارائه دهید. بلافاصله دیالوگ confirm ظاهر خواهد شد (تصویر فوق).
مشکل! در همین حالت بر روی دکمهی Ok کلیک کنید تا اطلاعات ذخیره نشده را از دست داده و به مسیر دیگری هدایت شویم. مجددا همین پروسه را تکرار کنید. اینبار اگر بر روی دکمهی Save کلیک کنید، باز هم دیالوگ confirm ظاهر میشود. علت اینجا است که شیء محصول اصلی و جاری، پس از ذخیره سازی به حالت اولیه بازگشت داده نشدهاند. برای این منظور متد reset را به کامپوننت ویرایش اطلاعات اضافه کرده:
reset(): void { this.dataIsValid = null; this.currentProduct = null; this.originalProduct = null; }
onSaveComplete(message?: string): void { if (message) { this.messageService.addMessage(message); } this.reset(); // Navigate back to the product list this.router.navigate(['/products']); }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-routing-lab-08.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng s -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.