مطالب
MongoDB #1

مروری بر MongoDB

MongoDB یک پایگاه داده سند-گرا (Document-Oriented) و مستقل از سکو است که کارائی بالا، دسترسی پذیری بالا و مقیاس پذیری آسانی را فراهم می‌کند. MongoDB بر اساس مفهوم مجموعه (Collection) و سند (Document) کار می‌کند.

پایگاه داده

پایگاه داده یک نگهدارنده‌ی فیزیکی برای مجموعه‌ها است. هر پایگاه داده مجموعه ای از فایل‌های خود را روی فایل سیستم دارد.  یک سرور MongoDB معمولا چندین پایگاه داده دارد. 

مجموعه

مجموعه یک گروه از سندهای MongoDB است. مجموعه معادل جدول در پایگاه داده‌های رابطه‌ای (Relational Database) است. یک مجموعه داخل یک پایگاه داده وجود دارد. مجموعه‌ها به شمای (Schema) تاکید ندارند. سندهای داخل مجموعه می‌توانند فیلدهای مختلفی داشته باشند. معمولا همه‌ی سندهای داخل یک مجموعه، شبیه یا مربوط به یک هدف هستند.

سند

یک سند مجموعه ای از جفت‌های کلید-مقدار (Key-Value Pairs) است. سند، شمای پویا دارد؛ یعنی سندها در مجموعه‌های مشابه نیازی به ساختار یا فیلدهای مشابه ندارند و فیلدهای مشترک در سند ممکن است نوع داده‌های متفاوتی را نگهداری کنند. جدول زیر مقایسه اصطلاحات پایگاه داده‌های رابطه‌ای و MongDB را نمایش می‌دهد:

  MongoDB   پایگاه داده رابطه ای
 پایگاه داده  پایگاه داده
 مجموعه    جدول
 سند  سطر
 فیلد  ستون
 سند توکار  ملحق کردن (Join)
کلید اصلی
(کلید پیش فرض  _id  توسط
MongoDB  فراهم شده)
 کلید اصلی
 پایگاه داده نسخه سرور و کلاینت  
Mongod Mysqld/Oracle 
mongo mysql/sqlplus 

مثالی از سند

در جدول زیر ساختار سند یک وبلاگ آمده است که جفت‌های کلید-مقدار بسادگی با کاما ازهم جدا شده اند.

{
   _id: ObjectId(7df78ad8902c)
   title: 'MongoDB Overview', 
   description: 'MongoDB is no sql database',
   by: 'tutorials point',
   url: 'http://www.tutorialspoint.com',
   tags: ['mongodb', 'database', 'NoSQL'],
   likes: 100, 
   comments: [
      {
         user:'user1',
         message: 'My first comment',
         dateCreated: new Date(2011,1,20,2,15),
         like: 0 
      },
      {
         user:'user2',
         message: 'My second comments',
         dateCreated: new Date(2011,1,25,7,45),
         like: 5
      }
   ]
}

_id یک 12بایتی هگزادسیمال است که یکتایی هر سند را اطمینان می‌دهد. شما می‌توانید یک _id را هنگام درج سند بسازید. اگر اینکار را نکنید، MongoDB یک شناسه‌ی یکتا را برای هر سند تهیه می‌کند. از این 12بایت، 4بایت اول آن مربوط به برچسب زمان جاری است، 3بایت بعدی برای شناسه‌ی ماشین، 2بایت بعدی برای شناسه‌ی پروسه MongoDB سرور و 3بایت باقیمانده یک مقدار صعودی ساده است. 

مطالب
SQL Injection چیست؟
برای ایجاد امنیت در نرم افزار، باید ابتدا مشکلات رایج را بدانیم. یکی از رایجترین نقائص امنیتی نرم افزارها SQL Injection می‌باشد.
SQL Injection در لغت به معنی تزریق کد SQL می‌باشد. در اصلاح یعنی تزریق دستوراتی به کد SQL تولیدی یک نرم افزار به نحوی که به جای عمل مورد انتظار برنامه نویس آن، کاری را که ما می‌خواهیم انجام دهد. مثلا به جای اینکه هنگام ورود به برنامه وقتی کاربر مشخصات کاربری خود را وارد می‌کند، مشخصات کاربری را به نحوی وارد کنیم که بتوانیم بعنوان مدیر سامانه و یا یک کاربر معمولی بدون داشتن کلمه عبور وارد سیستم شویم.
البته همیشه از این نوع حمله برای ورود به سیستم استفاده نمی‌شود. یعنی ممکن است هکر به عنوان یک کاربر عادی وارد سیستم شود ولی با به کاربردن دستورات خاص SQL در بخش‌های مختلف، بتواند اطلاعاتی را حذف نماید.
خوب حالا این کار چگونه انجام می‌شود؟
فرض کنید برنامه نویسی کد چک نام کاربری را اینگونه نوشته باشد:
SqlCommand cmd=new SqlCommand ("select count(*) from login where user='"+userName+"' and pass='"+password+"'",con);

فکر نکنید خوب این نوع کد نویسی مربوط به زمان تیرکمون شاه است! همین امروز در نظارت از یک پروژه به این نکته برخورد کردم! دلیل نوشتن این مقاله هم همین کد بود.
خوب حالا مگر کد بالا چه مشکلی دارد؟ ;) اگر کاربر در نامه کاربری و کلمه عبور مقادیر معمولی وارد کند (مانند admin, salam123) کد sql تولید شده به شکل زیر خواهد بود:

select count(*) from login where user='admin' and pass='salam123'

