خطای Provider
با کلیدهای ترکیبی Ctrl + Tab و Ctrl + Shift + Tab میتوانید بین پنجرههای Active جابجا شوید. با کلیدهای جهتنما هم میتوانید به بقیه پنجرهها از جمله Properties منتقل شوید. ناگفته نماند که این امکان در اکثر قسمتهای ویندوز و نرم افزارهایی مثل کروم و حتی SSMS نیز وجود دارد.
به طور کلی کلید شیفت برای دو کاربرد «برگشت از عملیات» و «انتخاب» استفاده میشود.
برای اینکه جستجویی را فیلتر کنید، میتوانید از الگوهای زیر استفاده کنید:
f جستجو در فایلها؛ r برای فایلهای اخیر؛ t جستجو در تایپها (مثل کلاس)؛ m برای جستجو در اعضا (متدها، پراپرتیها و Eventها).
برای مثال: اگر بنویسید t Controller، فقط Typeها را جستوجو میکند و نمایش میدهد.
۳) کلیپبورد چرخشی : «Ctrl + Shift + V»
ویژوال استودیو تا ۲۰ مقدار کپیشده قبلی را در حافظه خود ذخیره نگه میدارد. با دکمههای ترکیبی گفته شده میتوان از مقادیر قبلی کلیپبورد هم استفاده کرد. این قابلیت از ویژوال استودیو ۲۰۰۳ وجود داشته است.
۴) اجرای سریع : « Ctrl + Q »
برای دسترسی سریع به تنظیمات ویژوال استودیو (مثل فونت) یا بازکردن پنجرههایی که کلید میانبر استاندارد برای آنها تعریف نشدهاست، میتوان از این منو استفاده کرد.
حتی میتوان @nuget را نوشت، سپس اسم پکیج موردنظر نوشته میشود. با انجام این کار Manage NuGet Packages باز میشود و پکیجهای مرتبط نمایش داده میشوند.
۵) پیدا کردن ابتدا و انتهای براکت: « Ctrl + ] »
کاربرد این روش در هنگامی است با Legacy codeی مواجه میشوید که بلوکهای بزرگی از کد را دارد که فهم ابتدا و انتهای آن سخت است. با زدن این کلیدها میتوانید به محلی که براکت باز و بسته مربوطه هست، منتقل شوید.
۶) رفتن به محل کرسر قبلی: « Ctrl + - »
با زدن کلیکهای کنترل و Dash میتوانید به جاهایی که قبلا کرسر بوده است، بروید. البته اگر Shift را هم بگیرید، برعکس این کار انجام میشود.
منابع:
- استفاده از T4MVC اجباری است. به هیچ عنوان نباید از رشتهها برای مشخص سازی نام کنترلرها یا اکشن متدها در قسمتهای مختلف برنامه استفاده شود.
- تا حد امکان از ViewBag ، ViewData و امثال آن استفاده نشده و به ازای هر View یک مدل متناظر (ViewModel) ایجاد شود.
- فایل پروژه برنامه توسط یک ادیتور متنی ویرایش شده و MvcBuildViews آن به True تنظیم شود.
- مدلهای متناظر با جداول بانک اطلاعاتی نباید مستقیما در Viewهای برنامه استفاده شوند.
- پوشه Models، از پروژه اصلی حذف شود. یک پروژه class library جدید به نام MyProjectName.Models برای نگهداری ViewModels ایجاد گردد.
- یک پروژه Class library دیگر به نام MyProjectName.DomainClasses برای نگهداری کلاسهای متناظر با جداول بانک اطلاعاتی ایجاد شود.
- از سیستم minification و bundling، برای یکی سازی اسکریپتها و CSSهای برنامه استفاده شود.
- قسمت custom errors فایل web.config برنامه به نحو صحیحی مقدار دهی شود.
- تمام فرمهای عمومی برنامه باید دارای AntiForgeryToken باشند.
- تمام فرمهای عمومی برنامه باید captcha داشته باشند.
- پوشههای Content و Scripts از سیستم مسیریابی تعریف شده در Global.asax خارج شوند.
- MvcHandler.DisableMvcResponseHeader = true به Application_Start اضافه شود.
- اگر فقط از Razor به عنوان ViewEngine استفاده میشود، در Application_Start، باید سایر ViewEngineهای مورد استفاده، حذف شوند.
- فیلتر پیش فرض مدیریت خطاها حذف و بجای آن از ELMAH استفاده شود.
- در web.config، مقادیر executionTimeout و maxRequestLength مرتبط با httpRuntime تنظیم شوند. همچنین enableVersionHeader آن نیز خاموش گردد.
- استفاده از سشنها کلا باید حذف شود. ماژول توکار آن از قسمت httpModules حذف گردد تا پردازش موازی صفحات فعال گردد. (سشن مربوط است به دوران ASP کلاسیک دهه نود و هیچ نیازی به استفاده از آن در MVC نیست)
- در هیچ کنترلری نباید جزئیات پیاده سازی متدی مشاهده شود. تمام پیاده سازیها باید به لایه سرویسهای مختلف برنامه منتقل و از طریق تزریق وابستگیها در دسترس باشند.
- اگر نیاز به مشخص سازی آدرسی در سایت است (خصوصا در اسکریپتها) باید از Url.Action استفاده شود و نه رشتهها.
- بهتر است بومی سازی برنامه از روز اول آن درنظر گرفته شده و تمام عبارات مورد استفاده در فایلهای Resource درج شوند.
- برای مدیریت سادهتر بستههای مورد استفاده (وابستگیهای برنامه) بهتر است از NuGet استفاده شود.
- از یک ماژول HTTP compression مستقل و با کیفیت استفاده شود (برای سازگاری بهتر با نگارشهای مختلف IIS).
- برای معرفی HTTP modules و سادگی تعریف و فعال سازی آنها در انواع و اقسام IISها بهتر است از کتابخانه WebActivator استفاده شود.
- امکان دوبار کلیک کردن بر روی تمام دکمهها نباید وجود داشته باشد.
- از هشهای ترکیبی استفاده شود. مستقیما از MD5 یا SHA1 استفاده نشود.
- با اسکریپتهای anti IE6,7، این مرورگرها به رحمت ایزدی واصل شوند.
- اگر کاربری JavaScript را در مرورگر خود غیرفعال کرد، نباید بتواند از سایت استفاده کند.
- کلیه تغییرات تنظیمات و محتوای مهم سایت باید برای مدیر سایت بلافاصله ایمیل شوند.
- یک سری کارهای متداول مانند تهیه فایلهای favicon.ico، apple-touch-icon-XxY.png، crossdomain.xml، robots.txt و sitemap.xml (ترجیحا پویا) فراموش نشود.
- در web.config و در زمان ارائه، compilation debug=false تنظیم شود.
- در تمام قسمتهایی که AlllowHtml فعال شده باید از پاکسازی Html دریافتی جهت مقابله با XSS مطمئن شد.
- جهت سهولت طراحی table less از یک فریم ورک CSS ایی استفاده شود.
- در تمام قسمتهایی که فایلی آپلود میشود باید بررسی شود فایلهای نا امن (فایلهای اجرایی ASP.NET) قابل آپلود نباشند.
- حین کار با بانکهای اطلاعاتی یا از ORM استفاده شود و یا از کوئریهای پارامتری.
- هر برنامه ASP.NET باید داری یک Application pool مجزا به همراه تنظیمات حافظه مشخصی باشد.
- تمام صفحات باید عنوان داشته باشند. به همین منظور مقدار دهی پیش فرض آن در فایل ViewStart صورت گیرد.
- در صفحه لاگین سایت، autocomplete خاموش شود.
- تمام deleteهای برنامه باید به HttpPost محدود شوند. تمام گزارشات و نمایش اطلاعات غیرعمومی برنامه به HttpGet محدود شوند.
- تعداد رفت و برگشتهای به بانک اطلاعاتی در یک صفحه توسط پروفایلرها بررسی شده و اطلاعات عمومی پرمصرف کش شوند.
- در هیچکدام از کلاسهای ASP.NET MVC نباید از HttpContext مستقیما استفاده شود. کلاس پایه جدید آن باید مورد استفاده قرار گیرد یا از Action Result صحیحی استفاده گردد.
- کش کردن فایلهای استاتیک درنظر گرفته شود.
- تمام درخواستهای jQuery Ajax باید بررسی شوند. آیا واقعا مرتبط هستند به سایت جاری و آیا واقعا Ajax ایی هستند.
یک نکته:
امکان تهیه قالبهای سفارشی VS.NET و لحاظ موارد فوق در آن جهت استفادههای بعدی نیز وجود دارد:
Create Reusable Project And Item Templates For Your Development Team
Write Templates for Visual Studio 2010
Building a Custom Project Wizard in Visual Studio .NET
کتابخانه پرادزش متن و نمایش ایموجی
جایگزینهای ReSharper برای VS 2015
در پستهای قبلی مروری بر jQuery داشته و در چند پست انواع روشهای انتخاب عناصر صفحه وب را توسط jQuery بررسی کردیم. در پستهای آینده با مباحث پیشرفتهتری همچون انجام عملیاتی روی المانهای انتخاب شده، خواهیم پرداخت؛ امید است مفید واقع شود.
٢ -٢ - ایجاد عناصر HTML جدید
گاهی اوقات نیاز میشود که یک یا چند عنصر جدید به صفحهی در حال اجرا اضافه شوند. این حالت میتواند به سادگی قرار گرفتن یک متن در جایی از صفحه و یا به پیچیدگی ایجاد و نمایش یک جدول حاوی اطلاعات دریافت شده از بانک اطلاعاتی باشد.
ایجاد عناصر به صورت پویا در یک صفحه در حال اجرا کار ساده ای برای jQuery میباشد، زیرا همانطور که در پست آموزش (jQuery) جی کوئری 1# مشاهده کردیم ()$ با دریافت دستور ساخت یک عنصر HTML آن را در هر زمان ایجاد میکند، دستور زیر :
$("<div>Hello</div>")
دقت کنید که یک راه کوتاهتر نیز برای ایجاد یک عنصر <div> خالی وجود دارد که به شکل زیر است:
$("<div>") // همه اینها معادل هستند
$("<div></div>")
$("<div/>")
برای اینکه مزه اینکار را بچشید بد نیست نگاهی به مثال زیر بیندازید (نگران قسمتهای نامفهوم نباشید به مرور با آنها آشنا خواهیم شد):
$("<div class='foo'>I have foo!</div><div>I don't</div>") .filter(".foo").click(function() { alert("I'm foo!"); }).end().appendTo("#someParentDiv");
برای اجرا این کد میتوانید کد آن را دانلود کرده و فایل chapter2/new.divs.htmlرا اجرا کنید خروجی مانند تصویر زیر خواهد بود:
جهت تکمیل مطلب فعلی یک مثال کاملتر از این سایت جهت بررسی انتخاب کردم:
$( "<div/>", { "class": "test", text: "Click me!", click: function() { $( this ).toggleClass( "test" ); } }).appendTo( "body" );
با توجه به اینکه مطالب بعدی طولانی بوده و تقریبا مبحث جدایی است؛ در پست بعدی به بررسی توابع و متدهای مدیریت مجموعه انتخاب شده خواهیم پرداخت.
CREATE TABLE accounts ( user_id INTEGER PRIMARY KEY, balance INTEGER NOT NULL );
INSERT INTO accounts(user_id, balance) VALUES (1, 300);
DECLARE @amount INT; SET @amount = ( SELECT balance FROM accounts WHERE user_id = 1 ); SELECT @amount as 'balance' UPDATE accounts SET balance = @amount - 100 WHERE user_id = 1; SELECT balance as 'balance after shopping' FROM accounts WHERE user_id = 1
- در اینجا مقدار متغیر amount در ابتدای کار، مساوی 300 است که مربوط به همان insert ابتدایی است.
- سپس از این مقدار در کوئری دومی (برای مثال حاصل از خرید شماره یک)، 100 واحد کم میشود (برای مثال قیمت کل خرید است).
- در این حالت نتیجهی آن یا همان موجودی جدید کاربر، 200 خواهد بود.
معادل این عملیات در EF-Core چنین دستورات متداولی است:
var account1 = context.Accounts.First(x => x.UserId == 1); account1.Balance -= 100; context.SaveChanges();
سؤال: اگر کوئریهای فوق را در یک برنامهی ذاتا چند ریسمانی وب، دوبار به صورت همزمان اجرا کنیم، یعنی دو عمل خرید موازی را شبیه سازی کنیم، چه اتفاقی رخ میدهد؟ آیا موجودی نهایی اینبار برای مثال 100 میشود (با فرض 300 بودن موجودی ابتدایی)؟
پاسخ خیر است! و آنرا میتوانید در تصویر زیر مشاهده کنید:
در اینجا برای شبیه سازی اجرای موازی دو کوئری، از دستور WAITFOR TIME استفاده شدهاست که برای برای آزمایش آن میتوانید مقدار آنرا به یک دقیقه بعد تنظیم کرده و سپس آنرا در دو پنجرهی SQL server management studio اجرا کنید.
همانطور که مشاهده میکنید، با اجرای موازی این دو کوئری، یعنی دوبار خرید کردن همزمان، 100 واحد گم شدهاست ! به این مشکل همزمانی read و سپس update رخ داده، یک «race condition» گفته میشود و این روزها که مطالب منتشر شدهی از آسیب پذیریهای برنامههای وب ایرانی را بررسی میکنم، این مورد در صدر آنها قرار دارد!
علت اینجا است که عموما برنامه نویسها، برنامههای وب را در یک تک سشن باز شدهی توسط مرورگر خود آزمایش میکنند و در این حالت، همه چیز خوب است و اعمال آن به ترتیب پیش میروند. اما فراموش میکنند که میتوان قسمتهای مختلف برنامههای وب را به صورت همزمان، موازی و چندباره نیز اجرا کرد؛ حتی اگر آن قسمت متعلق به یک کاربر باشد.
سؤال: آیا استفاده تراکنشها این مشکل را حل نمیکنند؟!
عموما برنامه نویسها تصور میکنند که میتوانند تمام اینگونه مشکلات را با تراکنشها حل کنند:
همانطور که مشاهده میکنید، اینبار هرچند هر دو عملیات خرید داخل BEGIN TRAN و COMMIT TRAN قرار گرفتهاند، اما ... مشکل همزمانی هنوز پابرجا است! چون نوع پیشفرض تراکنش مورد استفاده، READ COMMITTED isolation level است و عدم دقت به آن ممکن است این تصور را ایجاد کند که با تعریف تراکنشها، تمام مشکلات همزمانی برطرف میشوند.
راهحلهای پیشنهادی جهت حل مشکل همزمانی عملیات read/update
برای حل مشکلات مرتبط با race condition و همزمانی درخواستهای read/update، میتوان از یکی از روشهای زیر استفاده کرد:
الف) بجای اینکه یکبار کوئری read و یکبار کوئری update به صورت جداگانه صادر شوند، فقط یکبار کوئری update داشته باشیم.
ب) پیاده سازی Row level locking؛ در صورت پشتیبانی بانک اطلاعاتی مورد استفاده از آن
ج) استفاده از تراکنشهایی از نوع SERIALIZABLE
د) پیاده سازی optimistic locking
این موارد را در ادامه با توضیحات بیشتری بررسی میکنیم.
الف) پرهیز از خواندن و به روز رسانی جداگانه
بجای اینکه مانند اعمال فوق، یکبار select داشته باشیم و یکبار update، بهتر است فقط یک دستور update بکارگرفته شود:
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
اینبار با خلاصه شدن دو دستور select و update به یک دستور update، دیگر پس از دو خرید همزمان، 100 واحد گم شده مشاهده نمیشود (!) و موجودی نهایی صحیح است.
ب) پیاده سازی Row level locking
همیشه امکان تغییر عملیات مورد نیاز، به سادگی حالت الف نیست. در یک چنین حالتهایی جهت حداقل شدن تغییرات مورد نیاز، میتوان از row level locking استفاده کرد:
WAITFOR TIME '13:47:00'; SET NOCOUNT, XACT_ABORT ON; BEGIN TRAN; DECLARE @amount INT; SET @amount = ( SELECT balance FROM accounts WITH (UPDLOCK, HOLDLOCK) WHERE user_id = 1 ); SELECT @amount as 'initial user''s balance' UPDATE accounts SET balance = @amount - 100 WHERE user_id = 1; SELECT balance as 'user''s balance after shopping 1' FROM accounts WHERE user_id = 1; COMMIT TRAN;
در اینجا اضافه شدن WITH (UPDLOCK, HOLDLOCK) را به Select تعریف شده، مشاهده میکنید که به آنها locking hints هم گفته میشود و داخل BEGIN TRAN و COMMIT TRAN عمل میکنند (که نوع پیشفرض آن READ COMMITTED isolation level است). کار UPDLOCK، تبدیل shared lock پیشفرض، به update lock است و کار HOLDLOCK، نگه داشتن قفل صورت گرفته تا پایان کار تراکنش تعریف شدهاست.
با این تغییرات، هر تراکنش همزمان دیگری، تا زمانیکه قفل صورت گرفتهی بر روی ردیف select، رها نشود (یعنی تا زمانیکه تراکنش قفل کننده، به COMMIT TRAN برسد)، نمیتواند آنرا تغییر دهد. به همین جهت است که در تصویر فوق، هرچند هر دو عملیات همزمان اجرا شدهاند، اما یکی موجودی ابتدایی 300 را میبیند و دیگری پس از صبر کردن تا پایان تراکنش و رها شدن قفل، موجودی تغییر یافتهی جدیدی را مشاهده کرده و از آن استفاده میکند. به این ترتیب دیگر 100 واحدی که در اولین تصویر این مطلب مشاهده کردید، گم نشدهاست.
ج) استفاده از تراکنشهایی از نوع SERIALIZABLE
بجای استفاده از روش row level locking یاد شده، روش دیگری را که میتوان استفاده کرد، تغییر نوع پیشفرض تراکنش مورد استفادهاست. برای مثال اگر از یک SERIALIZABLE transaction استفاده کنیم؛ یعنی SET TRANSACTION ISOLATION LEVEL SERIALIZABLE را در ابتدای کار ذکر کنیم و برای مثال دو تراکنش همزمان را اجرا کنیم، اگر در تراکنش اول اطلاعاتی خوانده شود، در هیچ تراکنش دیگری نمیتوان این اطلاعات خوانده شده را تا پایان کار تراکنش اول، تغییر داد:
د) پیاده سازی optimistic locking
پیاده سازی optimistic locking و یا Optimistic concurrency control عموما در سمت برنامه رخ میدهد و توسط ORMها زیاد مورد استفاده قرار میگیرد؛ مانند اضافه کردن ستون اضافی version و یا timestamp به جداول تعریف شده. در این حالت تمام updateها به همراه یک where اضافی هستند تا بررسی کنند که آیا version دریافتی در حین خواندن ردیف در حال به روز رسانی، تغییر کردهاست یا خیر؟ اگر تغییر کردهاست، تراکنش را با خطایی خاتمه خواهند داد. این روش برخلاف حالتهای ب و ج، حتی خارج از یک تراکنش نیز کار میکند و مشکلات قفل کردن طولانی مدت رکوردها توسط آنها را به همراه ندارد.