طراحی یک معماری خوب و مناسب یکی از عوامل مهم تولید یک برنامه
کاربردی موفق میباشد. بنابراین انتخاب یک ساختار مناسب به منظور تولید برنامه
کاربردی بسیار مهم و تا حدودی نیز سخت است. در اینجا یاد خواهیم گرفت که چگونه یک
طراحی مناسب را انتخاب نماییم. همچنین روشهای مختلف تولید برنامههای کاربردی را
که مطمئنا شما هم از برخی از این روشها استفاده نمودید را بررسی مینماییم و مزایا
و معایب آن را نیز به چالش میکشیم.
ضد الگو (Antipattern) – رابط کاربری هوشمند (Smart UI)
با استفاده از Visual Studio یا به اختصار VS، میتوانید برنامههای کاربردی را به راحتی تولید نمایید. طراحی رابط کاربری به آسانی عمل کشیدن و رها کردن (Drag & Drop) کنترلها بر روی رابط کاربری قابل انجام است. همچنین در پشت رابط کاربری (Code Behind) تمامی عملیات مربوط به مدیریت رویدادها، دسترسی به داده ها، منطق تجاری و سایر نیازهای برنامه کاربردی، کد نویسی خواهند شد. مشکل این نوع کدنویسی بدین شرح است که تمامی نیازهای برنامه در پشت رابط کاربری قرار میگیرند و موجب تولید کدهای تکراری، غیر قابل تست، پیچیدگی کدنویسی و کاهش قابلیت استفاده مجدد از کد میگردد.
به این روش کد نویسی Smart UI میگویند که موجب تسهیل تولید برنامههای کاربردی میگردد. اما یکی از مشکلات عمدهی این روش، کاهش قابلیت نگهداری و پشتیبانی و عمر کوتاه برنامههای کاربردی میباشد که در برنامههای بزرگ به خوبی این مشکلات را حس خواهید کرد.
از آنجایی که تمامی برنامه نویسان مبتدی و تازه کار، از جمله من و شما در روزهای اول برنامه نویسی، به همین روش کدنویسی میکردیم، لزومی به ارائه مثال در رابطه با این نوع کدنویسی نمیبینم.
تفکیک و جدا سازی اجزای برنامه کاربردی (Separating Your Concern)
راه حل رفع مشکل Smart UI، لایه بندی یا تفکیک اجزای برنامه از یکدیگر میباشد. لایه بندی برنامه میتواند به شکلهای مختلفی صورت بگیرد. این کار میتواند توسط تفکیک کدها از طریق فضای نام (Namespace)، پوشه بندی فایلهای حاوی کد و یا جداسازی کدها در پروژههای متفاوت انجام شود. در شکل زیر نمونه ای از معماری لایه بندی را برای یک برنامه کاربردی بزرگ میبینید.
به منظور پیاده سازی یک برنامه کاربردی لایه بندی شده و تفکیک اجزای برنامه از یکدیگر، مثالی را پیاده سازی خواهیم کرد. ممکن است در این مثال با مسائل جدید و شیوههای پیاده سازی جدیدی مواجه شوید که این نوع پیاده سازی برای شما قابل درک نباشد. اگر کمی صبر پیشه نمایید و این مجموعهی آموزشی را پیگیری کنید، تمامی مسائل نامانوس با جزئیات بیان خواهند شد و درک آن برای شما ساده خواهد گشت. قبل از شروع این موضوع را هم به عرض برسانم که علت اصلی این نوع پیاده سازی، انعطاف پذیری بالای برنامه کاربردی، پشتیبانی و نگهداری آسان، قابلیت تست پذیری با استفاده از ابزارهای تست، پیاده سازی پروژه بصورت تیمی و تقسیم بخشهای مختلف برنامه بین اعضای تیم و سایر مزایای فوق العاده آن میباشد.
1- Visual Studio را باز کنید و یک Solution خالی با نام SoCPatterns.Layered ایجاد نمایید.
· جهت ایجاد Solution خالی، پس از انتخاب New Project، از سمت چپ گزینه Other Project Types و سپس Visual Studio Solutions را انتخاب نمایید. از سمت راست گزینه Blank Solution را انتخاب کنید.
2- بر روی Solution کلیک راست نموده و از گزینه Add > New Project یک پروژه Class Library با نام SoCPatterns.Layered.Repository ایجاد کنید.
3- با استفاده از روش فوق سه پروژه Class Library دیگر با نامهای زیر را به Solution اضافه کنید:
- SoCPatterns.Layered.Model
- SoCPatterns.Layered.Service
- SoCPatterns.Layered.Presentation
4- با توجه به نیاز خود یک پروژه دیگر را باید به Solution اضافه نمایید. نوع و نام پروژه در زیر لیست شده است که شما باید با توجه به نیاز خود یکی از پروژههای موجود در لیست را به Solution اضافه کنید.
- Windows Forms Application (SoCPatterns.Layered.WinUI)
- WPF Application (SoCPatterns.Layered.WpfUI)
- ASP.NET Empty Web
Application (SoCPatterns.Layered.WebUI)
- ASP.NET MVC 4 Web
Application (SoCPatterns.Layered.MvcUI)
5- بر روی پروژه SoCPatterns.Layered.Repository کلیک راست نمایید و با انتخاب گزینه Add Reference به پروژهی SoCPatterns.Layered.Model ارجاع دهید.
6- بر روی پروژه SoCPatterns.Layered.Service کلیک راست نمایید و با انتخاب گزینه Add Reference به پروژههای SoCPatterns.Layered.Model و SoCPatterns.Layered.Repository ارجاع دهید.
7- بر روی پروژه SoCPatterns.Layered.Presentation کلیک راست نمایید و با انتخاب گزینه Add Reference به پروژههای SoCPatterns.Layered.Model و SoCPatterns.Layered.Service ارجاع دهید.
8- بر روی پروژهی UI خود به عنوان مثال SoCPatterns.Layered.WebUI کلیک راست نمایید و با انتخاب گزینه Add Reference به پروژههای SoCPatterns.Layered.Model، SoCPatterns.Layered.Repository، SoCPatterns.Layered.Service و SoCPatterns.Layered.Presentation ارجاع دهید.
9- بر روی پروژهی UI خود به عنوان مثال SoCPatterns.Layered.WebUI کلیک راست نمایید و با انتخاب گزینه Set as StartUp Project پروژهی اجرایی را مشخص کنید.
10- بر روی Solution کلیک راست نمایید و با انتخاب گزینه Add > New Solution Folder پوشههای زیر را اضافه نموده و پروژههای مرتبط را با عمل Drag & Drop در داخل پوشهی مورد نظر قرار دهید.
- 1. UI
- § SoCPatterns.Layered.WebUI
- 2. Presentation Layer
- § SoCPatterns.Layered.Presentation
- 3. Service Layer
- § SoCPatterns.Layered.Service
- 4. Domain Layer
- § SoCPatterns.Layered.Model
- 5. Data Layer
- § SoCPatterns.Layered.Repository
- توجه
داشته باشید که پوشه بندی برای مرتب سازی لایهها و دسترسی راحتتر به آنها میباشد.
پیاده سازی ساختار لایه بندی برنامه به صورت کامل انجام شد. حال به پیاده سازی کدهای مربوط به هر یک از لایهها و بخشها میپردازیم و از لایه Domain شروع خواهیم کرد.
اشتباهات هزینهبر در پروژههای نرم افزاری
واژه XSS مخفف Cross-site scripting، نوعی از آسیب پذیریست که در برنامههای تحت وب نمود پیدا میکند. به طور کلی و خلاصه، این آسیب پذیری به فرد نفوذ کننده اجازه تزریق اسکریپتهایی را به صفحات وب، میدهد که در سمت کاربر اجرا میشوند ( Client Side scripts ) . در نهایت این اسکریپتها توسط سایر افرادی که از صفحات مورد هدف قرار گرفته بازدید میکنند اجرا خواهد شد.
هدف از این نوع حمله :
بدست آوردن اطلاعات کوکیها و سشنهای کاربران ( مرتبط با آدرسی که صفحه آلوده شده در آن قرار دارد ) است. سپس فرد نفوذ کننده متناسب با اطلاعات بدست آمده میتواند به اکانت شخصی کاربران مورد هدف قرار گرفته، نفوذ کرده و از اطلاعات شخصی آنها سوء استفاده کند .
به صورت کلی دو طبقه بندی برای انواع حملات Cross-site scripting وجود دارند.
حملات XSS ذخیره سازی شده ( Stored XSS Attacks ) :
در این نوع ، کدهای مخرب تزریق شده، در سرور سایت قربانی ذخیره میشوند. محل ذخیره سازی میتواند دیتابیس سایت یا هر جای دیگری که دادهها توسط سایت یا برنامه تحت وب بازیابی میشوند و نمایش داده میشوند باشد. اما اینکه چگونه کدهای مخرب در منابع یاد شده ذخیره میشوند؟
فرض کنید در سایت جاری آسیب پذیری مذکور وجود دارد. راههای ارسال دادهها به این سایت چیست؟ نویسندگان میتوانند مطلب ارسال کنند و کاربران میتوانند نظر دهند. حال اگر در یکی از این دو بخش بررسیهای لازم جهت مقابله با این آسیب پذیری وجود نداشته باشد و نوشتههای کاربران که میتواند شامل کدهای مخرب باشد مستقیما در دیتابیس ذخیره شده و بدون هیچ اعتبار سنجی نمایش داده شود چه اتفاقی رخ خواهد داد؟ مسلما با بازدید صفحه آلوده شده، کدهای مخرب بر روی مرورگر شما اجرا و کوکیهای سایت جاری که متعلق به شما هستند برای هکر ارسال میشود و ...
حملات XSS منعکس شده ( Reflected XSS Attacks ) :
در این نوع از حمله، هیچ نوع کد مخربی در منابع ذخیره سازی وبسایت یا اپلیکیشن تحت وب توسط فرد مهاجم ذخیره نمیشود ! بلکه از ضعف امنیتی بخشهایی همچون بخش جستجو وب سایت، بخشهای نمایش پیغام خطا و ... استفاده میشود ... اما به چه صورت؟
در بسیاری از سایتها، انجمنها و سیستمهای سازمانی تحت وب، مشاهده میشود که مثلا در بخش جستجو، یک فیلد برای وارد کردن عبارت جستجو وجود دارد. پس از وارد کردن عبارت جستجو و submit فرم، علاوه بر نمایش نتایج جستجو، عبارت جستجو شده نیز به نمایش گذاشته میشود و بعضا در بسیاری از سیستمها این عبارت قبل از نمایش اعتبار سنجی نمیشود که آیا شامل کدهای مخرب میباشد یا خیر. همین امر سبب میشود تا اگر عبارت جستجو شامل کدهای مخرب باشد، آنها به همراه نتیجهی جستجو اجرا شوند.
اما این موضوع چگونه مورد سوء استفاده قرار خواهد گرفت؟ مگر نه اینکه این عبارت ذخیره نمیشود پس با توضیحات فوق، کد فقط بر روی سیستم مهاجم که کد جستجو را ایجاد میکند اجرا میشود، درست است؟ بله درست است ولی به نقطه ضعف زیر توجه کنید ؟
www.test.com/search?q=PHNjcmlwdD5hbGVydChkb2N1bWVudC5jb29raWUpOzwvc2NyaXB0Pg==
این آدرس حاصل submit شدن فرم جستجو وبسایت test (نام وبسایت واقعی نیست و برای مثال است ) و ارجاع به صفحه نتایج جستجو میباشد. در واقع این لینک برای جستجوی یک کلمه یا عبارت توسط این وبسایت تولید شده و از هر کجا به این لینک مراجعه کنید عبارت مورد نظر مورد جستجو واقع خواهد شد. در واقع عبارت جستجو به صورت Base64 به عنوان یک query String به وبسایت ارسال میشود؛ علاوه بر نمایش نتایج، عبارت جستجو شده نیز به کاربر نشان داده شده و اگر آسیب پذیری مورد بحث وجود داشته باشد و عبارت شامل کدهای مخرب باشد، کدهای مخرب بر روی مرورگر فردی که این لینک را باز کرده اجرا خواهد شد!
در این صورت کافیست فرد مهاجم لینک مخرب را به هر شکلی به فرد مورد هدف بدهد ( مثلا ایمیل و ... ). حال در صورتیکه فرد لینک را باز کند (با توجه به اینکه لینک مربوط به یک سایت معروف است و عدم آگاهی کاربر از آسیب پذیری موجود در لینک، باعث باز کردن لینک توسط کاربر میشود)، کدها بر روی مرورگرش اجرا شده و کوکیهای سایت مذکور برای مهاجم ارسال خواهد شد ... به این نوع حمله XSS ، نوع انعکاسی میگویند که کاملا از توضیحات فوق الذکر، دلیل این نامگذاری مشخص میباشد.
اهمیت مقابله با این حمله :
برای نمونه این نوع باگ حتی تا سال گذشته در سرویس ایمیل یاهو وجود داشت. به شکلی که یکی از افراد انجمن hackforums به صورت Private این باگ را به عنوان Yahoo 0-Day XSS Exploit در محیط زیر زمینی و بازار سیاه هکرها به مبلغ چند صد هزار دلار به فروش میرساند. کاربران مورد هدف کافی بود تا فقط یک ایمیل دریافتی از هکر را باز کنند تا کوکیهای سایت یاهو برای هکر ارسال شده و دسترسی ایمیلهای فرد قربانی برای هکر فراهم شود ... ( در حال حاظر این باگ در یاهو وجو ندارد ).
چگونگی جلوگیری از این آسیب پذیری
در این سری از مقالات کدهای پیرامون سرفصلها و مثالها با ASP.net تحت فریم ورک MVC و به زبان C# خواهند بود. هر چند کلیات مقابله با آسیب پذیری هایی از این دست در تمامی زبانها و تکنولوژیهای تحت وب یکسان میباشند.
خوشبختانه کتابخانهای قدرتمند برای مقابله با حمله مورد بحث وجود دارد با نام AntiXSS که میتوانید آخرین نسخه آن را با فرمان زیر از طریق nugget به پروژه خود اضافه کنید. البته ذکر این نکته حائز اهمیت است که Asp.net و فریم ورک MVC به صورت توکار تا حدودی از بروز این حملات جلوگیری میکند. برای مثال به این صورت که در View ها شما تا زمانی که از MvcHtmlString استفاده نکنید تمامی محتوای مورد نظر برای نمایش به صورت Encode شده رندر میشوند. این داستان برای Url ها هم که به صورت پیش فرض encode میشوند صدق میکند. ولی گاها وقتی شما برای ورود اطلاعات مثلا از یک ادیتور WYSWYG استفاده میکنید و نیاز دارید دادهها را بدون encoding رندر کنید. آنگاه به ناچار مجاب بر اعمال یک سری سیاستهای خاصتر بر روی داده مورد نظر برای رندر میشوید و نمیتوانید از encoding توکار فوق الذکر استفاده کنید. آنگاه این کتابخانه در اعمال سیاستهای جلوگیری از بروز این آسیب پذیری میتواند برای شما مفید واقع شود.
PM> Install-Package AntiXSS
… var reviewContent = model.UserReview; reviewContent = Microsoft.Security.Application.Encoder.HtmlEncode(review); …
امیدوارم در اولین بخش از این سری مقالات، به صورت خلاصه مطالب مهمی که باعث ایجاد فهم کلی در رابطه با حملات Xss وجود دارد، برای دوستان روشن شده و پیش زمینه فکری برای مقابله با این دست از حملات برایتان به وجود آمده باشد.
ASP.NET MVC #2
MVC چیست و اساس کار آن چگونه است؟
الگوی MVC در سالهای اول دهه 70 میلادی در شرکت زیراکس توسط خالقین زبان اسمالتاک که جزو اولین زبانهای شیءگرا محسوب میشود، ارائه گردید. نام MVC از الگوی Model-View-Controller گرفته شده و چندین دهه است که در صنعت تولید نرم افزار مورد استفاده میباشد. هدف اصلی آن جدا سازی مسئولیتهای اجزای تشکیل دهنده «لایه نمایشی» برنامه است.
این الگو در سال 2004 برای اولین بار در سکویی به نام Rails به کمک زبان روبی جهت ساخت یک فریم ورک وب MVC مورد استفاده قرار گرفت و پس از آن به سایر سکوها مانند جاوا، دات نت (در سال 2007)، PHP و غیره راه یافت.
تصاویری را از این تاریخچه در ادامه ملاحظه میکنید؛ از دکتر Trygve Reenskaug تا شرکت زیراکس و معرفی آن در Rails به عنوان اولین فریم ورک وب MVC.
C در MVC معادل Controller است. کنترلر قسمتی است که کار دریافت ورودیهای دنیای خارج را به عهده دارد؛ مانند پردازش یک درخواست HTTP ورودی.
زمانیکه کنترلر این درخواست را دریافت میکند، کار وهله سازی Model را عهده دار خواهد شد و حاوی اطلاعاتی است که نهایتا در اختیار کاربر قرار خواهد گرفت تا فرآیند پردازش درخواست رسیده را تکمیل نماید. برای مثال اگر کاربری جهت دریافت آخرین اخبار به سایت شما مراجعه کرده است، در اینجا کار تهیه لیست اخبار بر اساس مدل مرتبط به آن صورت خواهد گرفت. بنابراین کنترلرها، پایه اصلی و مدیر ارکستر الگوی MVC محسوب میشوند.
در ادامه، کنترلر یک View را جهت نمایش Model انتخاب خواهد کرد. View در الگوی MVC یک شیء ساده است. به آن میتوان به شکل یک قالب که اطلاعاتی را از Model دریافت نموده و سپس آنها را در مکانهای مناسبی در صفحه قرار میدهد، نگاه کرد.
نتیجه استفاده از این الگو، ایزوله سازی سه جزء یاد شده از یکدیگر است. برای مثال View نمیداند و نیازی ندارد که بداند چگونه باید از لایه دسترسی به اطلاعات کوئری بگیرد. یا برای مثال کنترلر نیازی ندارد بداند که چگونه و در کجا باید خطایی را با رنگی مشخص نمایش دهد. به این ترتیب انجام تغییرات در لایه رابط کاربری برنامه در طول توسعه کلی سیستم، سادهتر خواهد شد.
همچنین در اینجا باید اشاره کرد که این الگو مشخص نمیکند که از چه نوع فناوری دسترسی به اطلاعاتی باید استفاده شود. میتوان از بانکهای اطلاعاتی، وب سرویسها، صفها و یا هر نوع دیگری از اطلاعات استفاده کرد. به علاوه در اینجا در مورد نحوه طراحی Model نیز قیدی قرار داده نشده است. این الگو تنها جهت ساخت بهتر و اصولی «رابط کاربری» طراحی شده است و بس.
تفاوت مهم پردازشی ASP.NET MVC با ASP.NET Web forms
اگر پیشتر با ASP.NET Web forms کار کرده باشید اکنون شاید این سؤال برایتان وجود داشته باشد که این سیستم جدید در مقایسه با نمونه قبلی، چگونه درخواستها را پردازش میکند.
همانطور که مشاهده میکنید، در وب فرمها زمانیکه درخواستی دریافت میشود، این درخواست به یک فایل موجود در سیستم مثلا default.aspx ارسال میگردد. سپس ASP.NET یک کلاس وهله سازی شده معرف آن صفحه را ایجاد کرده و آنرا اجرا میکند. در اینجا چرخه طول عمر صفحه مانند page_load و غیره رخ خواهد داد. جهت انجام این وهله سازی، View به فایل code behind خود گره خورده است و جدا سازی خاصی بین این دو وجود ندارد. منطق صفحه به markup آن که معادل است با یک فایل فیزیکی بر روی سیستم، کاملا مقید است. در ادامه، این پردازش صورت گرفته و HTML نهایی تولیدی به مرورگر کاربر ارسال خواهد شد.
در ASP.NET MVC این نحوه پردازش تغییر کرده است. در اینجا ابتدا درخواست رسیده به یک کنترلر هدایت میشود و این کنترلر چیزی نیست جز یک کلاس مجزا و مستقل از هر نوع فایل ASPX ایی در سیستم. سپس این کنترلر کار پردازش درخواست رسیده را شروع کرده، اطلاعات مورد نیاز را جمع آوری و سپس به View ایی که انتخاب میکند، جهت نمایش نهایی ارسال خواهد کرد. در اینجا View این اطلاعات را دریافت کرده و نهایتا در اختیار کاربر قرار خواهد داد.
آشنایی با قرارداد یافتن کنترلرهای مرتبط
تا اینجا دریافتیم که نحوه پردازش درخواستها در ASP.NET MVC بر مبنای کلاسها و متدها است و نه بر مبنای فایلهای فیزیکی موجود در سیستم. اگر درخواستی به سیستم ارسال میشود، در ابتدا، این درخواست جهت پردازش، به یک متد عمومی موجود در یک کلاس کنترلر هدایت خواهد شد و نه به یک فایل فیزیکی ASPX (برخلاف وب فرمها).
همانطور که در تصویر مشاهده میکنید، در ابتدای پردازش یک درخواست، آدرسی به سیستم ارسال خواهد شد. بر مبنای این آدرس، نام کنترلر که در اینجا زیر آن خط قرمز کشیده شده است، استخراج میگردد (برای مثال در اینجا نام این کنترلرProducts است). سپس فریم ورک به دنبال کلاس این کنترلر خواهد گشت. اگر آنرا در اسمبلی پروژه بیابد، از آن خواهد خواست تا درخواست رسیده را پردازش کند؛ در غیراینصورت پیغام 404 یا یافت نشد، به کاربر نمایش داده میشود.
اما فریم ورک چگونه این کلاس کنترلر درخواستی را پیدا میکند؟
در زمان اجرا، اسمبلی اصلی پروژه به همراه تمام اسمبلیهایی که به آن ارجاعی دارند جهت یافتن کلاسی با این مشخصات اسکن خواهند شد:
1- این کلاس باید عمومی باشد.
2- این کلاس نباید abstract باشد (تا بتوان آنرا به صورت خودکار وهله سازی کرد).
3- این کلاس باید اینترفیس استاندارد IController را پیاده سازی کرده باشد.
4- و نام آن باید مختوم به کلمه Controller باشد (همان مبحث Convention over configuration یا کار کردن با یک سری قرار داد از پیش تعیین شده).
برای مثال در اینجا فریم ورک به دنبال کلاسی به نام ProductsController خواهد گشت.
شاید تعدادی از برنامه نویسهای ASP.NET MVC تصور کنند که فریم ورک در پوشهی استانداردی به نام Controllers به دنبال این کلاس خواهد گشت؛ اما در عمل زمانیکه برنامه کامپایل میشود، پوشهای در این اسمبلی وجود نخواهد داشت و همه چیز از طریق Reflection مدیریت خواهد شد.
تعریف Interaction Design در زبان طراحی، تعامل انسان و کامپیوتر و توسعه نرمافزار اینگونه بیان میشود:
« عمل طراحی تعاملی محصولات دیجیتالی، محیطها، سیستمها و سرویسها. مانند سایر رشتههای طراحی، Interaction Design دارای شاخهها و توجهاتی است، اما به طور ساده میتوان گفت که تمرکز اصلی این رشته برروی رفتارها است.»
طراحی تعاملی یا Interaction Design که به اختصار به آن IxD نیز گفته میشود، بر روی ایجاد واسطهای کاربری جذاب با رفتارهای خوب تمرکز دارد. فهم این نکته که کاربران و تکنولوژی چگونه با یکدیگر ارتباط دارند، در این شاخه بسیار مهم و ضروری است. با این درک، شما میتوانید موارد زیر را پیشبینی نماید: اینکه چگونه یک فرد با سیستم تعامل دارد؟ چگونه مشکلات را با داشتن آن سیستم رفع میکند؟ و در نهایت با استفاده از این موارد راههای جدیدی برای توسعه سیستم، برای انجام کارها پیشنهاد دهید. در ادامه به بررسی Best Practice های Interaction Design خواهیم پرداخت.
بهترین روشهای طراحی تعاملی ( Interaction Design )
در هنگام طراحی و توسعه یک محصول نرمافزاری با المانهای تعاملی، ویژگیها و سوالات مطرح شدهی زیر را در نظر بگیرید:
سوالات مهم در هنگام لحاظ کردن طراحی تعاملگرا
کاربران به چه صورتهایی میتوانند با واسط کاربری در ارتباط باشند | - کاربر چه تعاملاتی را میتواند به طور مستقیم با ماوس، انگشت یا stylus با واسط کاربری داشته باشد؟ - چه دستوراتی را کاربر میتواند صادر کند و با آنها تعامل داشته باشد که به طور مستقیم جزء محصول نیست؟ به عنوان مثال Ctrl+C که درون مرورگرها فعال است و جزئی از خود محصول نیست. |
دادن اطلاعاتی به کاربران، در مورد رفتارهای سیستم، پیش از انجام یک عمل | - ظاهر المانهای صفحه (رنگ، شکل، اندازه و ...) چه سرنخهایی را در مورد عملکرد آنها به کاربر خواهد داد؟ این المانها به کاربر میفهماند که چگونه باید از آنها استفاده کند. - شما چه اطلاعاتی را میتوانید در المانها بگنجانید که کاربر پیش از انجام یک عملیات از عملکرد آن المان مطلع شود؟ این مفاهیم میتوانند با گنجاندن label های با معنا در دکمهها، یا دستورالعملهای بسیار کوتاه برای تاییدیههای نهایی کامل شود. |
پیشبینی و کاهش خطاها | - آیا پیامهای خطا، راه روشنی را برای کاربر باز میکند تا بتواند مشکل کار خود را پیدا کند و منشا خطا را کشف نماید؟ - آیا در برخی موارد فشار و اجبار ( Constraint ) برای تحمیل عملیاتی خاص به کاربر جهت جلوگیری از خطا وجود دارد؟ اصل Poka-Yoka میگوید برای جلوگیری از سردرگمی کاربر و همچنین جلوگیری از خطاهای ممکن، در برخی موارد لازم است که کاربر را در محدودهای خاص و در یک مسیر مشخص (مانند مراحل تکمیل یک فرم) نگه داریم. این ایجاد فشار هم به کاربر کمک میکند و هم به تیم توسعه. |
در نظر گرفتن فیدبک و زمان پاسخ سیستم | - چگونه قرار است که به کاربر بازخورد بدهیم که پروسهای در حال اجرا است؟ هنگامیکه کاربر درگیر انجام عملیاتی است، سیستم باید متعاقبا یک پاسخ را برای کاربر نمایش دهد و چه بهتر که کاربر را در حین انجام پروسه (اگر پروسه طولانی باشد، مثلا بیش از 30 ثانیه) از آنچه که در سمت سرور صورت میگیرد آگاه سازد. این فرآیندها را میتوان با یک progress bar ساده مدل کرد. - بین یک عمل و پاسخ آن چه مدت زمانی طول خواهد کشید؟ واکنش پاسخ را میتوان در چهار سطح مشخص نمود: فوری یا immediate (کمتر از 0.1 ثانیه)، کند یا stammer (بین 0.1 تا 1 ثانیه)، وقفه یا interruption (بین 1 تا 10 ثانیه) و اختلال یا disruption (بیش از 10 ثانیه). |
نگاه استراتژیک دربارهی هر یک از عناصر درون صفحه | - آیا عناصر واسط کاربری اندازهی معقولی برای تعامل با کاربر دارند؟ عناصری مانند دکمهها، باید به اندازه کافی بزرگ باشند تا کاربر بتواند بر روی آنها کلیک کند. اما یک طراح نباید این نگاه را تنها به یک مرورگر منتهی کند. عمدهی مشکل در دستگاههای قابل حمل، مثل موبایلها و تبلتها رخ میدهد. - آیا لبهها و گوشهها (فضاهای خالی) به خوبی برای گنجاندن عناصر تعاملی مانند منوها استفاده شدهاند؟ یک قانون مهم در این زمینه میگوید که لبهها و گوشهها و نواحی مرزی، نواحی خوبی برای قرارگیری عناصر هستند. زیرا این نواحی معمولا نواحی مرزی هستند و کاربر به راحتی میتواند بر روی آنها کلیک و یا آنها را لمس نماید. - آیا شما از استانداردها پیروی میکنید؟ بالاخره کاربران آنقدرها هم بیاطلاع نیستند. آنها کمی هم دربارهی اینکه یک رابط کاربری چگونه است و عناصر آنها چگونه رفتار میکنند، اطلاعات دارد. پس، از این رو نیازی به خلق و بدعتگذاری نیست. تنها کافیاست اندکی از آنچه که در UX متداول شده، بهتر باشید. اگر روش شما بتواند خلاقانه و در عین حال ساده باشد، شما نیز میتوانید صاحب سبک شوید. |
سادهسازی برای افزایش سرعت یادگیری | - آیا اطلاعات مورد نیاز کاربر درون نرمافزار به هفت (به علاوه منهای دو) تکه تقسیم شدهاند؟ George Miller طی آزمایشاتی کشف کرد که افراد تنها قادرند پنج تا نه مورد را در حافظهی کوتاه مدت خود قرار دهند. - آیا واسط User End تا حد ممکن ساده شده است؟ قانون Tesler بیان میکند که شما باید سعی کنید که تمامی پیچیدگیها را تا آنجا که ممکن است از واسط User End حذف کنید. |
منابع:
عموما برای بومی سازی DataAnnotations، نیاز به قید resource name و resource type است. در اینجا resource name همان نام کلید منبع تعریف شدهاست و resource type به کلاس Designer.cs آن منبع اشاره میکند. برای تولید خودکار آن، نیاز است نام فایل منبع را «بدون قید نام زبان آن» درنظر گرفت؛ برای مثال Controllers.HomeController.resx. در این حالت به صورت خودکار فایل Controllers.HomeController.Designer.cs تولید خواهد شد که از نام کلاس آن (Controllers_HomeController) میتوان به عنوان مقدار resource type استفاده کرد. پس از آن فایل Controllers.HomeController.fa-IR.resx را ایجاد کنید تا به عنوان منبع زبان فارسی متناظر آن استفاده شود.
معرفی JSON Web Token
دو روش کلی و پرکاربرد اعتبارسنجی سمت سرور، برای برنامههای سمت کاربر وب وجود دارند:
الف) Cookie-Based Authentication که پرکاربردترین روش بوده و در این حالت به ازای هر درخواست، یک کوکی جهت اعتبارسنجی کاربر به سمت سرور ارسال میشود (و برعکس).
ب) Token-Based Authentication که بر مبنای ارسال یک توکن امضاء شده به سرور، به ازای هر درخواست است.
مزیتهای استفادهی از روش مبتنی بر توکن چیست؟
• Cross-domain / CORS: کوکیها و CORS آنچنان با هم سازگاری ندارند؛ چون صدور یک کوکی وابستهاست به دومین مرتبط به آن و استفادهی از آن در سایر دومینها عموما پذیرفته شده نیست. اما روش مبتنی بر توکن، وابستگی به دومین صدور آنرا ندارد و اصالت آن بر اساس روشهای رمزنگاری تصدیق میشود.
• بدون حالت بودن و مقیاس پذیری سمت سرور: در حین کار با توکنها، نیازی به ذخیرهی اطلاعات، داخل سشن سمت سرور نیست و توکن موجودیتی است خود شمول (self-contained). به این معنا که حاوی تمام اطلاعات مرتبط با کاربر بوده و محل ذخیرهی آن در local storage و یا کوکی سمت کاربر میباشد.
• توزیع برنامه با CDN: حین استفاده از روش مبتنی بر توکن، امکان توزیع تمام فایلهای برنامه (جاوا اسکریپت، تصاویر و غیره) توسط CDN وجود دارد و در این حالت کدهای سمت سرور، تنها یک API ساده خواهد بود.
• عدم در هم تنیدگی کدهای سمت سرور و کلاینت: در حالت استفادهی از توکن، این توکن میتواند از هرجایی و هر برنامهای صادر شود و در این حالت نیازی نیست تا وابستگی ویژهای بین کدهای سمت کلاینت و سرور وجود داشته باشد.
• سازگاری بهتر با سیستمهای موبایل: در حین توسعهی برنامههای بومی پلتفرمهای مختلف موبایل، کوکیها روش مطلوبی جهت کار با APIهای سمت سرور نیستند. تطابق یافتن با روشهای مبتنی بر توکن در این حالت سادهتر است.
• CSRF: از آنجائیکه دیگر از کوکی استفاده نمیشود، نیازی به نگرانی در مورد حملات CSRF نیست. چون دیگر برای مثال امکان سوء استفادهی از کوکی فعلی اعتبارسنجی شده، جهت صدور درخواستهایی با سطح دسترسی شخص لاگین شده وجود ندارد؛ چون این روش کوکی را به سمت سرور ارسال نمیکند.
• کارآیی بهتر: حین استفادهی از توکنها، به علت ماهیت خود شمول آنها، رفت و برگشت کمتری به بانک اطلاعاتی صورت گرفته و سرعت بالاتری را شاهد خواهیم بود.
• امکان نوشتن آزمونهای یکپارچگی سادهتر: در حالت استفادهی از توکنها، آزمودن یکپارچگی برنامه، نیازی به رد شدن از صفحهی لاگین را ندارد و پیاده سازی این نوع آزمونها سادهتر از قبل است.
• استاندارد بودن: امروزه همینقدر که استاندارد JSON Web Token را پیاده سازی کرده باشید، امکان کار با انواع و اقسام پلتفرمها و کتابخانهها را خواهید یافت.
اما JWT یا JSON Web Token چیست؟
JSON Web Token یا JWT یک استاندارد وب است (RFC 7519) که روشی فشرده و خود شمول (self-contained) را جهت انتقال امن اطلاعات، بین مقاصد مختلف را توسط یک شیء JSON، تعریف میکند. این اطلاعات، قابل تصدیق و اطمینان هستند؛ از اینرو که به صورت دیجیتال امضاء میشوند. JWTها توسط یک کلید مخفی (با استفاده از الگوریتم HMAC) و یا یک جفت کلید خصوصی و عمومی (توسط الگوریتم RSA) قابل امضاء شدن هستند.
در این تعریف، واژههایی مانند «فشرده» و «خود شمول» بکار رفتهاند:
- «فشرده بودن»: اندازهی شیء JSON یک توکن در این حالت کوچک بوده و به سادگی از طریق یک URL و یا پارامترهای POST و یا داخل یک HTTP Header قابل ارسال است و به دلیل کوچک بودن این اندازه، انتقال آن نیز سریع است.
- «خود شمول»: بار مفید (payload) این توکن، شامل تمام اطلاعات مورد نیاز جهت اعتبارسنجی یک کاربر است؛ تا دیگر نیازی به کوئری گرفتن هر بارهی از بانک اطلاعاتی نباشد (در این روش مرسوم است که فقط یکبار از بانک اطلاعاتی کوئری گرفته شده و اطلاعات مرتبط با کاربر را امضای دیجیتال کرده و به سمت کاربر ارسال میکنند).
چه زمانی بهتر است از JWT استفاده کرد؟
اعتبارسنجی: اعتبارسنجی یک سناریوی متداول استفادهی از JWT است. زمانیکه کاربر به سیستم لاگین کرد، هر درخواست بعدی او شامل JWT خواهد بود که سبب میشود کاربر بتواند امکان دسترسی به مسیرها، صفحات و منابع مختلف سیستم را بر اساس توکن دریافتی، پیدا کند. برای مثال روشهای «Single Sign On» خود را با JWT انطباق دادهاند؛ از این جهت که سربار کمی را داشته و همچنین به سادگی توسط دومینهای مختلفی قابل استفاده هستند.
انتقال اطلاعات: توکنهای با فرمت JWT، روش مناسبی جهت انتقال اطلاعات امن بین مقاصد مختلف هستند؛ زیرا قابل امضاء بوده و میتوان اطمینان حاصل کرد که فرستنده دقیقا همانی است که ادعا میکند و محتوای ارسالی دست نخوردهاست.
ساختار یک JWT به چه صورتی است؟
JWTها دارای سه قسمت جدا شدهی با نقطه هستند؛ مانند xxxxx.yyyyy.zzzzz و شامل header، payload و signature میباشند.
الف) Header
Header عموما دارای دو قسمت است که نوع توکن و الگوریتم مورد استفادهی توسط آن را مشخص میکند:
{ "alg": "HS256", "typ": "JWT" }
ب) payload
payload یا «بار مفید» توکن، شامل claims است. منظور از claims، اطلاعاتی است در مورد موجودیت مدنظر (عموما کاربر) و یک سری متادیتای اضافی. سه نوع claim وجود دارند:
Reserved claims: یک سری اطلاعات مفید و از پیش تعیین شدهی غیراجباری هستند؛ مانند:
iss یا صادر کنند (issuer)، exp یا تاریخ انقضاء، sub یا عنوان (subject) و aud یا مخاطب (audience)
Public claims: میتواند شامل اطلاعاتی باشد که توسط IANA JSON Web Token Registry پیشتر ثبت شدهاست و فضاهای نام آنها تداخلی نداشته باشند.
Private claims: ادعای سفارشی هستند که جهت انتقال دادهها بین مقاصد مختلف مورد استفاده قرار میگیرند.
یک نمونهی payload را در اینجا ملاحظه میکنید:
{ "sub": "1234567890", "name": "John Doe", "admin": true }
ج) signature
یک نمونه فرمول محاسبهی امضای دیجیتال پیام JWT به صورت ذیل است:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
یک نمونه مثال تولید این نوع توکنها را در آدرس https://jwt.io میتوانید بررسی کنید.
در این سایت اگر به قسمت دیباگر آن مراجعه کنید، برای نمونه قسمت payload آن قابل ویرایش است و تغییرات را بلافاصله در سمت چپ، به صورت انکد شده نمایش میدهد.
یک نکتهی مهم: توکنها امضاء شدهاند؛ نه رمزنگاری شده
همانطور که عنوان شد، توکنها از سه قسمت هدر، بار مفید و امضاء تشکیل میشوند (header.payload.signature). اگر از الگوریتم HMACSHA256 و کلید مخفی shhhh برای امضای بار مفید ذیل استفاده کنیم:
{ "sub": "1234567890", "name": "Ado Kukic", "admin": true }
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkbyBLdWtpYyIsImFkbWluIjp0cnVlLCJpYXQiOjE0NjQyOTc4ODV9.Y47kJvnHzU9qeJIN48_bVna6O0EDFiMiQ9LpNVDFymM
البته امکان رمزنگاری توسط JSON Web Encryption نیز پیش بینی شدهاست (JWE).
از JWT در برنامهها چگونه استفاده میشود؟
زمانیکه کاربر، لاگین موفقی را به سیستم انجام میدهد، یک توکن امن توسط سرور صادر شده و با فرمت JWT به سمت کلاینت ارسال میشود. این توکن باید به صورت محلی در سمت کاربر ذخیره شود. عموما از local storage برای ذخیرهی این توکن استفاده میشود؛ اما استفادهی از کوکیها نیز منعی ندارد. بنابراین دیگر در اینجا سشنی در سمت سرور به ازای هر کاربر ایجاد نمیشود و کوکی سمت سروری به سمت کلاینت ارسال نمیگردد.
سپس هر زمانیکه کاربری قصد داشت به یک صفحه یا محتوای محافظت شده دسترسی پیدا کند، باید توکن خود را به سمت سرور ارسال نماید. عموما اینکار توسط یک header سفارشی Authorization به همراه Bearer schema صورت میگیرد و یک چنین شکلی را دارد:
Authorization: Bearer <token>
نگاهی به محل ذخیره سازی JWT و نکات مرتبط با آن
محل متداول ذخیرهی JWT ها، در local storage مرورگرها است و در اغلب سناریوها نیز به خوبی کار میکند. فقط باید دقت داشت که local storage یک sandbox است و محدود به دومین جاری برنامه و از طریق برای مثال زیر دامنههای آن قابل دسترسی نیست. در این حالت میتوان JWT را در کوکیهای ایجاد شدهی در سمت کاربر نیز ذخیره کرد که چنین محدودیتی را ندارند. اما باید دقت داشت که حداکثر اندازهی حجم کوکیها 4 کیلوبایت است و با افزایش claims ذخیره شدهی در یک JWT و انکد شدن آن، این حجم ممکن است از 4 کیلوبایت بیشتر شود. بنابراین باید به این نکات دقت داشت.
امکان ذخیره سازی توکنها در session storage مرورگرها نیز وجود دارد. session storage بسیار شبیه است به local storage اما به محض بسته شدن مرورگر، پاک میشود.
اگر از local storage استفاده میکنید، حملات Cross Site Request Forgery در اینجا دیگر مؤثر نخواهند بود. اما اگر به حالت استفادهی از کوکیها برای ذخیرهی توکنها سوئیچ کنید، این مساله همانند قبل خواهد بود و مسیر است. در این حالت بهتر است طول عمر توکنها را تاحد ممکن کوتاه تعریف کنید تا اگر اطلاعات آنها فاش شد، به زودی بیمصرف شوند.
انقضاء و صدور مجدد توکنها به چه صورتی است؟
توکنهای بدون حالت، صرفا بر اساس بررسی امضای پیام رسیده کار میکنند. به این معنا که یک توکن میتواند تا ابد معتبر باقی بماند. برای رفع این مشکل باید exp یا تاریخ انقضای متناسبی را به توکن اضافه کرد. برای برنامههای حساس این عدد میتواند 15 دقیقه باشد و برای برنامههای کمتر حساس، چندین ماه.
اما اگر در این بین قرار به ابطال سریع توکنی بود چه باید کرد؟ (مثلا کاربری را در همین لحظه غیرفعال کردهاید)
یک راه حل آن، ثبت رکوردهای تمام توکنهای صادر شده در بانک اطلاعاتی است. برای این منظور میتوان یک فیلد id مانند را به توکن اضافه کرد و آنرا صادر نمود. این idها را نیز در بانک اطلاعاتی ذخیره میکنیم. به این ترتیب میتوان بین توکنهای صادر شده و کاربران و اطلاعات به روز آنها ارتباط برقرار کرد. در این حالت برنامه علاوه بر بررسی امضای توکن، میتواند به لیست idهای صادر شده و ذخیره شدهی در دیتابیس نیز مراجعه کرده و اعتبارسنجی اضافهتری را جهت باطل کردن سریع توکنها انجام دهد. هرچند این روش دیگر آنچنان stateless نیست، اما با دنیای واقعی سازگاری بیشتری دارد.
حداکثر امنیت JWTها را چگونه میتوان تامین کرد؟
- تمام توکنهای خود را با یک کلید قوی، امضاء کنید و این کلید تنها باید بر روی سرور ذخیره شده باشد. هر زمانیکه سرور توکنی را از کاربر دریافت میکند، این سرور است که باید کار بررسی اعتبار امضای پیام رسیده را بر اساس کلید قوی خود انجام دهد.
- اگر اطلاعات حساسی را در توکنها قرار میدهید، باید از JWE یا JSON Web Encryption استفاده کنید؛ زیرا JWTها صرفا دارای امضای دیجیتال هستند و نه اینکه رمزنگاری شده باشند.
- بهتر است توکنها را از طریق ارتباطات غیر HTTPS، ارسال نکرد.
- اگر از کوکیها برای ذخیره سازی آنها استفاده میکنید، از HTTPS-only cookies استفاده کنید تا از Cross-Site Scripting XSS attacks در امان باشید.
- مدت اعتبار توکنهای صادر شده را منطقی انتخاب کنید.
.
تولید یک پرووایدر منابع دیتابیسی - بخش سوم
برای پیادهسازی ویژگی بهروزرسانی ورودیهای منابع در زمان اجرا راهحلهای مخنلفی ممکن است به ذهن برنامهنویس خطور کند که هر کدام معایب و مزایای خودش را دارد. اما درنهایت بسته به شرایط موجود انتخاب روش مناسب برعهده خود برنامهنویس است.
مثلا برای پرووایدر سفارشی دیتابیسی تهیهشده در مطالب قبلی، تنها کافی است ابزاری تهیه شود تا به کاربران اجازه بهروزرسانی مقادیر موردنظرشان در دیتابیس را بدهد که کاری بسیار ساده است. بدین ترتیب بهروزرسانی این مقادیر در زمان اجرا کاری بسیار ابتدایی به نظر میرسد. اما در قسمت قبل نشان داده شد که برای بالا بردن بازدهی بهتر است که مقادیر موجود در دیتابیس در حافظه سرور کش شوند. استراتژی اولیه و سادهای نیز برای نحوه پیادهسازی این فرایند کشینگ ارائه شد. بنابراین باید امکاناتی فراهم شود تا درصورت تغییر مقادیر کششده در سمت دیتابیس، برنامه از این تغییرات آگاه شده و نسبت به بهروزرسانی این مقادیر در متغیر کشینگ اقدامات لازم را انجام دهد.
اما همانطور که در قسمت قبل نیز اشاره شد، نکتهای که باید درنظر داشت این است که مدیریت تمامی نمونههای تولیدشده از کلاسهای موردبحث کاملا برعهده ASP.NET است، بنابراین دسترسی مستقیمی به این نمونهها در بیرون و در زمان اجرا وجود ندارد تا این ویژگی را بتوان در مورد آنها پیاده کرد.
یکی از روشهای موجود برای حل این مشکل این است که مکانیزمی پیاده شود تا بتوان به تمامی نمونههای تولیدی از کلاس DbResourceManager در بیرون از محیط سیستم مدیریت منابع ASP.NET دسترسی داشت. مثلا یک کلاس حاول متغیری استاتیک جهت ذخیره نمونههای تولیدی از کلاس DbResourceManager، به کتابخانه خود اضافه کرد تا با استفاده از یکسری امکانات بتوان این نمونههای تولیدی را از تغییرات رخداده در سمت دیتابیس آگاه کرد. در این قسمت پیادهسازی این راهحل شرح داده میشود.
.
نکته: قبل از هرچیز برای مناسب شدن طراحی کتابخانه تولیدی و افزایش امنیت آن بهتر است تا سطح دسترسی تمامی کلاسهای پیادهسازی شده تا این مرحله به internal تغییر کند. ازآنجاکه سیستم مدیریت منابع ASP.NET از ریفلکشن برای تولید نمونههای موردنیاز خود استفاده میکند، بنابراین این تغییر تاثیری بر روند کاری آن نخواهد گذاشت.
.
نکته: با توجه به شرایط خاص موجود، ممکن است نامهای استفاده شده برای کلاسهای این کتابخانه کمی گیجکننده باشد. پس با دقت بیشتری به مطلب توجه کنید.
.
پیادهسازی امکان پاکسازی مقادیر کششده
برای اینکار باید تغییراتی در کلاس DbResourceManager داده شود تا بتوان این کلاس را از تغییرات بوجود آمده آگاه ساخت. روشی که من برای این کار درنظر گرفتم استفاده از یک اینترفیس حاوی اعضای موردنیاز برای پیادهسازی این امکان است تا مدیریت این ویژگی در ادامه راحتتر شود.
.
اینترفیس IDbCachedResourceManager
این اینترفیس به صورت زیر تعریف شده است:
namespace DbResourceProvider { internal interface IDbCachedResourceManager { string ResourceName { get; } void ClearAll(); void Clear(string culture); void Clear(string culture, string resourceKey); } }
در پراپرتی فقط خواندنی ResourceName نام منبع کش شده ذخیره خواهد شد.
متد ClearAll برای پاکسازی تمامی ورودیهای کششده استفاده میشود.
متدهای Clear برای پاکسازی ورودیهای کششده یک کالچر به خصوص و یا یک ورودی خاص استفاده میشود.
با استفاده از این اینترفیس، پیادهسازی کلاس DbResourceManager به صورت زیر تغییر میکند:
using System.Collections.Generic; using System.Globalization; using DbResourceProvider.Data; namespace DbResourceProvider { internal class DbResourceManager : IDbCachedResourceManager { private readonly string _resourceName; private readonly Dictionary<string, Dictionary<string, object>> _resourceCacheByCulture; public DbResourceManager(string resourceName) { _resourceName = resourceName; _resourceCacheByCulture = new Dictionary<string, Dictionary<string, object>>(); } public object GetObject(string resourceKey, CultureInfo culture) { ... } private object GetCachedObject(string resourceKey, string cultureName) { ... } #region Implementation of IDbCachedResourceManager public string ResourceName { get { return _resourceName; } } public void ClearAll() { lock (this) { _resourceCacheByCulture.Clear(); } } public void Clear(string culture) { lock (this) { if (!_resourceCacheByCulture.ContainsKey(culture)) return; _resourceCacheByCulture[culture].Clear(); } } public void Clear(string culture, string resourceKey) { lock (this) { if (!_resourceCacheByCulture.ContainsKey(culture)) return; _resourceCacheByCulture[culture].Remove(resourceKey); } } #endregion } }
اعضای اینترفیس IDbCachedResourceManager به صورت مناسبی در کد بالا پیادهسازی شدند. در تمام این پیادهسازیها مقادیر مربوطه از درون متغیر کشینگ پاک میشوند تا پس از اولین درخواست، بلافاصله از دیتابیس خوانده شوند. برای جلوگیری از دسترسی همزمان نیز از بلاک lock استفاده شده است.
برای استفاده از این امکانات جدید همانطور که در بالا نیز اشاره شد باید بتوان نمونههای تولیدی از کلاس DbResourceManager توسط ASP.NET درون متغیری استاتیک ذخیره شوند. برای اینکار از کلاس جدیدی با عنوان DbResourceCacheManager استفاده میشود که برخلاف تمام کلاسهای تعریفشده تا اینجا با سطح دسترسی public تعریف میشود.
کلاس DbResourceCacheManager
مدیریت نمونههای تولیدی از کلاس DbResourceManager در این کلاس انجام میشود. این کلاس پیادهسازی سادهای بهصورت زیر دارد:
using System.Collections.Generic; using System.Linq; namespace DbResourceProvider { public static class DbResourceCacheManager { internal static List<IDbCachedResourceManager> ResourceManagers { get; private set; } static DbResourceCacheManager() { ResourceManagers = new List<IDbCachedResourceManager>(); } public static void ClearAll() { ResourceManagers.ForEach(r => r.ClearAll()); } public static void Clear(string resourceName) { GetResouceManagers(resourceName).ForEach(r => r.ClearAll()); } public static void Clear(string resourceName, string culture) { GetResouceManagers(resourceName).ForEach(r => r.Clear(culture)); } public static void Clear(string resourceName, string culture, string resourceKey) { GetResouceManagers(resourceName).ForEach(r => r.Clear(culture, resourceKey)); } private static List<IDbCachedResourceManager> GetResouceManagers(string resourceName) { return ResourceManagers.Where(r => r.ResourceName.ToLower() == resourceName.ToLower()).ToList(); } } }
ازآنجاکه نیازی به تولید نمونه ای از این کلاس وجود ندارد، این کلاس به صورت استاتیک تعریف شده است. بنابراین تمام اعضای درون آن نیز استاتیک هستند.
از پراپرتی ResourceManagers برای نگهداری لیستی از نمونههای تولیدی از کلاس DbResourceManager استفاده میشود. این پراپرتی از نوع <List<IDbCachedResourceManager تعریف شده است و برای جلوگیری از دسترسی بیرونی، سطح دسترسی آن internal درنظر گرفته شده است.
در کانستراکتور استاتیک این کلاس (اطلاعات بیشتر درباره static constructor در اینجا) این پراپرتی با مقداردهی به یک نمونه تازه از لیست، اصطلاحا initialize میشود.
سایر متدها نیز برای فراخوانی متدهای موجود در اینترفیس IDbCachedResourceManager پیادهسازی شدهاند. تمامی این متدها دارای سطح دسترسی public هستند. همانطور که میبینید از خاصیت ResourceName برای مشخصکردن نمونه موردنظر استفاده شده است که دلیل آن در قسمت قبل شرح داده شده است.
دقت کنید که برای اطمینان از انتخاب درست همه موارد موجود در شرط انتخاب نمونه موردنظر در متد GetResouceManagers از متد ToLower برای هر دو سمت شرط استفاده شده است.
.
نکته مهم: درباره علت برگشت یک لیست از متد انتخاب نمونه موردنظر از کلاس DbResourceManager در کد بالا (یعنی متد GetResouceManagers) باید نکتهای اشاره شود. در قسمت قبل عنوان شد که سیستم مدیریت منابع ASP.NET نمونههای تولیدی از پرووایدرهای منابع را به ازای هر منبع کش میکند. اما یک نکته بسیار مهم که باید به آن توجه کرد این است که این کش برای «عبارات بومیسازی ضمنی» و نیز «متد مربوط به منابع محلی» موجود در کلاس HttpContext و یا نمونه مشابه آن در کلاس TemplateControl (همان متد GetLocalResourceObject که درباره این متدها در قسمت سوم این سری شرح داده شده است) از یکدیگر جدا هستند و استفاده از هریک از این دو روش موجب تولید یک نمونه مجزا از پرووایدر مربوطه میشود که متاسفانه کنترل آن از دست برنامه نویس خارج است. دقت کنید که این اتفاق برای منابع کلی رخ نمیدهد.
بنابراین برای پاک کردن مناسب ورودیهای کششده در کلاس فوق به جای استفاده از متد Single در انتخاب نمونه موردنظر از کلاس DbResourceManager (در متد GetResouceManagers) از متد Where استفاده شده و یک لیست برگشت داده میشود. چون با توجه به توضیح بالا امکان وجود دو نمونه DbResourceManager از یک منبع درخواستی محلی در لیست نمونههای نگهداری شده در این کلاس وجود دارد.
.
افزودن نمونهها به کلاس DbResourceCacheManager
برای نگهداری نمونههای تولید شده از DbResourceManager، باید در یک قسمت مناسب این نمونهها را به لیست مربوطه در کلاس DbResourceCacheManager اضافه کرد. بهترین مکان برای انجام این عمل در کلاس پایه BaseDbResourceProvider است که درخواست تولید نمونه را در متد EnsureResourceManager درصورت نال بودن آن میدهد. بنابراین این متد را به صورت زیر تغییر میدهیم:
private void EnsureResourceManager() { if (_resourceManager != null) return; { _resourceManager = CreateResourceManager(); DbResourceCacheManager.ResourceManagers.Add(_resourceManager); } }
تا اینجا کار پیادهسازی امکان مدیریت مقادیر کششده در کتابخانه تولیدی به پایان رسیده است.
.
استفاده از کلاس DbResourceCacheManager
پس از پیادهسازی تمامی موارد لازم، حالتی را درنظر بگیرید که مقادیر ورودیهای تعریف شده در منبع "dir1/page1.aspx" تغییر کرده است. بنابراین برای بروزرسانی مقادیر کششده کافی است تا از کدی مثل کد زیر استفاده شود:
DbResourceCacheManager.Clear("dir1/page1.aspx");
کد بالا کل ورودیهای کششده برای منبع "dir1/page1.aspx" را پاک میکند. برای پاک کردن کالچر یا یک ورودی خاص نیز میتوان از کدهایی مشابه زیر استفاده کرد:
DbResourceCacheManager.Clear("Default.aspx", "en-US"); DbResourceCacheManager.Clear("GlobalTexts", "en-US", "Yes");
.
دریافت کد پروژه
کد کامل پروژه DbResourceProvider به همراه مثال و اسکریپتهای دیتابیسی مربوطه از لینک زیر قابل دریافت است:
برای استفاده از این مثال ابتدا باید کتابخانه Entity Framework (با نام EntityFramework.dll) را مثلا از طریق نوگت دریافت کنید. نسخهای که من در این مثال استفاده کردم نسخه 4.4 با حجم حدود 1 مگابایت است.
نکته: در این کد یک بهبود جزئی اما مهم در کلاس ResourceData اعمال شده است. در قسمت سوم این سری، اشاره شد که نام ورودیهای منابع Case Sensitive نیست. بنابراین برای پیادهسازی این ویژگی، متدهای این کلاس باید به صورت زیر تغییر کنند:
public Resource GetResource(string resourceKey, string culture) { using (var data = new TestContext()) { return data.Resources.SingleOrDefault(r => r.Name.ToLower() == _resourceName.ToLower() && r.Key.ToLower() == resourceKey.ToLower() && r.Culture == culture); } } public List<Resource> GetResources(string culture) { using (var data = new TestContext()) { return data.Resources.Where(r => r.Name.ToLower() == _resourceName.ToLower() && r.Culture == culture).ToList(); } }
.
در آینده...
در ادامه مطالب، بحث تهیه پرووایدر سفارشی فایلهای resx. برای پیادهسازی امکان بهروزرسانی در زمان اجرا ارائه خواهد شد. بعد از پایان تهیه این پرووایدر سفارشی، این سری مطالب با ارائه نکات استفاده از این پرووایدرها در ASP.NET MVC پایان خواهد یافت.
.
منابع
- بلاکهای جزئی با عنوان اسپرینت
- ویژگیهای جزئی
- تیمهای کوچک
- product backlog : مجموعهای اولویت بندی شده از نیازمندیهای سطح بالای سیستمی که در نهایت بایستی تحویل داده شود.
- sprint backlog : مواردی از product backlog که قرار است در یک sprint انجام شوند.
- نمودار burn-down :هدف نمودار burn-down، نمایش روند پیشرفت پروژه به صورت نموداری به اعضای تیم توسعه است که حاوی اطلاعاتی دربارۀ کل زمان انجام کار، زمان تخمینزده شده، مقدار کارانجام شده و عقبماندگیهای پروژه است.
- مستقل (Independent): باید خودبسنده باشد و به سایر داستانها وابسته نباشد.
- قابل مذاکره (Negotiable): داستانهای کاربری که بخشی از یک اسپرینت هستند همیشه قابل تغییر و بازنویسی هستند.
- با ارزش (Valuable): یک داستان کاربر باید به کاربر نهایی، ارزشی را ارائه دهد.
- قابل برآورد (Estimable): همیشه باید بتوان اندازۀ داستان کاربر را تخمین زد.
- اندازۀ مناسب (Sized appropriately): داستانهای کاربر نباید آن قدر بزرگ باشند که تبدیلشان به یک طرح یا وظیفه یا امر اولویتبندی شده با درجۀ مشخصی ممکن نباشد.
- قابل آزمون (Testable): داستان کاربر یا توصیفات مربوط به آن باید اطلاعات ضروری برای آزمودن آن را فراهم کنند.
معیار پذیرش
اگرچه product backlog ، sprint backlog و نمودار burn-down بخشهای اصلی اسکرام هستند، معیار پذیرش خروجی جانبی بسیار مهمی از فرآیند اسکرام است. بدون معیار پذیرش خوب، یک پروژه محکوم به شکست است.
معیار پذیرش ضرورتاً شفاف کنندۀ داستان است. چنین معیاری مجموعهای از گامهای مختلف را در اختیار توسعه دهنده میگذارد که پیش از آنکه کار تمام شده تلقی شود، باید انجام دهد. معیار پذیرش، توسط صاحب محصول ( product owner ) به کمک مشتری ایجاد میشود. این معیار انتظار از داستان کاربر را تنظیم میکند. استفاده از این معیار درجای خود نقطۀ شروع خوبی برای نوشتن تستهای خودکار یا حتی توسعۀ آزمون محور توسط توسعهدهنده است. بدین طریق، توسعهدهنده چیزی را تولید میکند که مشتری بدان نیاز داشته و آن را میخواهد.
دیگر مزیت معیار پذیرش وقتی آشکار میشود که یک ویژگی در طول یک اسپرینت کامل نشده و نیاز است تا از اسپرینتها خارج شود. در چنین موردی تیم میتواند معیار پذیرش را به عنوان ابزاری به کار گیرد تا بفهمد که داستان کاربر چگونه به قطعات کوچکتری تقسیم شود تا کماکان ارزشی را برای مشتری فراهم کرده تا بتواند در یک اسپرینت کامل شود.
- Scrum Master
- Product Owner
- Delivery Team
- Scrum Master
- شما چه چیزی را از دیروز تا حالا انجام دادهاید؟
- شما چه برنامهای برای امروز دارید؟
- آیا شما مشکل دیگری که مانع رسیدن به هدفتان باشد، ندارید؟ چه جریانی باعث ایجاد این موانع شدهاند؟ آیا میتوان مانع را حذف کرد یا باید تشدید شود؟