خوب حالا اگر کاربر کمی با ورودی‌ها بازی کند. به عنوان مثال فرض کنید به جای کلمه عبور تایپ کند
' or 1=1 --

نتیجه حاصله خواهد بود:
select count(*) from login where user='admin' and pass='' or 1=1 --'

با وارد کردن این دستور کاربر بدون داشتن کلمه عبور خواهد توانست وارد سیستم شود. موردی که توضیح دادم پایه مسئله بود. ما قصد آموزش هک نداریم ولی داشتن اطلاعات پایه لازم است. ممکن است فردی بگوید خوب ما قبل از تولید همچین کدی ' را از رشته کلمه عبور حذف می‌کنیم. خیلی خوب ولی اگر هکر از معادل unicode آن استفاده کرد چه؟ اگر و اگر و اگر...
راه حل‌های متعددی برای این موضوع پیشنهاد شده است. ولی ساده‌ترین و کارآمد‌ترین راه، استفاده از پارامترها می‌باشد که علاوه بر حذف این خطر باعث ایجاد و ذخیره query plan در sql server می‌شود و اجرای این query را در آینده تسریع می‌کند.
بنابراین می‌توان کد فوق را به صورت زیر بازنویسی کرد:
SqlCommand cmd=new SqlCommand ("select count(*) from login where user=@u and pass=@p",con);

cmd.Parameters.Add("@u", SqlDbType.Varchar, 10).Value=TextLogin.Text.Trim();

cmd.Parameters.Add("@p", SqlDbType.Varchar,10).Value=TextPwd.Text.Trim();
مطالب
چگونه یک الگوی طراحی را انتخاب و اعمال کنیم؟

با توجه به اینکه الگوهای طراحی زیادی وجود دارند، چگونه می­‌توانید مناسب­ترین الگوی طراحی را برای حل مسئله خود انتخاب کنید و مهم­تر اینکه چگونه آن را اعمال نمایید؟ برای پاسخ به این سوال، رهنمودهای زیر را همیشه در نظر داشته باشید:

-  شما نمی­‌توانید یک الگو را به کار بگیرید مگر آنکه آن را به خوبی فهمیده باشید. بنابراین در اولین گام باید اصول و الگوهای طراحی را هم به شکل انتزاعی و هم به شکل واقعی خوانده و تمرین کنید. دقت کنید که یک الگو را به شکل­‌های مختلفی می­توان پیاده سازی کرد. هر چه پیاده سازی­‌های بیشتری ببینید، به هدف و چگونگی استفاده از آن بهتر مسلط می­‌شوید.

- آیا می­‌خواهید با استفاده از یک الگوی طراحی، برنامه خود را پیچیده‌­تر کنید؟ این معمول است که توسعه دهندگان برای حل هر مسئله از الگوهای طراحی استفاده می­کنند. ابتدا هزینه و فایده پیاده سازی یک الگو را ارزیابی کرده، سپس اقدام به استفاده کنید. همیشه قاعده KISS  را به خاطر داشته باشید.

- مسئله خود را تعمیم دهید. پیامدهای مسئله خود را با دید انتزاعی و سطح بالا بررسی کنید. به یاد داشته باشید که الگوهای طراحی، راه حل­های سطح بالا برای مسائل سطح بالا هستند. بنابراین روی پیامدهای جزئی یا وابسته به دامین مسئله خود تمرکز نکنید.

- به الگوهای مشابه و هم گروه نگاه کنید. اگر قبلا از یک الگو استفاده کرده‌­اید بدین معنی نیست برای هر مسئله­‌ای آن الگو درست است.

- هر چیزی که تغییر می­کند را بسته بندی کنید. ببینید که چه چیزی در برنامه کاربردی شما ممکن است تغییر کند.  اگر شما می­دانید که یک الگوریتم اعمال تخفیف ممکن است به مرور زمان تغییر کند، به دنبال الگویی باشید که تغییرات در آن الگوریتم را بدون تاثیر بر سایر قسمت­های برنامه کاربردی انجام دهید.

- وقتی که یک الگو را انتخاب کردید، از زبان الگو در کنار زبان دامین برای نام گذاری کلاس­ها استفاده کنید. برای مثال اگر ازالگوی Strategy  استفاده می‌­کنید تا هزینه حمل و نقل کالا توسط شرکت FedEx را محاسبه کند، از نام FedExShippingCostStrategy استفاده کنید. با استفاده از زبان مشترک بین الگو طراحی و مدل دامین، کد برنامه برای شما و دیگران خواناتر و قابل فهم‌­تر می­گردد.

- همیشه منظور هر الگو را در ذهن خود مرور کنید و هنگام برخورد با یک مسئله به دنبال مناسب‌­ترین الگو بگردید. یک تمرین یادگیری عالی  شناسایی الگوهای طراحی در فریم ورک .Net است. برای مثال، ASP.Net Cache از الگوی Singleton استفاده می­کند و کلاس Guid از الگوی Factory بهره می‌­برد.

تا به حال شما باید هدف و الگوریتم استفاده از الگوهای طراحی را درک کرده باشید. در ادامه  با لایه بندی برنامه کاربردی آشنا می­‌شوید و سپس نحوه استفاده از این الگوها در لایه­‌های مختلف را فرا خواهید گرفت.  

مطالب
چگونه نرم افزارهای تحت وب سریعتری داشته باشیم؟ قسمت ششم
قسمت پنجم 

