سلام؛ زمانی که در پروژه از [UIHint("GenderOptions")] استفاده میکنم به خوبی خروجی میگیرم اما زمانی که که Model من در DataModel و یک پروژه مجزا و در یک Dll دیگر است کار نمیکند.
EF Code First #12
ASP.NET MVC #22
تهیه سایتهای چند زبانه و بومی سازی نمایش اطلاعات در ASP.NET MVC
زمانیکه دات نت فریم ورک نیاز به انجام اعمال حساس به مسایل بومی را داشته باشد، ابتدا به مقادیر تنظیم شده دو خاصیت زیر دقت میکند:
الف) System.Threading.Thread.CurrentThread.CurrentCulture
بر این اساس دات نت میتواند تشخیص دهد که برای مثال خروجی متد DateTime.Now.ToString در کانادا و آمریکا باید با هم تفاوت داشته باشند. مثلا در آمریکا ابتدا ماه، سپس روز و در آخر سال نمایش داده میشود و در کانادا ابتدا سال، بعد ماه و در آخر روز نمایش داده خواهد شد. یا نمونهی دیگری از این دست میتواند نحوه نمایش علامت واحد پولی کشورها باشد.
ب) System.Threading.Thread.CurrentThread.CurrentUICulture
مقدار CurrentUICulture بر روی بارگذاری فایلهای مخصوصی به نام Resource، تاثیر گذار است.
این خواص را یا به صورت دستی میتوان تنظیم کرد و یا ASP.NET، این اطلاعات را از هدر Accept-Language دریافتی از مرورگر کاربر به صورت خودکار مقدار دهی میکند. البته برای این منظور نیاز است یک سطر زیر را به فایل وب کانفیگ برنامه اضافه کرد:
<system.web>
<globalization culture="auto" uiCulture="auto" />
یا اگر نیاز باشد تا برنامه را ملزم به نمایش اطلاعات Resource مرتبط با فرهنگ بومی خاصی کرد نیز میتوان در همین قسمت مقادیر culture و uiCulture را دستی تنظیم نمود و یا اگر همانند برنامههایی که چند لینک را بالای صفحه نمایش میدهند که برای مثال به نگارشهای فارسی/عربی/انگلیسی اشاره میکند، اینکار را با کد نویسی نیز میتوان انجام داد:
System.Threading.Thread.CurrentThread.CurrentCulture =
System.Globalization.CultureInfo.CreateSpecificCulture("fa");
جهت آزمایش این مطلب، ابتدا تنظیم globalization فوق را به فایل وب کانفیگ برنامه اضافه کنید. سپس به مسیر زیر در IE مراجعه کنید:
IE -> Tools -> Intenet options -> Genarl tab -> Languages
در اینجا میتوان هدر Accept-Language را مقدار دهی کرد. برای نمونه اگر مقدار زبان پیش فرض را به فرانسه تنظیم کنیم (به عنوان اولین زبان تعریف شده در لیست) و سپس سعی در نمایش مقدار decimal زیر را داشته باشیم:
string.Format("{0:C}", 10.5M)
اگر زبان پیش فرض، انگلیسی آمریکایی باشد، $ نمایش داده خواهد شد و اگر زبان به فرانسه تنظیم شود، یورو در کنار عدد مبلغ نمایش داده میشود.
تا اینجا تنها با تنظیم culture=auto به این نتیجه رسیدهایم. اما سایر قسمتهای صفحه چطور؟ برای مثال برچسبهای نمایش داده شده را چگونه میتوان به صورت خودکار بر اساس Accept-Language مرجح کاربر تنظیم کرد؟ خوشبختانه در دات نت، زیر ساخت مدیریت برنامههای چند زبانه به صورت توکار وجود دارد که در ادامه به بررسی آن خواهیم پرداخت.
آشنایی با ساختار فایلهای Resource
فایلهای Resource یا منبع، در حقیقت فایلهایی هستند مبتنی بر XML با پسوند resx و هدف آنها ذخیره سازی رشتههای متناظر با فرهنگهای مختلف میباشد و برای استفاده از آنها حداقل یک فایل منبع پیش فرض باید تعریف شود. برای نمونه فایل mydata.resx را در نظر بگیرید. برای ایجاد فایل منبع اسپانیایی متناظر، باید فایلی را به نام mydata.es.resx تولید کرد. البته نوع فرهنگ مورد استفاده را کاملتر نیز میتوان ذکر کرد برای مثال mydata.es-mex.resx جهت فرهنگ اسپانیایی مکزیکی بکارگرفته خواهد شد، یا mydata.fr-ca.resx به فرانسوی کانادایی اشاره میکند. سپس مدیریت منابع دات نت فریم ورک بر اساس مقدار CurrentUICulture جاری، اطلاعات فایل متناظری را بارگذاری خواهد کرد. اگر فایل متناظری وجود نداشت، از اطلاعات همان فایل پیش فرض استفاده میگردد.
حین تهیه برنامهها نیازی نیست تا مستقیما با فایلهای XML منابع کار کرد. زمانیکه اولین فایل منبع تولید میشود، به همراه آن یک فایل cs یا vb نیز ایجاد خواهد شد که امکان دسترسی به کلیدهای تعریف شده در فایلهای XML را به صورت strongly typed میسر میکند. این فایلهای خودکار، تنها برای فایل پیش فرض mydata.resx تولید میشوند،از این جهت که تعاریف اطلاعات سایر فرهنگهای متناظر نیز باید با همان کلیدهای فایل پیش فرض آغاز شوند. تنها «مقادیر» کلیدهای تعریف شده در کلاسهای منبع متفاوت هستند.
اگر به خواص فایلهای resx در VS.NET دقت کنیم، نوع Build action آنها به embedded resource تنظیم شده است.
مثالی جهت بررسی استفاده از فایلهای Resource
یک پروژه جدید خالی ASP.NET MVC را آغاز کنید. فایل وب کانفیگ آنرا ویرایش کرده و تنظیمات globalization ابتدای بحث را به آن اضافه کنید. سپس مدل، کنترلر و View متناظر با متد Index آنرا با محتوای زیر به پروژه اضافه نمائید:
namespace MvcApplication19.Models
{
public class Employee
{
public int Id { set; get; }
public string Name { set; get; }
}
}
using System.Web.Mvc;
using MvcApplication19.Models;
namespace MvcApplication19.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var employee = new Employee { Name = "Name 1" };
return View(employee);
}
}
}
@model MvcApplication19.Models.Employee
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>Employee</legend>
<div class="display-label">
Name
</div>
<div class="display-field">
@Html.DisplayFor(model => model.Name)
</div>
</fieldset>
<fieldset>
<legend>Employee Info</legend>
@Html.DisplayForModel()
</fieldset>
قصد داریم در View فوق بر اساس uiCulture کاربر مراجعه کننده به سایت، برچسب Name را مقدار دهی کنیم. اگر کاربری از ایران مراجعه کند، «نام کارمند» نمایش داده شود و سایر کاربران، «Employee Name» را مشاهده کنند. همچنین این تغییرات باید بر روی متد Html.DisplayForModel نیز تاثیرگذار باشد.
برای این منظور بر روی پوشه Views/Home که محل قرارگیری فایل Index.cshtml فوق است کلیک راست کرده و گزینه Add|New Item را انتخاب کنید. سپس در صفحه ظاهر شده، گزینه «Resources file» را انتخاب کرده و برای مثال نام Index_cshtml.resx را وارد کنید.
به این ترتیب اولین فایل منبع مرتبط با View جاری که فایل پیش فرض نیز میباشد ایجاد خواهد شد. این فایل، به همراه فایل Index_cshtml.Designer.cs تولید میشود. سپس همین مراحل را طی کنید، اما اینبار نام Index_cshtml.fa.resx را حین افزودن فایل منبع وارد نمائید که برای تعریف اطلاعات بومی ایران مورد استفاده قرار خواهد گرفت. فایل دومی که اضافه شده است، فاقد فایل cs همراه میباشد.
اکنون فایل Index_cshtml.resx را در VS.NET باز کنید. از بالای صفحه، به کمک گزینه Access modifier، سطح دسترسی متدهای فایل cs همراه آنرا به public تغییر دهید. پیش فرض آن internal است که برای کار ما مفید نیست. از این جهت که امکان دسترسی به متدهای استاتیک تعریف شده در فایل خودکار Index_cshtml.Designer.cs را در View های برنامه، نخواهیم داشت. سپس دو جفت «نام-مقدار» را در فایل resx وارد کنید. مثلا نام را Name و مقدار آنرا «Employee Name» و سپس نام دیگر را NameIsNotRight و مقدار آنرا «Name is required» وارد نمائید.
در ادامه فایل Index_cshtml.fa.resx را باز کنید. در اینجا نیز دو جفت «نام-مقدار» متناظر با فایل پیش فرض منبع را باید وارد کرد. کلیدها یا نامها یکی است اما قسمت مقدار اینبار باید فارسی وارد شود. مثلا نام را Name و مقدار آنرا «نام کارمند» وارد نمائید. سپس کلید یا نام NameIsNotRight و مقدار «لطفا نام را وارد نمائید» را تنظیم نمائید.
تا اینجا کار تهیه فایلهای منبع متناظر با View جاری به پایان میرسد.
در ادامه با کمک فایل Index_cshtml.Designer.cs که هربار پس از تغییر فایل resx متناظر آن به صورت خودکار توسط VS.NET تولید و به روز میشود، میتوان به کلیدها یا نامهایی که تعریف کردهایم، در قسمتهای مختلف برنامه دست یافت. برای نمونه تعریف کلید Name در این فایل به نحو زیر است:
namespace MvcApplication19.Views.Home {
public class Index_cshtml {
public static string Name {
get {
return ResourceManager.GetString("Name", resourceCulture);
}
}
}
}
بنابراین برای استفاده از آن در هر View ایی تنها کافی است بنویسیم:
@MvcApplication19.Views.Home.Index_cshtml.Name
به این ترتیب بر اساس تنظیمات محلی کاربر، اطلاعات به صورت خودکار از فایلهای Index_cshtml.fa.resx فارسی یا فایل پیش فرض Index_cshtml.resx، دریافت میگردد.
علاوه بر امکان دسترسی مستقیم به کلیدهای تعریف شده در فایلهای منبع، امکان استفاده از آنها توسط data annotations نیز میسر است. در این حالت میتوان مثلا پیغامهای اعتبار سنجی را بومی کرد یا حین استفاده از متد Html.DisplayForModel، بر روی برچسب نمایش داده شده خودکار، تاثیر گذار بود. برای اینکار باید اندکی مدل برنامه را ویرایش کرد:
using System.ComponentModel.DataAnnotations;
namespace MvcApplication19.Models
{
public class Employee
{
[ScaffoldColumn(false)]
public int Id { set; get; }
[Display(ResourceType = typeof(MvcApplication19.Views.Home.Index_cshtml),
Name = "Name")]
[Required(ErrorMessageResourceType = typeof(MvcApplication19.Views.Home.Index_cshtml),
ErrorMessageResourceName = "NameIsNotRight")]
public string Name { set; get; }
}
}
همانطور که ملاحظه میکنید، حین تعریف ویژگیهای Display یا Required، امکان تعریف نام کلاس متناظر با فایل resx خاصی وجود دارد. به علاوه ErrorMessageResourceName به نام یک کلید در این فایل و یا پارامتر Name ویژگی Display نیز به نام کلیدی در فایل منبع مشخص شده، اشاره میکنند. این اطلاعات توسط متدهای Html.DisplayForModel، Html.ValidationMessageFor، Html.LabelFor و امثال آن به صورت خودکار مورد استفاده قرار خواهند گرفت.
نکتهای در مورد کش کردن اطلاعات
در این مثال اگر فیلتر OutputCache را بر روی متد Index تعریف کنیم، حتما نیاز است به هدر Accept-Language نیز دقت داشت. در غیراینصورت تمام کاربران، صرفنظر از تنظیمات بومی آنها، یک صفحه را مشاهده خواهند کرد:
[OutputCache(Duration = 60, VaryByHeader = "Accept-Language")]
public ActionResult Index()
یک مثال ساده
using System; namespace AccessViolationExceptionSample { class Program { private static unsafe void AccessViolation() { byte b = *(byte*)(8762765876); } static void Main(string[] args) { try { AccessViolation(); } catch (Exception ex) { Console.WriteLine(ex); } Console.WriteLine("Press a key..."); Console.ReadKey(); } } }
System.AccessViolationException. Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
چرا قسمت catch اجرا نشد؟
از دات نت 4 به بعد، زمانیکه دسترسی غیرمجازی به حافظه صورت گیرد، برای مثال دسترسی به یک pointer آزاد شده، استثنای حاصل، توسط برنامه catch نشده و اجازه داده میشود تا برنامه کلا کرش کند. به این نوع استثناءها Corrupted State Exceptions یا CSE گفته میشود. اگر نیاز به مدیریت آنها توسط برنامه باشد، باید به یکی از دو طریق زیر عمل کرد:
الف) از ویژگی HandleProcessCorruptedStateExceptions بر روی متد فراخوان کتابخانهی native باید استفاده شود. برای مثال در کدهای فوق خواهیم داشت:
[HandleProcessCorruptedStateExceptions] static void Main(string[] args) {
<?xml version="1.0" encoding="utf-8" ?> <configuration> <runtime> <legacyCorruptedStateExceptionsPolicy enabled="true" /> </runtime> </configuration>
گریس مانکی یکی از افزونههای فایرفاکس است که توسط آن میتوان اسکریپتهایی را بر روی صفحات وب در حال مشاهده، جهت تغییر آنها اجرا کرد. این نوع تغییرات بیشتر در جهت بالا بردن کارآیی یا خوانایی سایتها صورت میگیرد. مثلا بررسی وجود لینکهای ارائه شده در یک صفحه وب (قبل از اینکه به آنها رجوع کنیم، مشخص شود که آیا وجود دارند یا خیر) و هزاران مثال شبیه به این که در سایت اسکریپتهای آن قابل دریافت هستند.
گریس مانکی به خودی خود کار خاصی را انجام نمیدهد و فقط میزبان اجرایی اسکریپتهایی است که برای آن تهیه شده اند. به این اسکریپتها user scripts گفته میشود و جهت تهیه آنها از زبان جاوا اسکریپت استفاده میگردد.
مطابق اصول نامگذاری آن، فایل این اسکریپتها حتما باید به .user.js ختم شود تا توسط افزونه گریس مانکی قابل شناسایی باشد.
اسکریپت سلام دنیای گریس مانکی!
// ==UserScript==
// @name Hello World
// @namespace http://diveintogreasemonkey.org/download/
// @description example script to alert "Hello world!" on every page
// @include *
// @exclude http://diveintogreasemonkey.org/*
// @exclude http://www.diveintogreasemonkey.org/*
// ==/UserScript==
alert('Hello world!');
در پایان مثلا نام آنرا hello.user.js خواهیم گذاشت (همانطور که ذکر شد قسمت user.js آن باید رعایت شود). برای نصب آن فقط کافی است این فایل را به درون پنجره فایر فاکس کشیده و رها کنیم (drag & drop).
استفاده از jQuery در اسکریپتهای گریس مانکی
عمده کاربردهای اسکریپتهای گریس مانکی در جهت اعمال تغییرات بر روی document object model صفحه (DOM) هستند. کتابخانه jQuery اساسا برای این منظور تهیه و بهینه سازی شده است.
مثال:
فرض کنید قصد داریم به نتایج خروجی جستجوی گوگل، fav icon سایتهای یافت شده را اضافه کنیم (در کنار هر لینک، آیکون سایت مربوطه را نمایش دهیم). گوگل اینکار را انجام نداده است. اما ما علاقمند هستیم که این قابلیت را اضافه کنیم!
// ==UserScript==
// @name Google FavIcon
// @namespace http://userscripts.org
// @description Shows favicon for Google searches results
// @include http://*.google.*/search?*
// ==/UserScript==
loadJquery();
$(document).ready(function(){
$("h3.r > a.l").each(function(){
var $a = $(this);
var href = $a.attr("href");
var domain = href.replace(/<\S[^><]*>/g, "").split('/')[2];
var image = '<img src="http://' + domain + '/favicon.ico" style="border:0;padding-right:4px;" />';
//GM_log(">image:> " + image);
$(this).prepend(image);
});
});
دریافت سورس این مثال
در اینجا ابتدا لینکهای حاصل از جستجو پیدا میشوند. سپس نام دومین مربوطه استخراج میگردد (با استفاده از regular expressions) و در ادامه از این نام دومین یک آدرس استاندارد http://domain/favico.ico ساخته شده و از آن یک تگ img درست میشود. در آخر این تگ به قبل از لینکهای گوگل اضافه میشود.
شاید سؤال بپرسید از کجا مشخص شد که باید به دنبال h3.r > a.l گشت؟ به تصویر زیر دقت نمائید (نمایی است از یکی از تواناییهای افزونه fireBug فایرفاکس).
هنگامیکه صفحه بارگذاری شد، بر روی آیکون سوسک موجود در status bar فایرفاکس کلیک کنید تا FireBug ظاهر شود (البته من اینجا سوسک دیدم شاید موجود دیگری باشد :) )
سپس بر روی دکمه inspect در نوار ابزار آن کلیک کنید. در همین حال اشارهگر ماوس را به یکی از لینکهای نتیجه جستجوی گوگل نزدیک کنید. بلافاصله تگها و کلاسهای مورد استفاده آن به شکل زیبایی ظاهر خواهند شد. به این صورت صرفه جویی قابل ملاحظهای در وقت صورت خواهد گرفت.
نتیجه اجرای اسکریپت فوق (پس از نصب) به صورت زیر است:
نکته: نحوه دیباگ کردن اسکریپتهای گریس مانکی
اگر نیاز به مشاهده مقدار متغیرها در لحظه اجرای اسکریپت داشتید، یکی از راهحلهای موجود استفاده از تابع GM_log مربوط به API گریس مانکی است که خروجی آنرا در قسمت messages مربوط به error console فایرفاکس میتوان دید.
رفع مشکلات:
در قسمت قبل با ذکر یک مثال و بیان مشکلات آن از دیدگاه اصول Defensive Code قصد داشتیم که مساله را روشنتر کنیم. مواردی که در
قسمت قبل ذکر شدند، به سادهترین شکل ممکن بیان
شدند و شما به راحتی با بررسی این موارد و تفکر در کدهای خود، میتوانید
این موارد را در کدی که خودتان مینویسید رعایت کنید. حل پیچیدگیهای موجود در کد قبل، با در نظر گرفتن اصول مذکور و اصولهای طراحی مختلف میتواند به روشهای مختلفی انجام گیرد. برای مثال میتوان برای هر یک از کارهایی که کد مثال قبل انجام میدهد، یک کلاس مجزا ایجاد نمود و اصول مذکور را در آن رعایت کرد. درنهایت این
کلاسها را در قالب یک Class Library دسته بندی کرد.
Predictability:
در ادامه قصد داریم در مورد قابلیت پیش بینی و فواید رعایت اصول آن در کد، بحث کنیم. به صورت کلی یک متد، یکسری پارامترها را به عنوان ورودی دریافت میکند؛ یک عملیات خاص را بر روی پارامترهای ورودی انجام میدهد و در نهایت، در صورت لزوم یک مقدار را بر میگرداند. پارامترهای ورودی میتوانند از سمت کاربر و از یک سورس خارجی وارد شوند و یا میتوانند از یک متد دیگر به این متد ارسال شوند. اولین مرحله برای ایجاد قابلیت Predictability این است که یکسری گاردها را به کد خود اضافه کنید تا به وسیلهی آنها پارامترهای ورودی را بررسی کنید و فقط اجازهی ورود، ورودیهای معتبر را بدهیم. برای مثال کد ذیل را درنظر بگیرد.
پارامترهای ورودی این کد با مستطیل قرمز رنگ مشخص شده اند. حال ما سعی داریم با قرار دادن یکسری گارد برای پارامترهای ورودی، از ورود مقادیر ناخواسته جلوگیری کنیم.
بر اساس اصول (GIGO (Garbage in-Garbage out در برنامه نویسی متدی که ورودیهای نامعتبر به آن پاس داده شوند، خروجیهای نامعتبری هم پس خواهد داد. بنابراین برای جلوگیری از این مسئله باید از ورود ورودیهای نامعتبر به متدها جلوگیری کرد. گاردها از ورود مقادیر نامعتبر به متدها جلوگیری خواهند کرد و در نتیجه خروجی مناسب و قابل پیش بینی از متد گرفته خواهد شد. برای جلوگیری از ورود دادههای نامعتبر، باید با استفاده از این دستورات که در ابتدای متد قرار داده میشوند، از ورود دادههای نامعتبر جلوگیری کرد. به این دستورات Guard Clauses گفته میشود. غیر از این مساله، کاهش دادن تعداد پارامترها و قراردادن قانونی برای تعیین اولویت پارامترهای متدها (برای مثال با توجه به اهمیت) میتواند به افزایش Predictability متدها بسیار کمک کند. با پیروی کردن از این اصول ساده شما میتوانید میزان خطاهایی که از پارامترهای ورودی منشاء میگیرند را کاهش دهید.
اجازه دهید با یک مثال؛ مسالهی بالا را تشریح کنیم. برای مثال یک برنامهی کوچک نوشتهایم؛ برای شمردن گام ها. در این برنامه تعداد قدمهای هدف و تعداد قدمهای برداشته شدهی امروز تعیین میشوند و سپس هدف، بر حسب درصد بیان خواهد شد.
با استفاده از این Application میخواهیم مفاهیمی را که بیان کردیم، به صورت کاربردی
نمایش دهیم. کدی این محاسبه را برای ما انجام میدهد، در ذیل نمایش داده شده و
در قالب یک متد تعیین شده است.
private decimal CalculatePercentOfGoalSteps (string goalSteps, string actualSteps) { return (Convert.ToDecimal(actualSteps) / Convert.ToDecimal(goalSteps)) * 100; }
این متد دارای دو پارامتر از نوع string می باشد و نتیجه هم در قالب یک مقدار decimal بازگشت داده خواهد شد. این جمله کلیتی از متد را بیان خواهد کرد. نحوهی فراخوانی این متد هم در کد ذیل آورده شد است.
private void Calculate_Click(object sender, EventArgs e) { var result =CalculatePercentOfGoalSteps (stepGoalForTodayTxt.Text, numberOfStepsForToday.Text); lblResult.Text = "شما به" + result + "% از هدف تان رسیده اید"; }
حال Application را اجرا کرده و نتیجه کار را مشاهده میکنیم. برای مثال شکل ذیل:
در این مثال با توجه به مقادیر وارد شده، به 40 درصد از هدف مورد نظر رسیدهایم. اما هدف از بیان این مثال، این نیست که مشخص گردد که ما چقدر به هدفمان نزدیک شدهایم. بلکه هدف مسایل دیگری است. در نظر بگیرید که بجای 5000، صفر را وارد کنید. در این حالت با یک Exception روبرو میشویم:
همانطور که در شکل بالا مشاهده میکنید، خطای Divide by zero رخ داده است. برای رفع این خطا و جلوگیری از رخداد این خطا، میتوان کد
ذیل را پیشنهاد داد.
private decimal CalculatePercentOfGoalSteps(string goalSteps, string actualSteps) { decimal result =0; var goalStepsCount = Convert.ToDecimal(goalSteps); if (goalStepsCount>0) { result = (Convert.ToDecimal(actualSteps) / goalStepsCount) * 100; } return result; }
با تغییر کد به این صورت مشکل Exception بالا حل میشود، اما باز هم مشکل دیگری وجود دارد. فرض کنید همانند شکل ذیل textbox اول را خالی کنیم و بعد از آن سعی در محاسبه داشته باشیم،
باز هم یک Exception دیگر
علت بوجود آمدن این مشکل این است که ما در کد امکان خالی بودن پارامترهای متد را در نظر نگرفتهایم و پیش بینیهای لازم صورت نگرفته است بنابراین دستور Convert .با مشکل مواجه شد. برای حل این مشکل میتوان به جای Convert از decimal.Tryparse استفاده کرد.
private decimal CalculatePercentOfGoalSteps(string goalSteps, string actualSteps) { decimal result = 0; decimal goalStepsCount = 0; decimal.TryParse(goalSteps, out goalStepsCount); decimal actualStepsCount = 0; decimal.TryParse(actualSteps, out actualStepsCount); if (goalStepsCount>0) { result = (actualStepsCount / goalStepsCount) * 100; } return result; }
با انجام دادن این کارها از بروز خطاهایی که ناشی از ورودیهای نامعتبر در کد هستند، جلوگیری کردیم. اما آیا این پایان کار است؟ خیر با استفاده کردن از این روش ما توانستهایم که از بروز خطا در برنامه جلوگیری کنیم. اما مشکلی که این روش دارد این است که کاربر متوجه نمیشود که چه زمانی برنامه دچار مشکل شده است. کاری که ما انجام میدهیم این است که برای تمامی حالات خطا، مقدار صفر را بر میگردانیم.
برای اینکه بتوانیم این کد به راحتی debug کنیم باید از مفهوم Fail Fast استفاده کنیم . این مفهوم قابلیتی را در کد ایجاد میکند که در صورتی که کد، دادههای نامعتبری را دریافت کرد، سریعا اجرای آن متوقف میشود و همزمان نیز اطلاعاتی در مورد خطا در اختیار کاربر قرار میدهد. برای این منظور با قرار دادن یکسری Guard Clauses، کد بالا را همانند شکل ذیل تغییر خواهیم داد.
private decimal CalculatePercentOfGoalSteps(string goalSteps, string actualSteps) { decimal goalStepsCount = 0; decimal actualStepsCount = 0; /// اطمینان حاصل میکنند که پارامترهای ورودی دارای مقدار هستند if (string.IsNullOrWhiteSpace(goalSteps)) throw new ArgumentException("مقدار هدف باید وارد شود", "goalSteps"); if (string.IsNullOrWhiteSpace(actualSteps)) throw new ArgumentException("مقدار واقعی باید وارد شود", "goalSteps"); ///اطمینان حاصل میکنند که مقادیر وارد شده حتما عددی هستند if (!decimal.TryParse(goalSteps, out goalStepsCount)) throw new ArgumentException("مقدار هدف باید عددی باشد", goalSteps); if(!decimal.TryParse(actualSteps, out actualStepsCount)) throw new ArgumentException("مقدار واقعی باید عددی باشد", actualSteps); ///اطمینان حاصل میکند که مقدار متغیر نباید صفر باشد if (goalStepsCount <= 0) throw new ArgumentException("مقدار هدف نباید صفر و یا کمتر از صفر باشد", "goalStepsCount"); return (actualStepsCount / goalStepsCount) * 100; }
ایجاد کردن این تغییرات در متد باعث افزایش خوانایی
کد میشود و هدف متد را روشنتر بیان خواهد کرد. اضافه کردن این کدها به دلیل اینکه
تمامی شرایط تست را تعیین خواهیم کرد Test-ability کد را بالا میبرد. اضافه کردن کدهای بالا به برنامه کمک خواهد کرد که
شرایط خطا در برنامه به درستی هندل شود و به طبع آن تصمیمات مناسبی گرفته شود و در
نهایت Predictability متدها و کل برنامه را افزایش میهد.
if ($(".tab-content").find("div.tab-pane.active:has(div.has-error)").length === 0) { $(".tab-content").find("div.tab-pane:hidden:has(div.has-error)").each(function (index, tab) { var id = $(tab).attr("id"); $('a[href="#' + id + '"]').tab('show'); }); }
$.validator.setDefaults({ //برای رفع مشکل عدم اعتبارسنجی المانهای مخفی مانند تبهای بسته ignore: [], invalidHandler: function (event, validator) { if ($(".tab-content").find("div.tab-pane.active:has(div.has-error)").length === 0) { $(".tab-content").find("div.tab-pane:hidden:has(div.has-error)").each(function (index, tab) { var id = $(tab).attr("id"); $('a[href="#' + id + '"]').tab('show'); }); } } });
$("form").unbind("invalid-form.validate"); // unbind default MS handler $("form").bind("invalid-form.validate", function(event , validator) { if ($(".tab-content").find("div.tab-pane.active:has(div.has-error)").length === 0) { $(".tab-content").find("div.tab-pane:hidden:has(div.has-error)").each(function (index, tab) { var id = $(tab).attr("id"); $('a[href="#' + id + '"]').tab('show'); }); } });
کار با IDE و حرکت بین کدها
در ادامه، همان پروژهای را که در قسمت قبل ایجاد کردیم، مجددا با وارد شدن به پوشهی آن و اجرای دستور . code، توسط VSCode باز خواهیم کرد. سپس فایل Program.cs آنرا باز کنید. فرض کنید در سطر ذیل آن:
.UseStartup<Startup>()
الف) اشارهگر ماوس را به آن نزدیک کنید و سپس دکمهی Ctrl را نگه دارید. به این ترتیب واژهی Startup تبدیل به یک لینک خواهد شد که با کلیک بر روی آن میتوان به کلاس Startup رسید.
ب) روش دوم، قرار دادن اشارهگر متنی بر روی واژه Startup و سپس فشردن دکمهی F12 است. این گزینه بر روی منوی کلیک راست بر روی این واژه نیز وجود دارد.
اگر دکمهی F12 را بر روی کلاسی فشار دهید که کدهای آن در IDE وجود ندارند، یک صفحهی جدید باز شده و کلاس تعریف آنرا بر اساس ساختار و متادیتای این اسمبلی، به همراه مستندات کامل آنها نمایش میدهد.
در این حالت اگر خواستید به مکان قبلی بازگردید فقط کافی است دکمههای alt + left cursor key را بفشارید.
در اینجا امکان یافتن ارجاعات به یک کلاس، مشاهدهی پیاده سازیها و همچنین یک Refactoring به نام rename symbol نیز موجود است که با استفادهی از آن، تمام ارجاعات به این کلاس در IDE نیز تغییرنام خواهند یافت.
یافتن سریع فایلها در IDE
در یک پروژهی بزرگ، برای یافتن سریع یک فایل، تنها کافی است دکمههای Ctrl+P را فشرده و در صفحهی دیالوگ باز شده، قسمتی از نام آنرا جستجو کنید:
همچنین اگر میخواهید محتوای فایلها را جهت یافتن واژهای خاص جستجو کنید، کلیدهای Ctrl+Shift+F (منوی Edit بالای صفحه و یا دومین آیکن در نوار ابزار عمودی سمت چپ صفحه) چنین امکانی را فراهم میکنند:
افزودن فایلهای جدید به پروژه
فرض کنید میخواهیم یک کنترلر جدید را به پوشهی Controllers اضافه کنیم:
برای اینکار ابتدا پوشهی Controllers را انتخاب کنید. در همین حال، نوار ابزاری ظاهر میشود که توسط آن میتوان در این پوشه، یک فایل جدید و یا یک پوشهی جدید را ایجاد کرد.
اگر علاقمند به ایجاد فایل یا پوشهای در ریشهی پروژه باشید، باید تمام فایلها و پوشههای موجود، در حالت انتخاب نشده قرار بگیرند. به همین جهت فقط کافی است در یک مکان خالی، در لیست فایلها کلیک کنید تا فایلها و پوشهها از حالت انتخاب شده خارج شوند. اکنون نوار ابزار ظاهر شدهی افزودن فایلها، به پوشهی ریشهی پروژه اشاره میکند.
در ادامه فایل جدید AboutController.cs را در پوشهی Controllers ایجاد کنید. مشاهده خواهید کرد که یک فایل کاملا خالی ایجاد شدهاست. در VSCode به ازای پسوندهای مختلف فایلها، قالبهای از پیش آماده شدهای برای آنها درنظر گرفته نمیشود و نمای ابتدایی تمام آنها خالی است.
اما در اینجا اگر کلمهی name را تایپ کنیم، دو پیشنهاد افزودن فضای نام را ارائه میدهد:
اولی صرفا نام namespace را در صفحه درج خواهد کرد. دومی به یک code snippet اشاره میکند و کار آن ایجاد قالب یک فضای نام جدید است. برای درج آن فقط کافی است دکمهی tab را بفشارید.
همین کار را در مورد class نیز میتوان تکرار کرد و در اینجا در intellisense ظاهر شده یا میتوان واژهی class را درج کرد و یا code snippet آنرا انتخاب نمود که یک کلاس جدید را ایجاد میکند:
یک نکته: در VSCode نیازی نیست تا مدام دکمههای Ctrl+S را جهت ذخیره سازی فایلهای تغییر کرده فشرد. میتوان از منوی فایل، گزینهی Auto Save را انتخاب کرد تا اینکار را به صورت خودکار انجام دهد.
ایجاد Code Snippets جدید
هرچند تا اینجا با استفاده از code snippets پیش فرض فضاهای نام و کلاسها، یک کلاس جدید را ایجاد کردیم، اما روش سادهتری نیز برای انجام اینکارها و تکمیل کنترلر وجود دارد.
برای این منظور به منوی File -> Preferences -> User snippets مراجعه کنید و سپس از لیست ظاهر شده، زبان #C را انتخاب کنید:
به این ترتیب یک قالب جدید code snippet تولید خواهد شد. در اینجا میخواهیم برای تولید Actionهای یک کنترلر نیز یک code snippet جدید را تهیه کنیم:
{ "MVC Action": { "prefix": "mvcAction", "body": [ "public IActionResult ${1:ActionName}()", "{", " $0 ", " return View();", "}" ], "description": "Creates a simple MVC action method." } }
- در این آرایه، 0$ جائی است که اشارهگر متنی پس از درج snippet قرار میگیرد و 1$ به نامی که قرار است توسط کاربر تکمیل شود، اشاره میکند که در اینجا یک نام پیش فرض مانند ActionName را هم میتوان برای آن درنظر گرفت.
- در اینجا prefix نامی است که اگر در صفحه تایپ شود، منوی انتخاب این code snippet را ظاهر میکند:
استفاده از بستههای Code Snippets آماده
خوشبختانه پشتیبانی جامعهی توسعه دهندگان از VSCode بسیار مطلوب است و علاوه بر افزونههای قابل توجهی که برای آن نوشته شدهاند، بستههای Code Snippets آمادهای نیز جهت بالابردن سرعت کار با آن وجود دارند. برای دریافت آنها، به نوار ابزار عمودی که در سمت چپ صفحه وجود دارد، مراجعه کنید و گزینهی extensions آنرا انتخاب نمائید:
در اینجا اگر aspnetcore را جستجو کنید، لیست بستههای code snippets برچسب گذاری شدهی با aspnetcore ظاهر میشود. در همینجا اگر یکی از آنها را انتخاب کنید، در سمت راست صفحه میتوانید توضیحات آن را نیز مشاهده و مطالعه نمائید (بدون نیاز به خروج از IDE).
برای مثال wilderminds-aspnetcore-snippets را نصب کنید. پس از آن تنها کافی است mvc6 را درون یک کلاس کنترلر تایپ نمائید تا امکانات آن ظاهر شوند:
برای نمونه پس از ایجاد یک فایل خالی کنترلر، انتخاب code snippet ایی به نام mvc6-controller، سبب ایجاد یک کنترلر کامل به همراه اکشن متدهایی پیش فرض میشود. بنابراین معادل قالبهای new items در نگارش کامل ویژوال استودیو در اینجا میتوان از Code Snippets استفاده کرد.
درکل برای یافتن مواردی مشابه، بهتر است واژهی کلیدی snippets را در قسمت extensions جستجو نمائید و آنها صرفا مختص به #C یا ASP.NET Core نیستند.
مدیریت ارجاعات و بستههای نیوگت در برنامههای ASP.NET Core
تا اینجا مدیریت فایلها و نحوهی تکمیل آنها را توسط code snippets، بررسی کردیم. قدم بعدی و گردش کاری مهم مورد نیاز دیگر، نحوهی افزودن ارجاعات به پروژه در VSCode است.
مدیریت ارجاعات در نگارشهای جدید ASP.NET Core، در فایل csproj برنامه انجام میشوند. در اینجا است که بستههای نیوگت جدید، ارجاعات به پروژههای دیگر و حتی شماره فریم ورک مورد استفاده تعیین میشوند.
برای نمونه بستهی نیوگت DNTPersianUtils.Core را به لیست ارجاعات آن اضافه کنید:
<PackageReference Include="DNTPersianUtils.Core" Version="2.2.0" />
در اینجا با کلیک بر روی لینک و یا دکمهی restore ، کار دریافت این بسته و نصب آن انجام خواهد شد.
اگر علاقمند بودید تا اینکار را در خط فرمان به صورت دستی انجام دهید، دکمههای Ctrl+ back-tick را فشرده، تا امکانات خط فرمان درون VSCode ظاهر شوند و سپس دستور dotnet restore را صادر کنید.
روش دوم ثبت بستههای نیوگت در فایل csproj، مراجعه به خط فرمان (فشردن دکمههای دکمههای Ctrl+ back-tick) و صدور دستور ذیل است:
> dotnet add package DNTPersianUtils.Core
دیباگ برنامههای ASP.NET Core در VSCode
در قسمت قبل با فرامین dotnet run و dotnet build و همچنین نحوهی اجرای سریع آنها آشنا شدیم. در ادامه اگر به نوار ابزار عمودی کنار صفحهی آن دقت کنید، گزینهی دیباگ نیز وجود دارد:
در اینجا دو نوع نحوهی برپایی و اجرای برنامه را مشاهده میکنید که هر دو مورد در فایل vscode\launch.json. زمانیکه دیباگ پروژه را در ابتدای باز کردن آن در VSCode فعال میکنیم، تعریف شدهاند.
برای بررسی آن فایل HomeController.cs را گشوده و در ابتدای متد About آن یک break point را قرار دهید (با حرکت دادن اشارهگر ماوس، جائیکه شماره سطرها قرار دارند، علامت درج break point ظاهر میشود که با کلیک بعدی، دائمی خواهد شد و برعکس):
پس از آن تنها کاری که باید انجام داد، فشردن دکمهی F5 است که به معنای اجرای برنامه به همراه اتصال دیباگر به آن میباشد (در قسمت قبل، Ctrl+F5 را بررسی کردیم که به معنای اجرای برنامه، بدون اتصال دیباگر به آن است).
در ادامه پس از اجرای برنامه، بر روی لینک About کلیک کنید تا اکشن متد آن اجرا شود. بلافاصله کنترل کار به VSCode بازگشته و سطری که بر روی آن break-point قرار داده بودیم، ظاهر میشود:
اطلاعات بیشتر آن در برگهی دیباگ ظاهر میشوند و در کل تجربهی کاربری آن همانند سایر IDEهایی است که تاکنون با آنها کار کردهاید.
یک نکته: در اینجا در داخل فایلهای View (فایلهای razor) نیز میتوان break point قرار داد.
برپایی یک Watcher Build
ابزار ویژهای به همراه ابزارهای Build مخصوص پروژههای NET Core. وجود دارد به نام watcher که تغییرات پوشههای برنامه را تحت نظر قرار داده و با هر تغییری، پروژه را کامپایل میکند. به این ترتیب به سرعت میتوان آخرین تغییرات برنامه را در مرورگر بررسی کرد. برای نصب آن، تنظیم ذیل را به فایل csproj برنامه اضافه کنید:
<ItemGroup> <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" /> </ItemGroup>
اکنون برای استفادهی از آن تنها کافی است دستور ذیل را صادر کنید:
>dotnet watch run ?[90mwatch : ?[39mStarted Hosting environment: Production Content root path: D:\vs-code-examples\FirstAspNetCoreProject Now listening on: http://localhost:5000 Application started. Press Ctrl+C to shut down.
یک نکته: اینبار برای دیباگ برنامه، باید گزینهی attach را انتخاب کرد:
اگر بر روی دکمهی سبز رنگ کنار آن کلیک کنید، لیست پروسههای دات نتی ظاهر شده و در این حالت میتوانید دیباگر را به پروسهی dotnet exec ایی که به dll برنامه اشاره میکند، متصل کنید (و نه پروسهی dotnet watch run که در حقیقت پروسهی dotnet exec را مدیریت میکند).