یک فید برای سایت درست کنید. این فید را در فیدبرنر ثبت کنید. مابقی آن خودکار است. دیگر گوگل دست از سر شما برنخواهد داشت!
نظرات مطالب
مروری بر کدهای کلاس SqlHelper
یک سری ویدیوی آزموشی به روز و با کیفیت در این زمینه از سایت pluralsight موجود است. در گوگل جستجو کنید قابل دریافت هستند.
مطالب
AngularJS #2
بهتر است قبل از این که به ادامهی آموزش بپردازم، دو نکته را متذکر شوم:
1) روند آموزشی این فریمورک از کل به جز است؛ به این معنا که ابتدا تمامی قابلیتهای اصلی فریمورک را به صورت کلی و بدون وارد شدن به جزئیات بیان میکنم و پس از آن، جزئیات را در قالب مثالهایی واقعی بیان خواهم کرد.
2) IDE مورد استفاده بنده Visual Studio 2012 است. همچنین از ابتدا پروژه را با ASP.NET MVC شروع میکنم. شاید بگویید که میشود Angular را بدون درگیر شدن با مباحث ASP.NET MVC بیان کرد؛ اما پاسخ من این است که این مثالها باید قابل پیادهسازی در نرمافزارهای واقعی باشند و یکی از بسترهای مورد علاقهی من ASP.NET MVC است. اگرچه باز هم تاکید میکنم که کلیهی مباحث ذکرشده، برای کلیهی زبانهای سمت سرور دیگر هم قابل استفاده است و هدف من در اینجا بیان یک سری چالشها در ASP.NET MVC است.
نحوهی دریافت AngularJS
1) NuGet Package Manager
2) دریافت از وبسایت angularjs.org
دریافت از طریق Nuget Package Manager
روش ارجح افزودن کتابخانههای جانبی در یک پروژهی واقعی، استفاده از NuGet Package Manager است. دلیل آن هم بارها بیانشده است از جمله: باخبر شدن از آخرین بهروزرسانی کتابخانهها، دریافت وابستگیهای کتابخانهی مورد نظر و نبودن محدودیت تحریم برای دریافت فایلها است.
روش کار هم بسیار ساده است، کافی است که بر روی پروژه کلیک راست کرده و گزینهی Manage NuGet Packages را انتخاب کنید و با جست جو angularjs نسبت به نصب آن اقدام نمایید.
اگر هم با ابزارهای گرافیکی رابطهی خوبی ندارید، میتوانید از Package Manager Console فراهمشده توسط NuGet استفاده کنید. کافی است در کنسول پاورشل آن عبارت زیر را تایپ کنید:
Install-Package angularjs
پس از نصب angularjs، شاهد تغییراتی در پوشهی Scripts پروژهی خودخواهید بود. تعداد زیادی فایل جاوا اسکریپت که با عبارت angular شروعشدهاند، به این پوشه اضافه شده است. در حال حاضر ما تنها به فایل angular.js نیاز داریم و احتیاجی به فایلهای دیگر نیست.
همچنین یک پوشه به نام i18n نیز اضافه شده است که برای مباحث Globalization و Internationalization به کار گرفته میشود.
دریافت از سایت angularjs.org
برای دریافت Angular از وب سایت رسمیاش، به angularjs.org مراجعه کنید؛ اما گویا به دلیل تحریمها این سایت برای IP ایران مسدود شده است (البته افرادی نیز بدون مشکل به آن دسترسی دارند). دکمهی Download را فشار داده و در نهایت کلید دریافت را بزنید. اگر نسخهی کامل آن را دریافت کنید، لیستی از مستندات AngularJS را نیز در فایل دریافتی، خواهید داشت. در هر صورت این روش برای استفاده از angular دریک پروژهی واقعی توصیه نمیشود.
پس به عنوان یک best practice، همیشه کتابخانههای جانبی را با NuGet دریافت و نصب کنید. رفع موانع تحریمها، یکی از مزایای مهم آن است.
پس از دریافت angular، نوشتن برنامهی معروف Hello, World به وسیلهی آن ، میتواند بهترین شروع باشد؛ اما اگر اجازه بدهید، نوشتن این برنامه را در قالب توضیح قالبهای سمت کلاینت انجام دهیم.
قالبهای سمت کلاینت (Client Side Templates)
در برنامههای وب چند صفحهای و یا اکثر وب سایتهای معمول، دادهها و کدهای HTML، در سمت سرور اصطلاحا سرهم و مونتاژ شده و خروجی نهایی که HTML خام است به مرورگر کاربر ارسال میشود. با یک مثال بیشتر توضیح میدهم: در ASP.NET MVC معمولا از لحظهای که کاربر صفحهای را درخواست میکند تا زمانی که پاسخ خود را در قالب HTML میبیند، این فرآیند طی میشود: ابتدا درخواست به Controller هدایت میشود و سپس اطلاعات مورد نیاز از پایگاه داده خواندهشده و در قالب یک Model به View که یک فایل HTML ساده است، منتقل میشود. سپس به کمک موتور نمایشی Razor، دادهها در جای مناسب خود قرار میگیرند و در نهایت، خروجی که HTML خام است به مرورگر کلاینت درخواستکننده ارسال میشود تا در مرورگر خود نتیجه را مشاهده نماید. روال کار نیز در اکثر SPAهای معمول و یا اصطلاحا برنامههای AJAX، باکمی تغییر به همین شکل است.
اما در Angular داستان به شکل دیگری اتفاق میافتد؛ Angular قالب HTML و دادهها را به صورت جداگانه از سرور دریافت میکند و در مرورگر کاربر آنها را سرهم و مونتاژ میکند. بدیهی است که در اینجا قالب، یک فایل HTML ساده و دادهها میتواند به فرم JSON باشد. در نتیجه کار سرور دیگر فراهم کردن قالب و دادهها برای کلاینت است و بقیهی ماجرا در سمت کلاینت رخ میدهد.
خیلی خوب، مزیت این کار نسبت به روشهای معمول چیست؟ اگر اجازه بدهید این را با یک مثال شرح دهم:
در بسیاری از سایت ها، ویژگی ای به نام اسکرول نامحدود وجود دارد. در همین سایت نیز دکمه ای با عنوان بیشتر در انتهای لیستی از مطالب، برای مشاهدهی ادامهی لیست قرار گرفته است. سعی کنید پس از فشردن دکمهی بیشتر، دادههای دریافتی از سرور را مشاهده کنید. پس از انجام این کار مشاهده خواهید کرد که پاسخ سرور HTML خام است. اگر تعداد 10 پست از سرور درخواست شود، 10 بار محتوای HTML تکراری نیز دریافت خواهد شد؛ در صورتی که ساختار HTML یک پست هم کفایت میکرد و تنها دادهها در آن 10 پست متفاوتند؛ چرا که قالب کار مشخص است و فقط به ازای هر پست باید آن دادهها در جای مناسب خود قرار داد.
دیدگاههای یک پست هم به خوبی با Angular قابل پیاده سازی است. قالب HTML یک دیدگاه را برای angular تعریف کرده و دادههای مناسب که احتمالا JSON خام است از سرور دریافت شود. نتیجهی این کار هم صرفه جوی در پهنای باند مصرفی و افزایش فوق العادهی سرعت است، همچنین در صورت نیاز میتوان دادهها و قالبها راکش کرد تا مراجعه به سرور به حداقل برسد.
چگونگی انجام این کار در AngularJs به صورت خلاصه به این صورت است که در angular یک directive به نام ng-repeat تعریف شده است که مانند یک حلقهی foreach برای HTML عمل میکند. شما در داخل حلقه، قالب را مشخص میکنید و به ازای تعداد دادهها، آن حلقه تکرار میشود و بر روی دادهها پیمایش صورت میگیرد.
البته این مثالها فقط دو نمونه از کاربرد این ویژگی در دنیای واقعی بود و مطمئن باشید که در مقالات آینده مثالهای زیادی از این موضوع را پیادهسازی خواهیم کرد.
بهتر است که دیگر خیلی وارد جزئیات نشویم و اولین برنامهی خود را به کمک angularjs بنویسیم. این برنامه، همان برنامهی معروف Hello ,World است؛ اما در این برنامه به جای نوشتن یک Hello, World ساده در صفحه، آن را با ساختار angularjs پیادهسازی میکنیم.
در داخل ویژوال استادیو یک فایل HTML ساده ایجاد کنید و کدهای زیر را داخل آن بنویسید.
<!DOCTYPE html> <html ng-app> <head> <title>Sample 1</title> </head> <body> <div ng-controller="GreetingController"> <p>{{greeting.text}}, World!</p> </div> <script src="../Scripts/angular.js"></script> <script> function GreetingController($scope) { $scope.greeting = { text: "Hello" }; } </script> </body> </html>
سپس فایل فوق را در مرورگر اجرا کنید. بله؛ عبارت Hello, World را مشاهده خواهید کرد. یک بار دیگر خاصیت text را در scope.greeting$ به hi تغییر بدهید و باز هم نتیجه را مشاهده کنید.
این مثال در نگاه اول خیلی ساده است، اما دنیایی از مفاهیم angular را در بر دارد. شما خواص جدیدی را برای عناصر HTML مشاهده میکنید: ng-app، ng-controller، آکلودها و عبارت درون آن و متغیر scope$ به عنوان پارامتر.
حال بیایید ویژگیها و مفاهیم جالب کدهای نوشته شده را بررسی کنیم؛ چرا که فرصت برای بررسی ng-app و بقیهی موارد نا آشنا زیاد است:
- هیچ id و یا class برای عناصر html در نظر گرفته نشده تا با استفاده از آنها، رویدادی را برای عناصر مورد نظر مشخص کنیم.
- وقتی در GreetingController مقدار greeting.text را مشخص کرده ایم، باز هم هیچ رویدادی را صدا نزده و یا مشخص نکرده ایم.
- GreetingController یک کلاس سادهی جاوا اسکریپت (POJO) است و از هیچ چیزی که توسط angular فراهم شده باشد، ارث بری نکرده است.
- اگر به متد سازندهی کلاس GreetingController دقت کنید، متغیر scope$ به عنوان پارامتر تعریف شده است. نکتهی جالب این است که ما هیچ گاه به صورت دستی سازندهی کلاس GreetingController را صدا نزده ایم و حتی درون سازنده هم scope$ را ایجاد نکرده ایم؛ پس چگونه توانسته ایم خاصیتی را به آن نسبت داده و برنامه به خوبی کار کند. بهتر است برای پاسخ به این سوال خودتان دست به کار شوید؛ ابتدا نام متغیر scope$ را به نام دلخواه دیگری تغییر دهید و سپس برنامه را اجرا کنید. بله برنامه دیگر کار نمیکند. دلیل آن چیست؟ همان طور که گفتم Angular دارای یک سیستم تزریق وابستگی توکار است و در اینجا نیز scope$ به عنوان وابستگی در سازندهی این کلاس مشخص شده است تا نمونهی مناسب آن توسط angular به کلاس GreetingController ما تزریق شود؛ اما چرا به نام آن یعنی scope$ حساس است؟ به این دلیل که زبان جاوا اسکریپت یک زبان پویا است و نوع در آن مطرح نیست؛ angular مجبور است که از نام پارامترها برای تزریق وابستگی استفاده میکند. در مقالات آینده چگونگی عملکرد سیستم تزریق وابستگی angular را به تشریح بیان میکنم.
- همچنین همان طور که در مورد قبلی نیز به آن اشاره کردم، ما هیچ گاه خود دستی سازندهی GreetingController را صدا نزدیم و جایی نیز نحوهی صدا زدن آن را مشخص نکرده ایم.
تا همین جا فکر کنم کاملا برای شما مشخص شده است که ساختار فریمورک Angular با تمامی کتاب خانههای مشابه متفاوت است و با ساختاری کاملا اصولی و حساب شده طرف هستیم. همچنین در مقالات آینده توجه شما را به قابلیتهایی بسیار قدرتمندتر جلب خواهم کرد.
MVC ،MVP ، MVVM و یا MVW
در بخش اول این مقاله، الگوی طراحی پیشنهادی فریمورک Angular را MVC بیان کردهام؛ اما همان طور که گفته بودم AngularJS از انقیاد داده دوطرفه (Two Way Data Binding) نیز به خوبی پشتیبانی میکند و به همین دلیل عده ای آن را یک MVVM Framework تلقی میکنند. حتی داستان به همین جا ختم نمیشود و عده ای آن را به چشم MVP Framework نیز نگاه میکنند. در ابتدا سایت رسمی AngularJS الگوی طراحی مورد استفاده را MVC بیان مینمود ولی در این چند وقت اخیر عنوانش را به MVW Framework تغییر داده است.
MVW مخفف عبارت Model View Whatever هست و کاملا مفهومش مشخص است. Model و View بخشهای مشترک تمام الگوها بودند و تنها بخش سوم مورد اختلاف توسعه دهندگان بود؛ در نتیجه انتخاب آن را بر عهدهی استفاده کننده قرار داده اند و تمام امکانات لازم برای پیادهسازی این الگوهای طراحی را فراهم کرده اند. در طی این مقالات صرف نظر از تمام الگوهای طراحی فوق، من بیشتر بر روی MVC تمرکز خواهم کرد.
الگوی طراحی MVC در سال 1970 به عنوان بخشی از زبان برنامه نویسی Smalltalk معرفی شد و از همان ابتدا به سرعت محبوبیت زیادی در بین محیطهای توسعهی دسکتاپی از قبیل ++C و Java که رابط کاربری گرافیکی به نوعی در آنها دخیل است، پیدا کرد.
تفکر MVC این را بیان میکند که باید جداسازی واضح و روشنی بین مدیریت دادهها (Model)، منطق برنامه (Controller) و نمایش دادهها به کاربر (View) وجود داشته باشد و در اصل هدفش جداسازی اجزای رابط کاربری به بخش هایی مجزا است.
شاید این سوال برای شما پیش بیاید که چرا باید چنین الگویی را در برنامهها پیاده کرد؟
احتمالا تا کنون از بین برنامه هایی که نوشته اید، رابط کاربری بیشتر از آنها را نیز خودتان مجبور شده اید طراحی کنید؛ به این دلیل که برنامهی شما بدون رابط کاربری قابل اجرا شدن نبوده است. اجرای برنامهی شما منوط به وجود تعدادی دکمه و textbox و ... بوده است و به قولی منطق برنامه به رابط گرافیکی گره خورده بوده است. پس میتوان گفت که پیادهسازی الگوی طراحی وقتی ضرورت پیدا میکند که رابط گرافیکی، قسمتی از برنامهی شما را تشکیل دهد.
آیا با وجود زبانهای طراحی ساده ای مثل HTML و XAML و ... احتیاجی است که برنامه نویس وقت خود را صرف طراحی رابط کاربری کند؟ مسلما خیر، چون دیگر با این امکانات یک طراح هم از پس این کار به خوبی و یا حتی بهتر بر میآید. دیگر وظیفهی برنامه نویس نوشتن کدهای مربوط به منطق برنامه است. کدهایی که بدون UI هم قابل تست شدن باشد و به راحتی بتوان برای آنها آزمونهای واحد نوشت. برنامه نویس باید این را در نظر بگیرد که UI وجود ندارد و حتی ممکن است هیچ گاه هم ایجاد نشود و این کدها تبدیل به یک کتابخانه شود و مورد استفاده قرار بگیرد تا در یک برنامه با رابط کاربری گرافیکی.
در MVC، روال عمومی کار به این شکل است که View دادهها را از Model دریافت میکند و به کاربر نمایش میدهد. وقتی که کاربر با کلیک کردن و تایپ کردن با برنامه ارتباط برقرار مینماید، Controller به این درخواستها پاسخ میدهد و دادههای موجود در Model را به روز رسانی میکند. در نهایت هم Model تغییرات خود را به View منعکس میکند تا View آن چه را که پیش از آن نمایش میداده است، تغییر دهد و View را از تغییرات رخ داده آگاه نماید.
اما در برنامههای Angular قضیه از چه قرار است؟ در Angular، قالب HTML یا اگر بخواهم دقیقتر بگویم (Document Object Model(DOM معادل View است؛ کلاسهای جاوا اسکریپتی نقش Controller را دارند؛ و خواص اشیای جاوا اسکریپتی و یا حتی خود اشیا نقش Model را بر عهده دارند.
ساختار بخشیدن به برنامه با استفاده MVC یک مزیت مهم دیگر نیز دارد: ساختار کار کاملا مشخص است و هر کسی نمیتواند به صورت سلیقه ای آن را پیاده سازی کند. با یک مثال این موضوع را تشریح میکنم: اگر کسی پروژهی بنده را که با ASP.NET MVC نوشتم، بررسی کند، اصلا احساس غریبی نمیکند و به راحتی میتواند آن را توسعه دهد. دلیل این موضوع این است که ASP.NET MVC یک ساختار مشخص را به توسعه دهندگان اجبار کرده است و هر کسی این ساختار را رعایت کند و با آن آشنا باشد، به راحتی میتواند با آن کار کند. توسعه دهنده میداندکه من Model را کجا تعریف کرده ام، Controller مربوط به هر View کجاست و در کدام قسمت با پایگاه داده ارتباط برقرار کردهام؛ اما در مورد کدهای JavaScript و سمت کلاینت چه طور؟ توسعه دهنده ای که میخواهد کار من را ادامه بدهد دچار وحشت میشود! الگوی مشخصی وجود ندارد؛ معلوم نیست که کجا DOM را دستکاری کردهام، در کدام قسمت با سرور ارتباط برقرار شده و... به قول معروف با یک اسپاگتی کد تمام عیار طرف میشود. AngularJS این مشکل را حل نموده و ساختار خاصی را سعی کرده به شما دیکته کند و تا حد ممکن دست شما را نیز باز گذاشته است. جدا از همهی اینها، برنامههای مبتنی بر Angular به راحتی نگه داری و تست میشوند و بدون هیچ دغدغه ای آنها را میتوان توسعه داد.
در حاشیه
شاید در هنگام دریافت فایل angularjs و افزودن آن به پروژهی خود شروع به اعتراض کرده اید که نسخهی فشرده شدهی آن 87 کیلو بایت حجم دارد در صورتی که این حجم در کتابخانههای مشابه ممکن است حتی به 10 کیلوبایت هم نرسد. اگر دقت کرده باشید من در بیان AngularJS از واژهی کتاب خانه استفاده نکردم و فقط از واژهی فریمورک استفاده کردم. بله نمیشود angular را با کتاب خانه هایی مقایسه کرد که مهمترین ویژگی خود را Data Binding میدانند. AngularJS یک بستر کاری قدرتمند است که تمام راه حلهای موجود را در خود جمع کرده است. تیم توسعه دهندهی آن هم هیچ ادعایی ندارد و میگویند که ما هیچ چیزی را خودمان اختراع نکرده ایم، بلکه راه حلهای عالی را برگزیدیم، تفکرهای خوب را ارتقا بخشیده و در فریمورک خود استفاده کردیم و حتی از ایدههای خوب دیگر کتاب خانهها هم استفاده کرده ایم. بنابر این نباید به حجم آن در مقابل توانایی هایی که دارد اعتراض کرد.
همچنین به نظر میآید که AngularJS یک فریمورک پیچیده است. ولی من همیشه بین پیچیده و پیچیده شده تفاوت قائل میشوم. به نظر شخصی خودم Angular به دلیل مشکلات خاص و پیچیده ای که حل میکند پیچیده است و پیچیده شده نیست. اگر آن را پیچیده شده حس میکنید، تنها دلیلش، نحوهی آموزش دادن بنده است، تمام سعی خود را میکنم که مفاهیم را تا حد ممکن ساده بیان کنم و امیدوارم در آینده که با مثالهای بیشتری روبرو میشوید، این مفاهیم به کارتان بیاید.
در مقالهی بعدی به مفاهیم انقیاد داده، تزریق وابستگی، هدایت گرها (Directives) و سرویسها در AngularJS میپردازم.
از متدهای HashPassword و VerifyHashedPassword سورس ASP.NET Identity ایده بگیرید. مورد اول برای ذخیره سازی اطلاعات در بانک اطلاعاتی است. مورد دوم در حین لاگین، جهت تعیین اعتبار کلمهی عبور کاربر استفاده میشود.
ابتدا افزونه را نصب کنید سپس روی آیکون افزونه کلیک کرده و فونت مورد نظر را انتخاب نمایید تا فونت روی سایت اعمال شود.
در حال حاضر پرکاربردترین وب سایتها، شبکههای اجتماعی و سرویسهای تحت وب میتوانند از این افزونه بهره ببرند.
وب سایت: ویکیپدیا و گوگل
شبکههای اجتماعی: توییتر، فیسبوک، اینستاگرام، لینکدین، گوگل پلاس
سرویسهای تحت وب: واتس آپ، تلگرام و ترلو
دانلود برای موزیلا
نظرات مطالب
Ankh-SVN-2.0.6347
Ankh هم مثل TortoiseSVN زمانیکه گزینه Ankh –> Add solution to Subversion repository را انتخاب میکنید (با کلیک راست روی پروژه موجود در VS.Net) و مثلا آدرس svn://localhost/trunk را ارائه میدهید، پس از کلیک بر روی OK ، به صورت خودکار صفحه وارد کردن نام کاربری و کلمه عبور را نمایش میدهد.
وجود نقشها و claims در این پروژه، جهت جلوگیری از انجام یک چنین کارهایی است. یک کاربر پس از لاگین میتواند نقش ادمین را داشته باشد یا نقش یک کاربر معمولی را و یا هر نقش دیگری که صلاح است (در این حالت نیازی به چندین صفحهی لاگین وجود ندارد). در سایت (یا حتی صفحهی جاری) roles و claims را جستجو کنید، اطلاعات بیشتری را در مورد کاربردهای آنها میتوانید پیدا کنید.
یکی از روشهای تهیهی برنامههای چند مستاجری، ایجاد بانکهای اطلاعاتی مستقلی به ازای هر مشتری است؛ یا نمونهی دیگر آن، برنامههایی هستند که اطلاعات هر سال را در یک بانک اطلاعاتی جداگانه نگهداری میکنند. در ادامه قصد داریم، نحوهی کار با این بانکهای اطلاعاتی را به صورت همزمان، توسط EF Code first و در حالت استفاده از الگوی واحد کار و تزریق وابستگیها، به همراه فعال سازی خودکار مباحث migrations و به روز رسانی ساختار تمام بانکهای اطلاعاتی مورد استفاده، بررسی کنیم.
مشخص سازی رشتههای متفاوت اتصالی
فرض کنید برنامهی جاری شما قرار است از دو بانک اطلاعاتی مشخص استفاده کند که تعاریف رشتههای اتصالی آنها در وب کانفیگ به صورت ذیل مشخص شدهاند:
البته، ذکر این مورد کاملا اختیاری است و میتوان رشتههای اتصالی را به صورت پویا نیز در زمان اجرا مشخص و مقدار دهی کرد.
تغییر Context برنامه جهت پذیرش رشتههای اتصالی پویای قابل تغییر در زمان اجرا
اکنون که قرار است کاربران در حین ورود به برنامه، بانک اطلاعاتی مدنظر خود را انتخاب کنند و یا سیستم قرار است به ازای کاربری خاص، رشتهی اتصالی خاص او را به Context ارسال کند، نیاز است Context برنامه را به صورت ذیل تغییر دهیم:
در اینجا دو متد سازنده را مشاهده میکنید. سازندهی پیش فرض، از رشتهای اتصالی با نامی مساوی Sample07Context استفاده میکند و سازندهی دوم، امکان پذیرش یک رشتهی اتصالی پویا را دارد. مقدار پارامتر ورودی آن میتواند نام رشتهی اتصالی و یا حتی مقدار کامل رشتهی اتصالی باشد. حالت پذیرش نام رشتهی اتصالی زمانی مفید است که همانند مثال ابتدای بحث، این نامها را پیشتر در فایل کانفیگ برنامه ثبت کرده باشید و حالت پذیرش مقدار کامل رشتهی اتصالی، جهت مقدار دهی پویای آن بدون نیاز به ثبت اطلاعاتی در فایل کانفیگ برنامه مفید است.
یک متد دیگر هم در اینجا در انتهای کلاس به نام SetConnectionString تعریف شدهاست. از این متد در حین ورود کاربر به سایت میتوان استفاده کرد. برای مثال حداقل دو نوع طراحی را میتوان درنظر گرفت:
الف) کاربر با برنامهای کار میکند که به ازای سالهای مختلف، بانکهای اطلاعاتی مختلفی دارد و در ابتدای ورود، یک drop down انتخاب سال کاری برای او درنظر گرفته شدهاست (علاوه بر سایر ورودیهای استانداردی مانند نام کاربری و کلمهی عبور). در این حالت بهتر است متد SetConnectionString نام رشتهی اتصالی را بر اساس سال انتخابی، در حین لاگین دریافت کند که اطلاعات آن در فایل کانفیگ سایت پیشتر مشخص شدهاست.
ب) کاربر یا مشتری پس از ورود به سایت، نیاز است صرفا از بانک اطلاعاتی خاص خودش استفاده کند. بنابراین اطلاعات تعریف کاربران و مشتریها در یک بانک اطلاعاتی مجزا قرار دارند و پس از لاگین، نیاز است رشتهی اتصالی او به صورت پویا از بانک اطلاعاتی خوانده شده و سپس توسط متد SetConnectionString تنظیم گردد.
مدیریت سشنهای رشتهی اتصالی جاری
پس از اینکه کاربر، در حین ورود مشخص کرد که از چه بانک اطلاعاتی قرار است استفاده کند و یا اینکه برنامه بر اساس اطلاعات ثبت شدهی او تصمیمگیری کرد که باید از کدام رشتهی اتصالی استفاده کند، نگهداری این رشتهی اتصالی نیاز به سشن دارد تا به ازای هر کاربر متصل به سایت منحصربفرد باشد. در مورد مدیریت سشنها در برنامههای وب، از نکات مطرح شدهی در مطلب «مدیریت سشنها در برنامههای وب به کمک تزریق وابستگیها» استفاده خواهیم کرد:
در اینجا نحوهی پویا سازی تامین رشتهی اتصالی را مشاهده میکنید. در مورد اینترفیس ISessionProvider و کلاس پایه HttpSessionStateBase پیشتر در مطلب «مدیریت سشنها در برنامههای وب به کمک تزریق وابستگیها» بحث شد.
نکتهی مهم این تنظیمات، قسمت مقدار دهی سازندهی کلاس Context برنامه به صورت پویا توسط IoC Container جاری است. در اینجا هر زمانیکه قرار است وهلهای از Sample07Context ساخته شود، از سازندهی دوم آن که دارای پارامتری به نام connectionString است، استفاده خواهد شد. همچنین مقدار آن به صورت پویا از متد getCurrentConnectionString که در انتهای کلاس تعریف شدهاست، دریافت میگردد.
در این متد ابتدا مقدار HttpContext.Current بررسی شدهاست. این مقدار اگر نال باشد، یعنی برنامهی جاری یک برنامهی دسکتاپ است و مدیریت رشتهی اتصالی جاری آنرا توسط یک خاصیت Static یا Singleton تعریف شدهی در برنامه نیز میتوان تامین کرد. از این جهت که در هر زمان، تنها یک کاربر در App Domain جاری برنامهی دسکتاپ میتواند وجود داشته باشد و Singleton یا Static تعریف شدن اطلاعات رشتهی اتصالی، مشکلی را ایجاد نمیکند. اما در برنامههای وب، چندین کاربر در یک App Domain به سیستم وارد میشوند. به همین جهت است که مشاهده میکنید در اینجا از تامین کنندهی سشن، برای نگهداری اطلاعات رشتهی اتصالی جاری کمک گرفته شدهاست.
کلید این سشن نیز در این مثال مساوی CurrentConnectionStringName تعریف شدهاست. بنابراین در حین لاگین موفقیت آمیز کاربر، دو مرحلهی زیر باید طی شوند:
ابتدا باید سشن CurrentConnectionStringName به بانک اطلاعاتی انتخابی کاربر تنظیم شود. برای نمونه در این مثال خاص، از نام رشتهی اتصالی مشخص شدهی در وب کانفیگ برنامه (مثال ابتدای بحث) به نام Sample07Context استفاده شدهاست.
سپس از متد SetConnectionString برای خواندن مقدار نام مشخص شده در سشن CurrentConnectionStringName کمک گرفتهایم. هرچند سازندهی کلاس Context برنامه، هر دو حالت استفاده از نام رشتهی اتصالی و یا مقدار کامل رشتهی اتصالی را پشتیبانی میکند، اما خاصیت this.Database.Connection.ConnectionString تنها رشتهی کامل اتصالی را میپذیرد (بکار رفته در متد SetConnectionString).
تا اینجا کار پویا سازی انتخاب و استفاده از رشتهی اتصالی برنامه به پایان میرسد. هر زمانیکه قرار است Context برنامه توسط IoC Container نمونه سازی شود، به متد getCurrentConnectionString رجوع کرده و مقدار رشتهی اتصالی را از سشن تنظیم شدهای به نام CurrentConnectionStringName دریافت میکند. سپس از مقدار آن جهت مقدار دهی سازندهی دوم کلاس Context استفاده خواهد کرد.
مدیریت migrations خودکار برنامه در حالت استفاده از چندین بانک اطلاعاتی
یکی از مشکلات کار با برنامههای چند دیتابیسی، به روز رسانی ساختار تمام بانکهای اطلاعاتی مورد استفاده، پس از تغییری در ساختار مدلهای برنامه است. از این جهت که اگر تمام بانکهای اطلاعاتی به روز نشوند، کوئریهای جدید برنامه که از خواص و فیلدهای جدید استفاده میکنند، دیگر کار نخواهند کرد. پویا سازی اعمال این تغییرات را میتوان به صورت ذیل انجام داد:
نکتهی مهمی که در اینجا بکار گرفته شدهاست، مشخص سازی صریح سازندهی شیء MigrateDatabaseToLatestVersion است. به صورت معمول در اکثر برنامههای تک دیتابیسی، نیازی به مشخص سازی پارامتر سازندهی این کلاس نیست و در این حالت از سازندهی بدون پارامتر کلاس Context برنامه استفاده خواهد شد. اما اگر سازندهی آنرا مشخص کنیم، به صورت خودکار از متد سازندهای در کلاس Context استفاده میکند که پارامتر رشتهی اتصالی را به صورت پویا میپذیرد.
در این مثال خاص، متد initDatabases در حین آغاز برنامه فراخوانی شدهاست. منظور این است که اینکار در طول عمر برنامه تنها کافی است یکبار انجام شود و پس از آن است که EF Code first میتواند از رشتههای اتصالی متفاوتی که به آن ارسال میشود، بدون مشکل استفاده کند. زیرا اطلاعات نگاشت کلاسهای مدل برنامه به جداول بانک اطلاعاتی به این ترتیب است که کش میشوند و یا بر اساس کلاس Configuration به صورت خودکار به بانک اطلاعاتی اعمال میگردند.
کدهای کامل این مثال را که در حقیقت نمونهی بهبود یافتهی مطلب «EF Code First #12» است، از اینجا میتوانید دریافت کنید:
UoW-Sample
مشخص سازی رشتههای متفاوت اتصالی
فرض کنید برنامهی جاری شما قرار است از دو بانک اطلاعاتی مشخص استفاده کند که تعاریف رشتههای اتصالی آنها در وب کانفیگ به صورت ذیل مشخص شدهاند:
<connectionStrings> <clear /> <add name="Sample07Context" connectionString="Data Source=(local);Initial Catalog=TestDbIoC;Integrated Security = true" providerName="System.Data.SqlClient" /> <add name="Database2012" connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true" providerName="System.Data.SqlClient" /> </connectionStrings>
تغییر Context برنامه جهت پذیرش رشتههای اتصالی پویای قابل تغییر در زمان اجرا
اکنون که قرار است کاربران در حین ورود به برنامه، بانک اطلاعاتی مدنظر خود را انتخاب کنند و یا سیستم قرار است به ازای کاربری خاص، رشتهی اتصالی خاص او را به Context ارسال کند، نیاز است Context برنامه را به صورت ذیل تغییر دهیم:
using System.Collections.Generic; using System.Data.Entity; using System.Linq; using EF_Sample07.DomainClasses; namespace EF_Sample07.DataLayer.Context { public class Sample07Context : DbContext, IUnitOfWork { public DbSet<Category> Categories { set; get; } public DbSet<Product> Products { set; get; } /// <summary> /// It looks for a connection string named Sample07Context in the web.config file. /// </summary> public Sample07Context() : base("Sample07Context") { } /// <summary> /// To change the connection string at runtime. See the SmObjectFactory class for more info. /// </summary> public Sample07Context(string connectionString) : base(connectionString) { //Note: defaultConnectionFactory in the web.config file should be set. } public void SetConnectionString(string connectionString) { this.Database.Connection.ConnectionString = connectionString; } } }
یک متد دیگر هم در اینجا در انتهای کلاس به نام SetConnectionString تعریف شدهاست. از این متد در حین ورود کاربر به سایت میتوان استفاده کرد. برای مثال حداقل دو نوع طراحی را میتوان درنظر گرفت:
الف) کاربر با برنامهای کار میکند که به ازای سالهای مختلف، بانکهای اطلاعاتی مختلفی دارد و در ابتدای ورود، یک drop down انتخاب سال کاری برای او درنظر گرفته شدهاست (علاوه بر سایر ورودیهای استانداردی مانند نام کاربری و کلمهی عبور). در این حالت بهتر است متد SetConnectionString نام رشتهی اتصالی را بر اساس سال انتخابی، در حین لاگین دریافت کند که اطلاعات آن در فایل کانفیگ سایت پیشتر مشخص شدهاست.
ب) کاربر یا مشتری پس از ورود به سایت، نیاز است صرفا از بانک اطلاعاتی خاص خودش استفاده کند. بنابراین اطلاعات تعریف کاربران و مشتریها در یک بانک اطلاعاتی مجزا قرار دارند و پس از لاگین، نیاز است رشتهی اتصالی او به صورت پویا از بانک اطلاعاتی خوانده شده و سپس توسط متد SetConnectionString تنظیم گردد.
مدیریت سشنهای رشتهی اتصالی جاری
پس از اینکه کاربر، در حین ورود مشخص کرد که از چه بانک اطلاعاتی قرار است استفاده کند و یا اینکه برنامه بر اساس اطلاعات ثبت شدهی او تصمیمگیری کرد که باید از کدام رشتهی اتصالی استفاده کند، نگهداری این رشتهی اتصالی نیاز به سشن دارد تا به ازای هر کاربر متصل به سایت منحصربفرد باشد. در مورد مدیریت سشنها در برنامههای وب، از نکات مطرح شدهی در مطلب «مدیریت سشنها در برنامههای وب به کمک تزریق وابستگیها» استفاده خواهیم کرد:
using System; using System.Threading; using System.Web; using EF_Sample07.DataLayer.Context; using EF_Sample07.ServiceLayer; using StructureMap; using StructureMap.Web; using StructureMap.Web.Pipeline; namespace EF_Sample07.IoCConfig { public static class SmObjectFactory { private static readonly Lazy<Container> _containerBuilder = new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication); public static IContainer Container { get { return _containerBuilder.Value; } } public static void HttpContextDisposeAndClearAll() { HttpContextLifecycle.DisposeAndClearAll(); } private static Container defaultContainer() { return new Container(ioc => { // session manager setup ioc.For<ISessionProvider>().Use<DefaultWebSessionProvider>(); ioc.For<HttpSessionStateBase>() .Use(ctx => new HttpSessionStateWrapper(HttpContext.Current.Session)); ioc.For<IUnitOfWork>() .HybridHttpOrThreadLocalScoped() .Use<Sample07Context>() // Remove these 2 lines if you want to use a connection string named Sample07Context, defined in the web.config file. .Ctor<string>("connectionString") .Is(ctx => getCurrentConnectionString(ctx)); ioc.For<ICategoryService>().Use<EfCategoryService>(); ioc.For<IProductService>().Use<EfProductService>(); ioc.For<ICategoryService>().Use<EfCategoryService>(); ioc.For<IProductService>().Use<EfProductService>(); ioc.Policies.SetAllProperties(properties => { properties.OfType<IUnitOfWork>(); properties.OfType<ICategoryService>(); properties.OfType<IProductService>(); properties.OfType<ISessionProvider>(); }); }); } private static string getCurrentConnectionString(IContext ctx) { if (HttpContext.Current != null) { // this is a web application var sessionProvider = ctx.GetInstance<ISessionProvider>(); var connectionString = sessionProvider.Get<string>("CurrentConnectionString"); if (string.IsNullOrWhiteSpace(connectionString)) { // It's a default connectionString. connectionString = "Database2012"; // this session value should be set during the login phase sessionProvider.Store("CurrentConnectionStringName", connectionString); } return connectionString; } else { // this is a desktop application, so you can store this value in a global static variable. return "Database2012"; } } } }
نکتهی مهم این تنظیمات، قسمت مقدار دهی سازندهی کلاس Context برنامه به صورت پویا توسط IoC Container جاری است. در اینجا هر زمانیکه قرار است وهلهای از Sample07Context ساخته شود، از سازندهی دوم آن که دارای پارامتری به نام connectionString است، استفاده خواهد شد. همچنین مقدار آن به صورت پویا از متد getCurrentConnectionString که در انتهای کلاس تعریف شدهاست، دریافت میگردد.
در این متد ابتدا مقدار HttpContext.Current بررسی شدهاست. این مقدار اگر نال باشد، یعنی برنامهی جاری یک برنامهی دسکتاپ است و مدیریت رشتهی اتصالی جاری آنرا توسط یک خاصیت Static یا Singleton تعریف شدهی در برنامه نیز میتوان تامین کرد. از این جهت که در هر زمان، تنها یک کاربر در App Domain جاری برنامهی دسکتاپ میتواند وجود داشته باشد و Singleton یا Static تعریف شدن اطلاعات رشتهی اتصالی، مشکلی را ایجاد نمیکند. اما در برنامههای وب، چندین کاربر در یک App Domain به سیستم وارد میشوند. به همین جهت است که مشاهده میکنید در اینجا از تامین کنندهی سشن، برای نگهداری اطلاعات رشتهی اتصالی جاری کمک گرفته شدهاست.
کلید این سشن نیز در این مثال مساوی CurrentConnectionStringName تعریف شدهاست. بنابراین در حین لاگین موفقیت آمیز کاربر، دو مرحلهی زیر باید طی شوند:
sessionProvider.Store("CurrentConnectionString", "Sample07Context"); uow.SetConnectionString(WebConfigurationManager.ConnectionStrings[_sessionProvider.Get<string>("CurrentConnectionString")].ConnectionString);
سپس از متد SetConnectionString برای خواندن مقدار نام مشخص شده در سشن CurrentConnectionStringName کمک گرفتهایم. هرچند سازندهی کلاس Context برنامه، هر دو حالت استفاده از نام رشتهی اتصالی و یا مقدار کامل رشتهی اتصالی را پشتیبانی میکند، اما خاصیت this.Database.Connection.ConnectionString تنها رشتهی کامل اتصالی را میپذیرد (بکار رفته در متد SetConnectionString).
تا اینجا کار پویا سازی انتخاب و استفاده از رشتهی اتصالی برنامه به پایان میرسد. هر زمانیکه قرار است Context برنامه توسط IoC Container نمونه سازی شود، به متد getCurrentConnectionString رجوع کرده و مقدار رشتهی اتصالی را از سشن تنظیم شدهای به نام CurrentConnectionStringName دریافت میکند. سپس از مقدار آن جهت مقدار دهی سازندهی دوم کلاس Context استفاده خواهد کرد.
مدیریت migrations خودکار برنامه در حالت استفاده از چندین بانک اطلاعاتی
یکی از مشکلات کار با برنامههای چند دیتابیسی، به روز رسانی ساختار تمام بانکهای اطلاعاتی مورد استفاده، پس از تغییری در ساختار مدلهای برنامه است. از این جهت که اگر تمام بانکهای اطلاعاتی به روز نشوند، کوئریهای جدید برنامه که از خواص و فیلدهای جدید استفاده میکنند، دیگر کار نخواهند کرد. پویا سازی اعمال این تغییرات را میتوان به صورت ذیل انجام داد:
using System; using System.Data.Entity; using System.Web; using EF_Sample07.DataLayer.Context; using EF_Sample07.IoCConfig; namespace EF_Sample07.WebFormsAppSample { public class Global : HttpApplication { void Application_Start(object sender, EventArgs e) { initDatabases(); } private static void initDatabases() { // defined in web.config string[] connectionStringNames = { "Sample07Context", "Database2012" }; foreach (var connectionStringName in connectionStringNames) { Database.SetInitializer( new MigrateDatabaseToLatestVersion<Sample07Context, Configuration>(connectionStringName)); using (var ctx = new Sample07Context(connectionStringName)) { ctx.Database.Initialize(force: true); } } } void Application_EndRequest(object sender, EventArgs e) { SmObjectFactory.HttpContextDisposeAndClearAll(); } } }
در این مثال خاص، متد initDatabases در حین آغاز برنامه فراخوانی شدهاست. منظور این است که اینکار در طول عمر برنامه تنها کافی است یکبار انجام شود و پس از آن است که EF Code first میتواند از رشتههای اتصالی متفاوتی که به آن ارسال میشود، بدون مشکل استفاده کند. زیرا اطلاعات نگاشت کلاسهای مدل برنامه به جداول بانک اطلاعاتی به این ترتیب است که کش میشوند و یا بر اساس کلاس Configuration به صورت خودکار به بانک اطلاعاتی اعمال میگردند.
کدهای کامل این مثال را که در حقیقت نمونهی بهبود یافتهی مطلب «EF Code First #12» است، از اینجا میتوانید دریافت کنید:
UoW-Sample
- این سیستم مدنظر شما نیست؟ اشکالی ندارد! از صفر خودتان آنرا بنویسید و تنظیم کنید: «اعتبارسنجی مبتنی بر کوکیها در ASP.NET Core 2.0 بدون استفاده از سیستم Identity»
- در Identity به ازای هر نقش کاربر، یک select به بانک اطلاعاتی وجود ندارد. دقیقا پس از لاگین، اطلاعات User Claims در کوکی شخص به صورت رمزنگاری شده، کش میشوند و در دفعات بعدی/درخواستهای بعدی، از این کوکی خوانده خواهند شد. اما در پروژهی DNT Identity به ازای هر درخواست، طوری تنظیم شده تا وضعیت اعتبار کاربر، بررسی شود و اینکار توسط تنظیم enableImmediateLogout و ValidationInterval = TimeSpan.Zero آن انجام میشود. چرا؟ چون اگر در سمت سرور، کاربری را غیرفعال کردید، آیا باید تا زمان منقضی شدن اعتبار کوکی آن، بتواند از سایت استفاده کند؟ بله. این مورد هزینهی اعتبارسنجی User Claims و در نتیجه، کوئری گرفتن از بانک اطلاعاتی را به ازای هر درخواست، به همراه دارد؛ ولی ضروری است. یعنی این سیستم صرفا هر کوکی را که شامل یکسری User Claims باشد، معتبر نمیداند.
با سلام و تشکر بابت همه زحمات. میخواستم بدونم برای لاگین کردن به پروژه فعلی از طریق Xamarin چه تنظیماتی در سمت کلاینت بایستی انجام بشه. من در فایل Config کلاینتی با مشخصات زیر اضافه کردم:
new Client { ClientName = "Native Client (Hybrid with PKCE)", ClientId = "native.hybrid", AllowedGrantTypes = GrantTypes.Hybrid, RedirectUris = new List<string> { "https://notused" }, PostLogoutRedirectUris = new List<string> { "https://notused" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Address, "roles", "imagegalleryapi", "country", "subscriptionlevel" }, ClientSecrets = { new Secret("secret".Sha256()) }, AllowOfflineAccess = true, RequireClientSecret = false, RequireConsent = false, RequirePkce = true, UpdateAccessTokenClaimsOnRefresh = true, AccessTokenType = AccessTokenType.Reference, RefreshTokenUsage = TokenUsage.ReUse }
var options = new OidcClientOptions { Authority = "https://localhost:6001", ClientId = "native.hybrid", ClientSecret = "secret", Scope = "openid profile email imagegalleryapi", RedirectUri = "xamarinformsclients://callback", Browser = browser, ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect };
System.InvalidOperationException: 'Error loading discovery document: Error connecting to https://localhost:6001/.well-known/openid-configuration. Network subsystem is down.'