17. پرهیز از استفاده نسخه debug
وقتی به ASP.NET مراجعه می‌کنید، توجه فرمایید که از چه نوع build برای محصول نهایی استفاده می‌کنید. وقتی از نسخه debug برنامه استفاده می‌کنید، بهبود دهنده‌های سطح کامپایلر عمل نکرده و کدشما در حالت بهینه اجرا نخواهد شد (کد شما همانگونه که هست اجرا می‌شود!).
برای مثال هنگامی که از نسخه release استفاده می‌کنید، کامپایلر c# به صورت خودکار از StringBuilder‌ها به جای تلفیق عادی رشته ها، از آرایه‌ها به جای لیست ها، از دستور switch/case به جای دستورات if/then/else، تلفیق شروط با یکدیگر و... استفاده کرده و کد شما را در حالت بهینه‌تری اجرا می‌کند. عدم استفاده از این نسخه شما را از این مزایا محروم می‌سازد و نرم افزار شما به کندی اجرا خواهد شد. البته ناگفته نماند این موضوع فقط باید برای محصول نهایی استفاده شود و جهت دیباگ کردن برنامه همچنان باید از نسخه debug استفاده نمایید.
توجه نمایید می‌توانید با استفاده از متغیرهای کامپایلر در کد خود بخشی از کد را مختص build خاصی از برنامه کنید. مثلا اگر برنامه در حال debug کامپایل شد، MiniProfiler را فعال کن در غیر این صورت غیر فعال باشد.
#if DEBUG
    //فعال کردن MiniProfiler
#endif

18.تنظیم دقیق لاگ‌های سیستم در محیط اجرا
وقتی محصول نهایی را آماده می‌کنید، فراموش نکنید که سطح لاگ گیری را در سطح مطلوبی قرار دهید تا بتوانید در صورت نیاز برنامه را اشکال زدایی کنید. البته زیاده روی در این مورد نیز می‌تواند مشکل زا باشد.
اکثر برنامه نویسان هنگامی که محصول نهایی را برای مشتری آماده می‌کنند، لاگ را غیر فعال می‌کنند تا کاربر سرعت بیشتری را تجربه کند. این سیاست غلط شما را از امکانات بی نظیر لاگ کردن (مانند وقابع نگاری امنیتی، رفع سریع مشکلات و...) محروم می‌سازد. بنابر این حتما سیستم لاگ خود را در زمان تولید محصول اصلی (و نصب بر روی سرور اصلی) در حالت متعادلی تنظیم نمایید. کمی تست و تجربه شما را در این امر یاری می‌کند.

19.مشخص کردن اندازه عکس
مشخص کردن اندازه عکس در تک img به صورت css یا attribute باعث می‌شود که همان اولین بار که صفحه رندر می‌شود، اندازه مورد نیاز عکس به آن اختصاص یابد تا در صورت دانلود سریعا جایگرین آن گردد. عدم مشخص کردن سایز عکس (طول و عرض) باعث رندر شدن مجدد تمامی المان‌های صفحه بعد از دانلود هر عکس از سرور می‌شود و منابع با ارزش cpu کاربر شما را به سادگی از بین می‌برد.
<img src="smiley.gif" alt="Smiley face" height="42" width="42">

مطالب
Highlight کردن لینک صفحه جاری در ASP.NET MVC
راه حل‌های مختلفی جهت Highlight کردن لینک صفحه جاری وجود دارد و مهم‌ترین کاربرد آن در منوی اصلی سایت است.

در این مطلب سعی داریم با ارائه یک Helper راه حل مناسبی را برای این موضوع ارائه کنیم. مسئولیت این Helper ایجاد لینک است با در نظر گرفتن یک شرط: آیا لینک ایجاد شده به Action جاری اشاره دارد؟ اگر بله یک CSS Class با عنوان currentMenuItem به آن اضافه کن.

public static MvcHtmlString MenuLink(this HtmlHelper helper, string text, string action, string controller)
{            
    var routeData = helper.ViewContext.RouteData.Values;
    var currentController = routeData["controller"];
    var currentAction = routeData["action"];             
 
    if(String.Equals(action, currentAction as string,
              StringComparison.OrdinalIgnoreCase)
        &&
       String.Equals(controller, currentController as string,
               StringComparison.OrdinalIgnoreCase))
        
    {
       return helper.ActionLink(
           text,action, controller, null,
           new { @class="currentMenuItem"}
           );
    }
    return helper.ActionLink(text, action, controller);
}
نحوه استفاده:
<li>@Html.MenuLink("Contact", "Contact", "Home")</li>
و البته کمی تغییرات در فایل CSS خود را فراموش نکنید:
ul#menu li a {
    background: none;
    color: #999;
    padding: 5px;
    border-radius: 15px;
    text-decoration: none;
}
 
ul#menu li a.currentMenuItem {
    background-color: black;   
    color: white;
}
همچنین دوستانی که از Bootstrap و البته Navbar آن استفاده می‌کنند می‌توانند با کمی تغییرات در این Helper استفاده بهینه ای از آن داشته باشند:
public static MvcHtmlString MenuLinkBootstrap(this HtmlHelper helper, string text, string action, string controller)
{

    var routeData = helper.ViewContext.RouteData.Values;
    var currentController = routeData["controller"];
    var currentAction = routeData["action"];

   if (String.Equals(action, currentAction as string, StringComparison.OrdinalIgnoreCase) && String.Equals(controller, currentController as string, StringComparison.OrdinalIgnoreCase))
   {
        return new MvcHtmlString("<li class=\"active\">" + helper.ActionLink(text, action, controller) + "</li>");
   }
   
    return new MvcHtmlString("<li>" + helper.ActionLink(text, action, controller) + "</li>");

}

مطالب
فعال سازی قسمت ارسال فایل و تصویر ویرایشگر آنلاین RedActor در ASP.NET MVC
در سایت جاری از ویرایشگر آنلاین RedActor استفاده شده و کار کردن با آن هم بسیار ساده است:
یک TextArea ساده را به صفحه اضافه کرده و این افزونه جی‌کوئری را بر روی آن اجرا می‌کنید. به این ترتیب TextArea به صورت خودکار تبدیل به یک ویرایشگر مطلوب خواهد شد. برای مثال:
@Html.TextAreaFor(model => model.ArticleBody, htmlAttributes: new { style = "width:98%; height:500px" })

<script type="text/javascript">
$('#ArticleBody').redactor({
                autoformat: false,
                convertDivs: false
            });
</script>
اما فعال سازی قسمت ارسال فایل و تصویر همراه با آن چطور؟
یک سری مثال نوشته شده با PHP به همراه این ویرایشگر آنلاین هستند که برای ایده گرفتن بد نیستند (البته به این معنا نیست که این ویرایشگر نیازی به PHP دارد. تنها قسمت سمت سرور مثال‌های آن با PHP است). برای مثال اگر در PHP از دستور echo برای ارائه یک نتیجه نهایی به ویرایشگر RedActor استفاده شده، معادل آن در ASP.NET MVC مساوی return Content است.
<script type="text/javascript">
$('#ArticleBody').redactor({
                imageUpload: "@Url.Action(result: MVC.RedactorUpload.ImageUpload())",
                fileUpload: "@Url.Action(result: MVC.RedactorUpload.FileUpload())",
                linkFileUpload: "@Url.Action(result: MVC.RedactorUpload.FileLinkUpload())"
                , autoformat: false
                , convertDivs: false
            });
</script>
برای فعال سازی قسمت آپلود این ادیتور نیاز است پارامترهای imageUpload، fileUpload و linkFileUpload مقدار دهی شوند.
همانطور که ملاحظه می‌کنید از T4MVC برای مشخص سازی مسیرها استفاده شده. برای مثال MVC.RedactorUpload.ImageUpload به این معنا است که در کنترلری به نام RedactorUpload، اکشن متدی به نام ImageUpload پذیرای ارسال فایل ادیتور خواهد بود و به همین ترتیب در مورد سایر پارامترها.
RedactorUploadController هم ساختار بسیار ساده‌ای دارد. برای مثال هر کدام از متدهای آپلود یاد شده یک چنین امضایی دارند:
[HttpPost]
public virtual ActionResult ImageUpload(HttpPostedFileBase file)
{
}
البته در مورد مسایل امنیتی آپلود هم پیشتر در سایت بحث شده است. برای مثال در اینجا استفاده از فیلتر زیر را فراموش نکنید:
 [AllowUploadSpecialFilesOnly(".jpg,.gif,.png")]
در هر کدام از متدهای آپلود (به سه متد برای سه پارامتر یاد شده نیاز است)، ابتدا HttpPostedFileBase را در پوشه‌ایی که مدنظر دارید ذخیره کنید. سپس باید محتوایی را به RedActor بازگشت دهید و اصل کار یکپارچگی با ASP.NET MVC نیز در همینجا است:
در حالت imageUpload، محتوایی به شکل زیر باید بازگشت داده شود:
 return Content("<img src='" + path + "' />");
در حالت fileUpload، پس از ذخیره سازی فایل در سرور، مسیر آن باید به نحو زیر بازگشت داده شود:
 return Content("<a href=" + path + ">" + someName + "</a>");
و در حالت linkFileUpload فقط باید مسیر نهایی فایل ذخیره شده بر روی سرور را بازگشت دهید:
 return Content(path);

همچنین باید دقت داشت که کار ارسال فایل به سرور توسط خود افزونه RedActor انجام می‌شود و نیازی به کدنویسی ندارد. فقط باید سمت سرور آن‌را به نحوی که عنوان شد مدیریت کنید. ابتدا فایل را در سرور ذخیره کنید. سپس باید یک محتوای رشته‌ای را به نحو یاد شده، ساخت و توسط return Content بازگشت داد.

پ.ن.
قسمتی از مطالب متن فوق در نگارش جدید این ویرایشگر به نحو زیر تغییر کرده است.
مطالب
نحوه صحیح تولید Url در ASP.NET MVC
کار متد کمکی Url.Action ایجاد یک Url بر اساس تعاریف مسیریابی برنامه است. البته متد کمکی مشابهی نیز مانند Html.ActionLink در ASP.NET MVC وجود دارد که کار آن تولید یک لینک قابل کلیک است؛ اما ممکن است در حالتی خاص تنها نیاز به خود Url داشته باشیم و نه لینک قابل کلیک آن.

الف) اگر از jQuery Ajax استفاده می‌کنید، حتما باید استفاده از Url.Action را لحاظ کنید

برای نمونه اگر قسمتی از عملیات Ajaxایی برنامه شما به نحو زیر تعریف شده است :
$.ajax({  
           type: "POST",  
            url: "/Home/EmployeeInfo",  
...
... غلط است!
در این حالت برنامه شما تنها در زمانیکه در ریشه یک دومین قرار گرفته باشد کار خواهد کرد. اگر برنامه شما در مسیری مانند http://www.site.com/myNewApp نصب شود، کلیه فراخوانی‌های Ajax ایی آن دیگر کار نخواهند کرد چون مسیر url فوق به ریشه سایت اشاره می‌کند و نه مسیر جاری برنامه شما (در یک sub domain جدید).
به همین جهت در یک چنین حالتی حتما باید به کمک Url.Action مسیر یک اکشن متد را معرفی کرد تا به صورت خودکار بر اساس محل قرارگیری برنامه و تعاریف مسیریابی آن، Url صحیحی تولید شود.

@Url.Action("EmployeeInfo", "Home")

ب) دریافت Url مطلق از یک Url.Action

Urlهای تولید شده توسط Url.Action به صورت پیش فرض نسبی هستند (نسبت به محل نصب و قرارگیری برنامه تعریف می‌شوند). اگر نیاز به دریافت یک مسیر مطلق که با http برای مثال شروع می‌شود دارید، باید به نحو زیر عمل کرد:
@Url.Action("About", "Home", null, "http")
پارامتر چهارم تعریف شده، سبب خواهد شد لینک تولیدی از حالت نسبی خارج شود.


ج) استفاده از Url.Action در یک کنترلر

فرض کنید قصد تولید یک فید RSS را در کنترلری دارید. یکی از آیتم‌هایی که باید ارائه دهید، لینک به مطلب مورد نظر است. این لینک باید مطلق باشد همچنین در یک View هم قرار نداریم که به کمک @ بلافاصله به متد کمکی Url.Action دسترسی پیدا کنیم.
در کنترلرها، وهله جاری کلاس به شیء Url و متد Action آن به نحو زیر دسترسی دارد و خروجی نهایی آن یک رشته است:

var url = this.Url.Action(actionName: "Index",
                                  controllerName: "Post",
                                  protocol: "http",
                                  routeValues: new { id = item.Id });
بنابراین در کنترلرها نیز Urlها را دستی تولید نکنید و اجازه دهید بر اساس پارامترهای زیادی که در پشت صحنه Url.Action لحاظ می‌شود، Url صحیحی برای شما تولید گردد.


د) استفاده از Url.Action در کلاس‌های کمکی برنامه خارج از یک کنترلر

فرض کنید قصد تهیه یک Html Helper سفارشی را به کمک کدنویسی در یک کلاس مجزا دارید. در اینجا نیز نباید Urlها را دستی تولید کرد. در Html Helperهای سفارشی نیز می‌توان به کمک متد  UrlHelper.GenerateUrl، به همان امکانات Url.Action دسترسی یافت:

public static class Extensions  
    {  
        public static string MyLink(this HtmlHelper html, ...)  
        {  
            string url = UrlHelper.GenerateUrl(null, "actionName", "controllerName",
                                                null,
                                                html.RouteCollection,
                                                html.ViewContext.RequestContext,
                                                includeImplicitMvcValues: true);
//...
 
مطالب
ASP.NET MVC #16

مدیریت خطاها در یک برنامه ASP.NET MVC


استفاده از فیلتر HandleError

یکی از فیلترهای توکار ASP.NET MVC به نام HandleError،‌ می‌تواند کار هدایت کاربر را به یک صفحه‌ی خطای عمومی، در حین بروز استثنایی در برنامه،‌ انجام دهد. برای آزمایش آن یک برنامه خالی جدید ASP.NET MVC را آغاز کنید. سپس یک کنترلر جدید را با محتوای زیر به آن اضافه نمائید:

using System;
using System.Web.Mvc;

namespace MvcApplication13.Controllers
{
public class HomeController : Controller
{
[HandleError]
public ActionResult Index()
{
throw new InvalidOperationException();
return View();
}
}
}

در اینجا جهت آزمایش برنامه، به عمد یک استثنای دستی را صادر می‌کنیم. برای آزمایش برنامه هم نیاز است آن‌را خارج از دیباگر VS.NET اجرا کرد (آدرس برنامه را مستقیما خارج از VS.NET در یک مرورگر وارد کنید). همچنین یک سطر زیر را نیز لازم است به فایل web.config برنامه اضافه نمائید:

<system.web>
<customErrors mode="On" />

اکنون اگر برنامه را خارج از مرورگر اجرا کنید، با توجه به استفاده از ویژگی HandleError و همچنین بروز یک استثنا در متد Index، خودبخود صفحه Views\Shared\Error.cshtml به کاربر نمایش داده خواهد شد. در غیراینصورت صفحه زرد رنگ پیش فرض خطای ASP.NET به کاربر نمایش داده می‌شود که محتوای آن‌ها بیشتر برای برنامه نویس‌ها مناسب است و نه کاربران نهایی سیستم.
اگر علاقمند باشید که این ویژگی به صورت خودکار به تمام متدهای کنترلرهای برنامه اعمال شود، کافی است یک سطر زیر را به متد Application_Start فایل Global.asax.cs اضافه نمائید:

GlobalFilters.Filters.Add(new HandleErrorAttribute());

البته نیازی به انجام اینکار نیست زیرا اگر به متد RegisterGlobalFilters فایل Global.asax.cs دقت کنیم، اینکار پیشتر توسط قالب پیش فرض VS.NET انجام شده است. فقط برای فعال سازی آن نیاز است تگ customErrors در فایل وب کانفیگ برنامه مقدار دهی و تنظیم شود.



استفاده از صفحه خطای سفارشی دیگری بجای فایل Error.cshtml

امکان تنظیم نمایش صفحه خطای سفارشی دیگری نیز وجود دارد. برای مثال استفاده از فایل Views\Shared\CustomErrorView.cshtml :

[HandleError(View = "CustomErrorView")]



استفاده از صفحات خطای متفاوت به ازای استثناهای مختلف

می‌توان فیلتر HandleError را تنها به یک نوع استثنای خاص محدود کرد. همچنین امکان استفاده از چندین ویژگی HandleError برای یک متد نیز وجود دارد:

[HandleError(ExceptionType = typeof(NullReferenceException), View = "ErrorHandling")]



دسترسی به اطلاعات استثناء در صفحه نمایش خطاها

زمانیکه برنامه به صفحه خطا هدایت می‌شود، نوع Model آن System.Web.Mvc.HandleErrorInfo می‌باشد:

@model System.Web.Mvc.HandleErrorInfo

@{
ViewBag.Title = "DbError";
}

<h2>An Error Has Occurred</h2>

@if (Model != null)
{
<p>@Model.Exception.GetType().Name<br />
thrown in @Model.ControllerName @Model.ActionName</p>
}

البته این نکته را صرفا به عنوان اطلاعات عمومی در نظر داشته باشید. زیرا اگر قرار باشد مجددا اصل استثناء را نمایش دهیم، همان صفحه زرد رنگ ASP.NET شاید بهتر باشد.



استفاده از تگ customErrors در فایل Web.config برنامه

ویژگی حالت تگ customErrors در فایل web.config برنامه، سه مقدار را می‌تواند بپذیرد:
الف) Off : صفحه زرد رنگ معرفی خطای ASP.NET را به همراه تمام اطلاعات مرتبط با استثنای رخ داده نمایش می‌دهد.
ب) RemoteOnly : همان حالت الف است با این تفاوت که صفحه خطا را فقط در کامپیوتری که وب سرور بر روی آن نصب است نمایش خواهد داد.
ج) On : یک صفحه خطای سفارشی شده را نمایش می‌دهد.

بنابراین هیچگاه از حالت Off استفاده نکنید. زیرا خطاهای نمایش داده شده، علاوه بر برنامه نویس، برای مهاجم به یک سایت نیز بسیار دلپذیر است!
حالت RemoteOnly در زمان توسعه برنامه توصیه می‌شود.
حالت On حین توزیع برنامه باید بکارگرفته شود.



مدیریت خطاهای رخ داده خارج از MVC Pipeline

HandleErrorAttribute تنها استثناهای رخ داده داخل ASP.NET MVC Pipeline را مدیریت می‌کند (یا خطاهایی از نوع 500). اگر این نوع استثناها خارج از آن رخ دهند مثلا فایلی یافت نشود (خطای 404) و امثال آن، باید به روش زیر عمل کرد:

<customErrors mode="On" defaultRedirect="error">
<error statusCode="404" redirect="error/notfound" />
<error statusCode="403" redirect="error/forbidden" />
</customErrors>

در اینجا اگر فایلی یافت نشد، کاربر به کنترلری به نام error و متدی به نام notfound هدایت خواهد شد. بنابراین نیاز به کنترلر زیر وجود دارد؛ به علاوه به ازای هر متد هم یک View متناظر باید اضافه شود (کلیک راست روی نام متد و انتخاب گزینه افزودن View جدید).

using System.Web.Mvc;

namespace MvcApplication13.Controllers
{
public class ErrorController : Controller
{
public ActionResult Index()
{
return View();
}

public ActionResult NotFound()
{
return View();
}

public ActionResult Forbidden()
{
return View();
}
}
}

برای آزمایش این قسمت، برنامه را اجرا کرده و سپس مثلا آدرس غیرموجود http://localhost/xyz را وارد کنید.



استفاده از فیلتر HandleError اجباری نیست

در همین قسمت قبل پس از افزودن customErrors و defaultRedirect آن که به نام یک کنترلر اشاره می‌کند، کلیه فیلترهای HandleError اضافه شده به برنامه را حذف کنید. سپس برنامه را خارج از محیط VS.NET اجرا کنید. باز هم متد Index کنترلر Error اجرا خواهد شد. به عبارتی الزاما نیازی به استفاده از فیلتر HandleError نیست و به کمک مقدار دهی صحیح تگ customErrors، کار نمایش خودکار صفحه سفارشی خطاها به کاربر انجام خواهد شد.
البته بدیهی است که گزینه‌های نمایش یک View خاص به ازای استثنایی ویژه، یکی از مزیت‌های استفاده از فیلتر HandleError می‌باشد که امکان تنظیم آن در فایل web.config وجود ندارد.



ثبت اطلاعات استثناهای رخ داده به کمک ELMAH

نمایش صفحه‌ی خطای سفارشی به کاربر، یکی از موارد ضروری تمام برنامه‌های ASP.NET است، اما کافی نیست. ثبت اطلاعات جزئیات استثناهای رخ داده در طول زمان می‌توانند به بالا بردن کیفیت برنامه به شدت کمک کنند. برای این منظور می‌توان همانند سابق از متد Application_Error قابل تعریف در فایل Global.asax.cs کمک گرفت؛ اما با وجود افزونه‌ای به نام ELMAH اینکار اتلاف وقت است و اصلا توصیه نمی‌شود. همچنین به کمک ELMAH می‌توان مشکلات را تبدیل به ایمیل‌های خودکار کرد یا از آن‌ها فید RSS درست نمود.
برای دریافت ELMAH یا به سایت اصلی آن مراجعه نمائید و یا به کمک NuGet هم به سادگی قابل دریافت است. پس از دریافت، ارجاعی را به اسمبلی آن (Elmah.dll) اضافه نمائید. در ادامه فایل web.config برنامه را گشوده و چند سطر زیر را به آن در قسمت configuration اضافه کنید:

<configuration>
<configSections>
<sectionGroup name="elmah">
<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"/>
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah"/>
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
<section name="errorTweet" requirePermission="false" type="Elmah.ErrorTweetSectionHandler, Elmah"/>
</sectionGroup>
</configSections>

سپس ذیل قسمت appSettings، تنظیمات پروایدر ذخیره سازی اطلاعات آن‌را وارد نمائید. مثلا در اینجا از فایل‌های XML برای ذخیره سازی اطلاعات استفاده خواهد شد (که امن‌ترین حالت ممکن است؛ از این لحاظ که اگر بانک اطلاعاتی را انتخاب کنید، ممکن است مشکل اصلی از همانجا ناشی شده باشد. بنابراین خطایی ثبت نخواهد شد. همچنین در این حالت نیازی به سایر DLLهای همراه ELMAH هم نیست). در اینجا مسیر ذخیره سازی اطلاعات در پوشه app_data/errorslog تنظیم شده است:

<elmah>
<security allowRemoteAccess="1"/>
<errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/App_Data/ErrorsLog"/>
</elmah>

در ادامه در قسمت system.web، دو تعریف زیر را اضافه نمائید. به این ترتیب امکان دسترسی به آدرس http://server/elmah.axd مهیا می‌گردد:

<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
</httpModules>
<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah"/>
</httpHandlers>

البته برای IIS7 تنظیمات ذیل نیز باید اضافه شوند:

<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules runAllManagedModulesForAllRequests="true">
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
</modules>
<handlers>
<add name="Elmah" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah"/>
</handlers>
</system.webServer>

و به این ترتیب تنظیمات اولیه ELMAH به پایان می‌رسد (و با ASP.NET Web forms هیچ تفاوتی ندارد).
مرحله بعد، تنظیمات مسیریابی ASP.NET MVC است برای اینکه آدرس http://server/elmah.axd را وارد سیستم پردازشی خود نکند. البته اینکار پیشتر انجام شده است:

public static void RegisterRoutes(RouteCollection routes)
{
//routes.IgnoreRoute("elmah.axd");
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

بنابراین همین تنظیمات، به همراه قالب پیش فرض یک پروژه جدید ASP.NET MVC برای استفاده از ELMAH کفایت می‌کند. اکنون پروژه جاری را یکبار دیگر خارج از VS.NET اجرا کرده و سپس به مسیر http://localhost/elmah.axd جهت مشاهده خطاهای لاگ شده به همراه جزئیات کامل آن‌ها مراجعه کنید.

مشکل: استثناهای برنامه توسط ELMAH لاگ نمی‌شوند!

فیلتر HandleError با ELMAH سازگار نیست. زیرا با استفاده از آن، متدهای کنترلرها به صورت خودکار داخل یک try/catch اجرا شده و به این ترتیب استثناهای رخ داده، مدیریت گردیده و به ELMAH هدایت نمی‌شوند. بنابراین نیاز است به متد RegisterGlobalFilters فایل Global.asax.cs مراجعه کرده و سطر زیر را حذف کنید:

filters.Add(new HandleErrorAttribute());

و یا اگر قصد نداشتید اینکار را انجام دهید، می‌توان به نحو زیر نیز مشکل را حل کرد:

using System.Web.Mvc;
using Elmah;

namespace MvcApplication13.CustomFilters
{
public class ElmahHandledErrorLoggerFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (context.ExceptionHandled)
ErrorSignal.FromCurrentContext().Raise(context.Exception);
// all other exceptions will be caught by ELMAH anyway
}
}
}

در اینجا یک فیلتر سفارشی به برنامه اضافه شده است تا خطاهای مدیریت شده برنامه (خطاهای مدیریت شده توسط فیلتر HandleError توکار) را به موتور ELMAH هدایت کند. سایر خطاهای مدیریت نشده به صورت خودکار توسط ELMAH ثبت خواهند شد و نیازی به انجام کار اضافی در این مورد نیست.
سپس این فیلتر جدید را به صورت سراسری تعریف کنید:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ElmahHandledErrorLoggerFilter());
filters.Add(new HandleErrorAttribute());
}

ترتیب این‌ها هم مهم است. ابتدا باید ElmahHandledErrorLoggerFilter معرفی شود.


تذکر مهم!
حین استفاده از ELMAH یک نکته را فراموش نکنید:
اگر allowRemoteAccess آن‌را به عدد 1 تنظیم کرده‌اید، به هیچ عنوان از نام پیش فرض elmah.axd استفاده نکنید (هر نام اختیاری دیگری را که علاقمند بودید و به سادگی قابل حدس زدن نبود، در فایل web.config وارد کنید).


خلاصه بحث
1- در ASP.NET MVC نیازی نیست تا متدهای کنترلرها را با try/catch شلوغ کنید.
2- حتما قسمت customErrors فایل وب کانفیگ برنامه را دهی کنید (این مورد را به چک لیست اجباری تهیه یک برنامه ASP.NET MVC اضافه کنید).
3- استفاده از فیلتر HandleError اختیاری است. اگر از قابلیت فیلتر کردن استثناهای ویژه آن استفاده نمی‌کنید، مقدار دهی customErrors وب کانفیگ برنامه هم همان کار را انجام می‌دهد.
4- برای ثبت جزئیات دقیق استثناهای رخ داده در برنامه، از ELMAH استفاده کنید و بی‌جهت وقت خودتان را صرف بازنویسی این افزونه ارزشمند نکنید.

مطالب مشابه
معرفی ELMAH
ثبت استثناهای مدیریت شده توسط ELMAH

مطالب
اهمیت code review

تا جایی که دقت کردم (در بلاگ‌هایی که منتشر می‌شوند) در آنسوی آب‌ها، «code review» یک شغل محسوب می‌شود. سازمان‌ها، شرکت‌ها و امثال آن از مشاورین یا برنامه نویس‌هایی با مطالعه بیشتر دعوت می‌کنند تا از کدهای آن‌ها اشکال‌گیری کنند و بابت اینکار هم هزینه می‌کنند.
اگر علاقمند باشید قسمتی از یک پروژه سورس باز دریافت شده از همین دور و اطراف را با هم مرور کنیم:

//It's only for code review purpose!
protected void Button1_Click1(object sender, EventArgs e)
{
string strcon;
string strUserURL;
string strSQL;
string strSQL1;
strSQL = "SELECT UserLevel FROM listuser " + "WHERE Username='" + TextBox2.Text + "' " + "And Password='" + TextBox3.Text + "';";
strSQL1 = "SELECT Pnumber FROM listuser " + "WHERE Username='" + TextBox2.Text + "' " + "And Password='" + TextBox3.Text + "';";
strcon = @"Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\bimaran.mdf;Integrated Security=True;User Instance=True";
SqlConnection myConnection = new SqlConnection(strcon);

SqlCommand myCommand = new SqlCommand(strSQL, myConnection);
SqlCommand myCommand1 = new SqlCommand(strSQL1, myConnection);
myConnection.Open();

strUserURL = (string)myCommand.ExecuteScalar();
send = (string)myCommand1.ExecuteScalar();
myCommand.Dispose();
myCommand1.Dispose();
myConnection.Close();


if (strUserURL != null)
{
Label1.Text = "";

url = "?Pn=" + code(send);
FormsAuthentication.SetAuthCookie(TextBox2.Text, true);
Response.Redirect("Page/" + strUserURL + url);
}
else
Label3.Text = "چنین کاربری با این مشخصات ثبت نشده است.";
}


مروری بر این کد یا «مشکلات این کد»:
- کانکشن استرینگ داخل کدها تعریف شده. یعنی اگر نیاز به تغییری در آن بود باید کدهای برنامه تغییر کنند. آن هم نه فقط در این تابع بلکه در کل برنامه.
- از پارامتر استفاده نشده. کد 100 درصد به تزریق اس کیوال آسیب پذیر است.
- نحوه‌ی dispose شیء کانکشن غلط است. هیچ ضمانتی وجود ندارد که کدهای فوق سطر به سطر اجرا شود و خیلی زیبا به سطر بستن کانکشن استرینگ برسد. فقط کافی است این میان یک استثنایی صادر شود و تمام. به عبارتی این سایت فقط با کمتر از 30 کاربر همزمان از کار می‌افته. بعد نیاید بگید من یک سرور دارم با 16 گیگ رم ولی باز کم میاره! همش برنامه کند میشه. همش سایت بالا نمیاد!
- همین تعریف کردن متغیرها در ابتدای تابع یعنی این برنامه نویس هنوز حال و هوای ANSI C را دارد!
- مهم نیست لایه بندی کنید. ولی یک لایه در این نوع پروژه‌ها الزامی است و آن هم DAL نام دارد. DAL یعنی کثافت کاری نکنید. یعنی داخل هر تابع کُپه کُپه بر ندارید open و close بذارید. برید یک تابع یک گوشه‌ای درست کنید که این عملیات را محصور کند.
- همین وجود Button1 و Label1 یعنی تو خود شرح مفصل بخوان از این مجمل!

مطالب
سرعت ایجاد اشیاء CLR

به نظر شما چه تعداد شیء CLR را می‌توان در یک ثانیه ایجاد کرد؟
برنامه کنسول زیر دو نسخه معمولی و نسخه پردازش موازی یک آزمایش ساده را برای اندازه گیری این مطلب ارائه می‌دهد:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ObjectInitSpeedTest
{
class Program
{
//Note: don't forget to build it in Release mode.
static void Main()
{
normalSpeedTest();
parallelSpeedTest();

Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Press a key ...");
Console.ReadKey();
}

private static void parallelSpeedTest()
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("parallelSpeedTest");

long totalObjectsCreated = 0;
long totalElapsedTime = 0;

var tasks = new List<Task>();
var processorCount = Environment.ProcessorCount;

Console.WriteLine("Running on {0} cores", processorCount);

for (var t = 0; t < processorCount; t++)
{
tasks.Add(Task.Factory.StartNew(
() =>
{
const int reps = 1000000000;
var sp = Stopwatch.StartNew();
for (var j = 0; j < reps; ++j)
{
new object();
}
sp.Stop();

Interlocked.Add(ref totalObjectsCreated, reps);
Interlocked.Add(ref totalElapsedTime, sp.ElapsedMilliseconds);
}
));
}

// let's complete all the tasks
Task.WaitAll(tasks.ToArray());

Console.WriteLine("Created {0:N} objects in 1 sec\n", (totalObjectsCreated / (totalElapsedTime / processorCount)) * 1000);
}

private static void normalSpeedTest()
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("normalSpeedTest");

const int reps = 1000000000;
var sp = Stopwatch.StartNew();
sp.Start();
for (var j = 0; j < reps; ++j)
{
new object();
}
sp.Stop();

Console.WriteLine("Created {0:N} objects in 1 sec\n", (reps / sp.ElapsedMilliseconds) * 1000);
}
}
}

هنگام اجرای برنامه فراموش نکنید که حالت Build را بر روی Release قرار دهید.