۱۳۹۱/۰۱/۱۲

ASP.NET MVC #7


آشنايي با Razor Views

قبل از اينكه بحث جاري ASP.NET MVC را بتوانيم ادامه دهيم و مثلا مباحث دريافت اطلاعات از كاربر، كار با فرم‌ها و امثال آن‌را بررسي كنيم، نياز است حداقل به دستور زبان يكي از View Engineهاي ASP.NET MVC آشنا باشيم.
MVC3 موتور View جديدي را به نام Razor معرفي كرده است كه به عنوان روش برگزيده ايجاد Viewها در اين سيستم به شمار مي‌رود و فوق العاده نسبت به ASPX view engine سابق، زيباتر، ساده‌تر و فشرده‌تر طراحي شده است و يكي از اهداف آن تلفيق code و markup مي‌باشد. در اين حالت ديگر پسوند فايل‌هاي Viewها همانند سابق ASPX نخواهد بود و به cshtml و يا vbhtml تغيير يافته است. همچنين برخلاف web forms view engine از System.Web.Page مشتق نشده است. و بايد دقت داشت كه Razor يك زبان برنامه نويسي جديد نيست. در اينجا از مخلوط زبان‌هاي سي شارپ و يا ويژوال بيسيك به همراه تگ‌هاي html استفاده مي‌شود.
البته اين را هم بايد عنوان كرد كه اين مسايل سليقه‌اي است. اگر با web forms view engine راحت هستيد، با همان كار كنيد. اگر با هيچكدام از اين‌ها راحت نيستيد (!) نمونه‌هاي ديگر هم وجود دارند، مثلا:

Razor Views يك سري قابليت جالب را هم به همراه دارند:
1) امكان كامپايل آن‌ها به درون يك DLL وجود دارد. مزيت: استفاده مجدد از كد، عدم نياز به وجود صريح فايل cshtml يا vbhtml بر روي ديسك سخت.
2) آزمون پذيري: از آنجائيكه Razor viewها به صورت يك كلاس كامپايل مي‌شوند و همچنين از System.Web.Page مشتق نخواهند شد، امكان بررسي HTML نهايي توليدي آن‌هابدون نياز به راه اندازي يك وب سرور وجود دارد.
3) IntelliSense ويژوال استوديو به خوبي آن‌را پوشش مي‌دهد.
4) با توجه به مواردي كه ذكر شد، يك اتفاق جالب هم رخ داده است: امكان استفاده از Razor engine خارج از ASP.NET MVC هم وجود دارد. براي مثال يك سرويس ويندوز NT طراحي كرده‌ايد كه قرار است ايميل فرمت شده‌اي به همراه اطلاعات مدل‌هاي شما را در فواصل زماني مشخص ارسال كند؟ مي‌توانيد براي طراحي آن از Razor engine استفاده كنيد و تهيه خروجي نهايي HTML آن نيازي به راه اندازي وب سرور و وهله سازي HttpContext ندارد.


ساختار پروژه مثال جاري

در ادامه مرور سريعي خواهيم داشت بر دستور زبان Razor engine و جهت نمايش اين قابليت‌ها، يك مثال ساده را در ابتدا با مشخصات زير ايجاد خواهيم كرد:
الف) يك empty ASP.NET MVC 3 project را ايجاد كنيد و نوع View engine را هم در ابتداي كار Razor انتخاب نمائيد.
ب) دو كلاس زير را به پوشه مدل‌هاي برنامه اضافه كنيد:
namespace MvcApplication3.Models
{
    public class Product
    {
        public Product(string productNumber, string name, decimal price)
        {
            Name = name;
            Price = price;
            ProductNumber = productNumber;
        }
        public string ProductNumber { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}

using System.Collections.Generic;

namespace MvcApplication3.Models
{
    public class Products : List<Product>
    {
        public Products()
        {
            this.Add(new Product("D123", "Super Fast Bike", 1000M));
            this.Add(new Product("A356", "Durable Helmet", 123.45M));
            this.Add(new Product("M924", "Soft Bike Seat", 34.99M));
        }
    }
}

كلاس Products صرفا يك منبع داده تشكيل شده در حافظه است. بديهي است هر نوع ORM ايي كه يك ToList را بتواند در اختيار شما قرار دهد، توانايي تشكيل ليست جنريكي از محصولات را نيز خواهد داشت و تفاوتي نمي‌كند كه كداميك مورد استفاده قرار گيرد.

ج) سپس يك كنترلر جديد به نام ProductsController را به پوشه Controllers برنامه اضافه مي‌كنيم:

using System.Web.Mvc;
using MvcApplication3.Models;

namespace MvcApplication3.Controllers
{
    public class ProductsController : Controller
    {
        public ActionResult Index()
        {
            var products = new Products();
            return View(products);
        }
    }
}

د) بر روي نام متد Index كليك راست كرده، گزينه Add view را جهت افزودن View متناظر آن، انتخاب كنيد. البته مي‌شود همانند قسمت پنجم گزينه Create a strongly typed view را انتخاب كرد و سپس Product را به عنوان كلاس مدل انتخاب نمود و در آخر خيلي سريع يك ليست از محصولات را نمايش داد، اما فعلا از اين قسمت صرفنظر نمائيد، چون مي‌خواهيم آن‌ را دستي ايجاد كرده و توضيحات و نكات بيشتري را بررسي كنيم.

ه) براي اينكه حين اجراي برنامه در VS.NET هربار نخواهيم كه آدرس كنترلر Products را دستي در مرورگر وارد كنيم، فايل Global.asax.cs را گشوده و سپس در متد RegisterRoutes، در سطر Parameter defaults، مقدار پيش فرض كنترلر را مساوي Products قرار دهيد.


مرجع سريع Razor

ابتدا كدهاي View متد Index را به شكل زير وارد نمائيد:

@model List<MvcApplication3.Models.Product>
@{
    ViewBag.Title = "Index";
    var number = 12;
    var data = "some text...";    
    <h2>line1: @data</h2>
    
    @:line-2: @data <‪br />
    <text>line-3:</text> @data
}
<‪br />
site@(data)
<‪br />
@@name
<‪br />
@(number/10)
<‪br />
First product: @Model.First().Name
<‪br />
@if (@number>10)
{
  <span>@data</span>
}
else
{
  <text>Plain Text</text>
}
<‪br />
@foreach (var item in Model)
{
    <li>@item.Name, $@item.Price </li>
}

@*
    A Razor Comment
*@

<‪br />
@("First product: " + Model.First().Name)
<‪br />
<img src="@(number).jpg" />

در ادامه توضيحات مرتبط با اين كدها ارائه خواهد شد:

1) نحوه معرفي يك قطعه كد
@model List<MvcApplication3.Models.Product>
@{
    ViewBag.Title = "Index";
    var number = 12;
    var data = "some text...";    
    <h2>line1: @data</h2>
    
    @:line-2: @data <‪br />
    <text>line-3:</text> @data
}

اين كدها متعلق به Viewايي است كه در قسمت (د) بررسي ساختار پروژه مثال جاري، ايجاد كرديم. در ابتداي آن هم نوع model مشخص شده تا بتوان ساده‌تر به اطلاعات شيء Model به كمك IntelliSense دسترسي داشت.
براي ايجاد يك قطعه كد در Viewايي از نوع Razor به اين نحو عمل مي‌شود:
@{  ...Code Block.... }

در اينجا مجاز هستيم كدهاي سي شارپ را وارد كنيم. يك نكته جالب را هم بايد درنظر داشت: امكان نوشتن تگ‌هاي html هم در اين بين وجود دارد (بدون اينكه مجبور باشيم قطعه كد شروع شده را خاتمه دهيم، به حالت html معمولي سوئيچ كرده و دوباره يك قطعه كد ديگر را شروع نمائيم). مانند line1 مثال فوق. اگر كمي پايين‌تر از اين سطر مثلا بنويسيم line2 (به عنوان يك برچسب) كامپايلر ايراد خواهد گرفت، زيرا اين مورد نه متغير است و نه از پيش تعريف شده است. به عبارتي نبايد فراموش كنيم كه اينجا قرار است كد نوشته شود. براي رفع اين مشكل دو راه حل وجود دارد كه در سطرهاي دو و سه ملاحظه مي‌كنيد. يا بايد از تگي به نام text براي معرفي يك برچسب در اين ميان استفاده كرد (سطر سه) يا اگر قرار است اطلاعاتي به شكل يك متن معمولي پردازش شود ابتداي آن مانند سطر دوم بايد يك @: قرار گيرد.
كمي پايين‌تر از قطعه كد معرفي شده در بالا بنويسيد:
<‪br />
site@data

اكنون اگر خروجي اين View را در مرورگر بررسي كنيد، دقيقا همين site@data خواهد بود. چون در اين حالت Razor تصور خواهد كرد كه قصد داشته‌ايد يك آدرس ايميل را وارد كنيد. براي اين حالت خاص بايد نوشت:
<‪br />
site@(data)

به اين ترتيب data از متغير data تعريف شده در code block قبلي برنامه دريافت و نمايش داده خواهد شد.
شبيه به همين حالت در مثال زير هم وجود دارد:
<img src="@(number).jpg" />

در اينجا اگر پرانتزها را حذف كنيم، Razor فرض را بر اين خواهد گذاشت كه شيء number داراي خاصيت jpg است. بنابراين بايد به نحو صريحي، بازه كاري را مشخص نمائيم.

بكار گيري اين علامت @ يك نكته جنبي ديگر را هم به همراه دارد. فرض كنيد در صفحه قصد داريد آدرس توئيتري شخصي را وارد كنيد. مثلا:
<‪br />
@name

در اين حالت View كامپايل نخواهد شد و Razor تصور خواهد كرد كه قرار است اطلاعات متغيري به نام name را نمايش دهيد. براي نمايش اين اطلاعات به همين شكل، يك @ ديگر به ابتداي سطر اضافه كنيد:
<‪br />
@@name

2) نحوه معرفي عبارات
عبارات پس از علامت @ معرفي مي‌شوند و به صورت پيش فرض Html Encoded هستند (در قسمت 5 در اينباره بيشتر توضيح داده شد):
First product: @Model.First().Name

در اين مثال با توجه به اينكه نوع مدل در ابتداي View مشخص شده است، شيء Model به ليستي از Products اشاره مي‌كند.

يك نكته:
مشخص سازي حد و مرز صريح يك متغير در مثال زير نيز كاربرد دارد:
<‪br />
@number/10

اگر خروجي اين مثال را بررسي كنيد مساوي 12/10 خواهد بود و محاسبه‌اي انجام نخواهد شد. براي حل اين مشكل باز هم از پرانتز مي‌توان كمك گرفت:
<‪br />
@(number/10)
3) نحوه معرفي عبارات شرطي

@if (@number>10)
{
  <span>@data</span>
}
else
{
  <text>Plain Text</text>
}

يك عبارت شرطي در اينجا با @if شروع مي‌شود و سپس نكاتي كه در «نحوه معرفي يك قطعه كد» بيان شد، در مورد عبارات داخل {} صادق خواهد بود. يعني در اينجا نيز مي‌توان عبارات سي شارپ مخلوط با تگ‌هاي html را نوشت.
يك نكته: عبارت شرطي زير نادرست است. حتما بايد سطرهاي كدهاي سي شارپ بين {} محصور شوند؛‌ حتي اگر يك سطر باشند:
@if( i < 1 ) int myVar=0;


4) نحوه استفاده از حلقه foreach
@foreach (var item in Model)
{
    <li>@item.Name, $@item.Price </li>
}

حلقه foreach نيز مانند عبارات شرطي با يك @ شروع شده و داخل {} بدنه آن نكات «نحوه معرفي يك قطعه كد» برقرار هستند (امكان تلفيق code و markup با هم).
كساني كه پيشتر با web forms كار كرده باشند، احتمالا الان خواهند گفت كه اين يك پس رفت است و بازگشت به دوران ASP كلاسيك دهه نود! ما به ندرت داخل صفحات aspx وب فرم‌ها كد مي‌نوشتيم. مثلا پيشتر يك GridView وجود داشت و يك ديتاسورس كه به آن متصل مي‌شد؛ مابقي خودكار بود و ما هيچ وقت حلقه‌اي ننوشتيم. در اينجا هم اين مساله با نوشتن براي مثال «html helpers» قابل كنترل است كه در قسمت‌هاي بعدي به آن‌ پرداخته خواهد شد. به عبارتي قرار نيست به اين نحو با Viewهاي Razor رفتار كنيم. اين قسمت فقط يك آشنايي كلي با Syntax است.


5) امكان تعريف فضاي نام در ابتداي View
@using namespace;

6) نحوه نوشتن توضيحات سمت سرور:
@*
    A Razor Comment / Server side Comment
*@

7) نحوه معرفي عبارات چند جزئي:
@("First product: " + Model.First().Name)

همانطور كه ملاحظه مي‌كنيد، ذكر يك پرانتز براي معرفي عبارات چندجزئي كفايت مي‌كند.


استفاده از موتور Razor خارج از ASP.NET MVC

پيشتر مطلبي را در مورد «تهيه قالب براي ايميل‌هاي ارسالي يك برنامه ASP.Net» در اين سايت مطالعه كرده‌ايد. اولين سؤالي هم كه در ذيل آن مطلب مطرح شده اين است: «در برنامه‌هاي ويندوز چطور؟» پاسخ اين است كه كل آن مثال بر مبناي HttpContext.Current.Server.Execute كار مي‌كند. يعني بايد مراحل وهله سازي HttpContext و شيء Server توسط يك وب سرور و درخواست رسيده طي شود و ... شبيه سازي آن آنچنان مرسوم و كار ساده‌اي نيست.
اما اين مشكل با Razor وجود ندارد. به عبارتي در اينجا براي رندر كردن يك Razor View به html نهايي، نيازي به HttpContext نيست. بنابراين از اين امكانات مثلا در يك سرويس ويندوز ان تي يا يك برنامه كنسول، WinForms، WPF و غيره هم مي‌توان استفاده كرد.
براي اينكه بتوان از Razor خارج از ASP.NET MVC استفاده كرد، نياز به اندكي كدنويسي هست مثلا استفاده از كامپايلر سي شارپ يا وي بي و كامپايل پوياي كد و يك سري ست آپ ديگر. پروژه‌اي به نام RazorEngine اين‌ كپسوله سازي رو انجام داده و از اينجا http://razorengine.codeplex.com/ قابل دريافت است.


۱۳۹۱/۰۱/۱۱

ASP.NET MVC #6


آشنايي با انواع ActionResult

در قسمت چهارم، اولين متد يا اكشني كه به صورت خودكار توسط VS.NET به برنامه اضافه شد، اينچنين بود:

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/
        public ActionResult Index()
        {
            return View();
        }
    }
}

توضيحات تكميلي مرتبط با خروجي از نوع ActionResult ايي را كه مشاهده مي‌كنيد، در اين قسمت ارائه خواهد شد.
رفتار يك كنترلر توسط متدهايي كه در آن كلاس تعريف مي‌شوند، مشخص مي‌گردد. هر متد هم از طريق يك URL مجزا قابل دسترسي و فراخواني خواهد بود. اين متدها كه به آن‌ها اكشن نيز گفته مي‌شود بايد عمومي بوده، استاتيك يا متد الحاقي (extension method) نباشند و همچنين داراي پارامترهايي از نوع ref و out نيز نباشند.
هر درخواست رسيده، به يك كنترلر و متدي عمومي در آن توسط سيستم مسيريابي، نگاشت خواهد شد. اگر علاقمند باشيد كه در يك كلاس كنترلر، متدي عمومي را از اين سيستم خارج كنيد، تنها كافي است آن‌را با ويژگي (attribute) به نام NonAction مزين كنيد:

using System.Web.Mvc;

namespace MvcApplication2.Controllers
{
    public class HomeController : Controller
    {
        [NonAction]
        public string ShowData()
        {
            return "Text";
        }

        public ActionResult Index()
        {
            ViewBag.Message = string.Format("{0}/{1}/{2}",
                                              RouteData.Values["controller"],
                                              RouteData.Values["action"],
                                              RouteData.Values["id"]);
            return View();
        }

        public ActionResult Search(string data = "*")
        {
            // do something ...
            return View();
        }
    }
}

چند نكته در اين مثال قابل ذكر است:
الف) در اينجا اگر شخصي آدرس http://localhost/home/showdata را درخواست نمايد، با توجه به استفاده از ويژگي NonAction، با پيغام يافت نشد يا 404 مواجه مي‌گردد.
ب) صرفنظر از پارامترهاي يك متد و ساختار كلاس جاري، اطلاعات مسيريابي از طريق شيء RouteData.Values نيز در دسترس هستند كه نمونه‌اي از آن‌را در اينجا بر اساس مقادير پيش فرض تعاريف مسيريابي يك پروژه ASP.NET MVC ملاحظه مي‌نمائيد.
ج) در متد Search، از قابليت امكان تعريف مقداري پيش فرض جهت آرگومان‌ها در سي شارپ 4 استفاده شده است. به اين ترتيب اگر شخصي آدرس http://localhost/home/search را وارد كند، چون پارامتري را ذكر نكرده است، به صورت خودكار از مقدار پيش فرض آرگومان data استفاده مي‌گردد.


انواع Action Results در ASP.NET MVC

در ASP.NET MVC بجاي استفاده مستقيم از شيء Response، از شيء ActionResult جهت ارائه خروجي يك متد استفاده مي‌شود و مهم‌ترين دليل آن هم مشكل بودن نوشتن آزمون‌هاي واحد براي شيء Response است كه وهله سازي آن مساوي است با به كار اندازي موتور ASP.NET و Http Runtime آن توسط يك وب سرور (بنابراين در ASP.NET MVC سعي كنيد شيء Response را فراموش كنيد).
سلسله مراتب ActionResult‌هاي قابل استفاده در ASP.NET در تصوير زير مشخص شده‌اند:


و در مثال زير تقريبا انواع و اقسام ActionResult‌هاي مهم و كاربردي ASP.NET MVC را مي‌توانيد مشاهده كنيد:

using System.Web.Mvc;

namespace MvcApplication2.Controllers
{
    public class ActionResultsController : Controller
    {
        //http://localhost/actionresults/welcome
        public string Welcome()
        {
            return "Hello, World";
        }

        //http://localhost/actionresults/index
        public ActionResult Index() // or ContentResult
        {
            return Content("Hello, World");
        }

        //http://localhost/actionresults/SendMail
        public void SendMail()
        {
        }

        public ActionResult SendMailCompleted() // or EmptyResult
        {
            // do whatever
            return new EmptyResult();
        }

        public ActionResult GetFile() // or FilePathResult
        {
            return File(Server.MapPath("~/content/site.css"), "text/css", "mySite.css");
        }

        public ActionResult UnauthorizedStatus() // or HttpStatusCodeResult/HttpUnauthorizedResult
        {
            return new HttpUnauthorizedResult("You need to login first.");
        }

        public ActionResult Status() // or HttpStatusCodeResult
        {
            return new HttpStatusCodeResult(501, "Server Error");
        }

        public ActionResult GetJavaScript() // or JavaScriptResult
        {
            return JavaScript("...JavaScript...");
        }

        public ActionResult GetJson() // or JsonResult
        {
            var obj = new { prop1 = 1, prop2 = "data" };
            return Json(obj, JsonRequestBehavior.AllowGet);
        }

        public ActionResult RedirectTo() // or RedirectResult
        {
            return RedirectPermanent("http://www.site.com");
            //return RedirectToAction("Home", "Index");
        }

        public ActionResult ShowView() // or ViewResult
        {
            return View();
        }
    }
}

چند نكته در اين مثال وجود دارد:
1) مثلا متد GetJavaScript را درنظر بگيريد. در اين متد خاص، چه بنويسيد public ActionResult GetJavaScript يا بنويسيد public JavaScriptResult GetJavaScript تفاوتي نمي‌كند. در ساير موارد هم به همين ترتيب است. علت را در تصوير سلسله مراتبي ActionResult‌ها مي‌توان جستجو كرد. تمام اين كلاس‌ها نوعي ActionResult هستند و از يك كلاس پايه به ارث رسيده‌اند.
2) مثلا ContentResult شبيه به همان Response.Write سابق ASP.NET عمل مي‌كند. علت وجودي آن هم عدم وابستگي مستقيم به شيء Response و ساده‌تر سازي نوشتن آزمون‌هاي واحد براي اين نوع اكشن متدها است.
3) منهاي متد آخري كه نمايش داده شده (ShowView)، هيچكدام از متدهاي ديگر نيازي به View متناظر ندارند. يعني نيازي نيست تا روي متد كليك راست كرده و Add view را انتخاب كنيم. چون در همين متد كنترلر، كار Response به پايان مي‌رسد و مرحله بعدي ندارد. مثلا در حالت return File، يك فايل به درون مرورگر كاربر Flush خواهد شد و تمام.
4) متد Welcome و متد Index در اينجا به يك صورت تفسير مي‌شوند. به اين معنا كه اگر خروجي متد تعريف شده در يك كنترلر از نوع ActionResult نباشد، به صورت پيش فرض درون يك ContentResult محصور خواهد شد.
5) اگر خروجي متدي در اينجا از نوع void باشد، با ActionResult ايي به نام EmptyResult يكسان خواهد بود. بنابراين با متدهاي SendMail و SendMailCompleted به يك نحو رفتار مي‌گردد.
6) return Json ياد شده كه خروجي‌اش از نوع JsonResultاست در پياده سازي‌هاي Ajax ايي كاربرد دارد.
7) جهت بازگرداندن حالت وضعيت 403 يا غيرمجاز مي‌توان از return new HttpUnauthorizedResult استفاده كرد.
8) يا جهت اعلام مشكلي در سمت سرور به كمك return new HttpStatusCodeResultكد ويژه‌اي را مي‌توان به كاربر نمايش داد.
9) به كمك return RedirectToAction مي‌توان به يك كنترلر و متدي خاص در آن، كاربر را هدايت كرد.

و خلاصه اينكه تمام كارهايي را كه پيشتر در ASP.NET Web forms ، مستقيما به كمك شيء Response انجام مي‌داديد (Response.Write، Response.End، Response.Redirect و غيره)، اينبار به كمك يكي از ActionResult‌هاي ياد شده انجام دهيد تا بتوان بدون نياز به راه اندازي يك وب سرور، براي متدهاي كنترلرها آزمون واحد نوشت. براي مثال:

[TestMethod]
public void TestMethod1()
{
    // Arrange
    var controller = new ActionResultsController();

    // Act
    var result = controller.Index() as ContentResult;

    // Assert
    Assert.NotNull(result);
    Assert.AreEqual( "Hello, World", result.Content);
}



۱۳۹۱/۰۱/۱۰

رفع مشكل نصب به روز رساني ASP.NET MVC 3


در مورد نحوه نصب پيشنيازهاي ASP.NET MVC 3 پيشتر توضيح داده شد. يك روش ديگر هم براي اينكار مهيا است؛ اگر به مشكل برخورديد حين نصب.
  • دريافت ASP.NET MVC 3 RTM (نگارش RTM يعني نگارش نهايي ارائه شده به صنعت)
  • دريافت ASP.NET MVC 3 Tools Update (اين هم شامل يك سري به روز رساني است؛ مثلا قالب اينترانت به آن اضافه شده و غيره)

اگر حين نصب ASP.NET MVC 3 Tools Update، كارها خوب پيش نرفت و دست آخر زمانيكه فايل log خطا را باز كرديد در پايان آن ذكر شده بود « Installation failed with error code: 0x80070643» بايد اين مراحل را طي كنيد:
الف) فايل نصاب را با استفاده از برنامه 7-zip آنپك كنيد (فايل‌هاي داخلي آن‌را استخراج كنيد).
ب) فايل VS10-KB*.msp يا vs10-kb*.msi را يافته و بر روي آن كليك راست كنيد. گزينه install را از منوي باز شده انتخاب نمائيد تا كار نصب شروع شود.
ج) در حين نصب اين پيغام را دريافت خواهيد كرد: «can't find vs_setup.msi». در اين حالت بر روي دكمه browse كليك كرده و اين فايل vs_setup.msi را كه در DVD نصب خود VS 2010 اصلي وجود دارد به آن معرفي كنيد. علت هم به اين بر مي‌گردد كه پروسه نصب VS 2010 شما از ابتدا ناقص بوده است و نياز است يك سري فايل ديگر در ابتدا بر روي سيستم نصب گردند.
اكنون كار نصب بدون مشكل پيش خواهد رفت. لازم به ذكر است كه اين پيغام خطا را حين عمليات نصب معمولي MVC3 «دريافت نخواهيد كرد».
د) سپس يكبار ديگر هم فايل setup.exe اصلي را بدون مراحل فوق اجرا كنيد تا خيالتان از اين بابت راحت شود و تمام موارد نصب نشده نيز نصب گردند (مهم!).

اين مراحل بايد مشكل را حل كنند، در غيراينصورت يك سري راه حل ديگر هم در اينجا ذكر شده است: http://support.microsoft.com/kb/2531566
و خلاصه آن اين است كه فايل C:\Windows\Microsoft.NET\Framework\v4.0.30319\web.config را پيش از نصب rename كنيد و اجازه دهيد تا نصاب يك نمونه جديد را ايجاد كند.

۱۳۹۱/۰۱/۰۹

ASP.NET MVC #5


بررسي نحوه انتقال اطلاعات از يك كنترلر به View‌هاي مرتبط با آن

در ASP.NET Web forms در فايل code behind يك فرم مثلا مي‌توان نوشت Label1.Text و سپس مقداري را به آن انتساب داد. اما اينجا به چه ترتيبي مي‌توان شبيه به اين نوع عمليات را انجام داد؟ با توجه به اينكه در كنترلر‌ها هيچ نوع ارجاع مستقيمي به اشياء رابط كاربري وجود ندارد و اين دو از هم مجزا شده‌اند.
در پاسخ به اين سؤال، همان مثال ساده قسمت قبل را ادامه مي‌دهيم. يك پروژه جديد خالي ايجاد شده است به همراه HomeController ايي كه به آن اضافه كرده‌ايم. همچنين مطابق روشي كه ذكر شد، View ايي به نام Index را نيز به آن اضافه كرده‌ايم. سپس براي ارسال اطلاعات از يك كنترلر به View از يكي از روش‌هاي زير مي‌توان استفاده كرد:

الف) استفاده از اشياء پويا

ViewBag يك شيء dynamic است كه در دات نت 4 امكان تعريف آن ميسر شده است. به اين معنا كه هر نوع خاصيت دلخواهي را مي‌توان به اين شيء انتساب داد و سپس اين اطلاعات در View نيز قابل دسترسي و استخراج خواهد بود. مثلا اگر در اينجا به شيء ViewBag، خاصيت دلخواه Country را اضافه كنيم و سپس مقداري را نيز به آن انتساب دهيم:

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Country = "Iran";
            return View();
        }
    }
}

اين اطلاعات در View مرتبط با اكشني به نام Index به نحو زير قابل بازيابي خواهد بود (نحوه اضافه كردن View متناظر با يك اكشن يا متد را هم در قسمت قبل با تصوير مرور كرديم):

@{
    ViewBag.Title = "Index";
}
<h2>
    Index</h2>
<p>
    Country : @ViewBag.Country
</p>

در اين مثال، @ در View engine جاري كه Razor نام دارد، به اين معنا مي‌باشد كه اين مقداري است كه مي‌خواهم دريافت كني (ViewBag.Country) و سپس آن‌را در حين پردازش صفحه نمايش دهي.


ب) انتقال اطلاعات يك شيء كامل و غير پويا به View

هر پروژه جديد MVC به همراه پوشه‌اي به نام Models است كه در آن مي‌توان تعاريف اشياء تجاري برنامه را قرار داد. در پروژه جاري، يك كلاس ساده را به نام Employee به اين پوشه اضافه مي‌كنيم:

namespace MvcApplication1.Models
{
    public class Employee
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }
}

اكنون براي نمونه يك وهله از اين شيء را در متد Index ايجاد كرده و سپس به view متناظر با آن ارسال مي‌كنيم (در قسمت return View كد زير مشخص است). بديهي است اين وهله سازي در عمل مي‌تواند از طريق دسترسي به يك بانك اطلاعاتي يا يك وب سرويس و غيره باشد.

using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Country = "Iran";

            var employee = new Employee
            {
                 Email = "name@site.com",
                 FirstName = "Vahid",
                 LastName = "N."
            };

            return View(employee);
        }
    }
}

امضاهاي متفاوت (overloads) متد كمكي View هم به شرح زير هستند:

ViewResult View(Object model)
ViewResult View(string viewName, Object model)
ViewResult View(string viewName, string masterName, Object model)


اكنون براي دسترسي به اطلاعات اين شيء employee در View متناظر با اين متد، چندين روش وجود دارد:

@{
    ViewBag.Title = "Index";
}
<h2>
    Index</h2>
<div>
    Country: @ViewBag.Country <‪br />
    FirstName: @Model.FirstName
</div>

مي‌توان از طريق شيء استاندارد ديگري به نام Model (كه اين هم يك شيء dynamic است مانند ViewBag قسمت قبل)، به خواص شيء يا مدل ارسالي به View جاري دسترسي پيدا كرد كه يك نمونه از آن‌را در اينجا ملاحظه مي‌كنيد.
روش دوم، بر اساس تعريف صريح نوع مدل است به نحو زير:

@model MvcApplication1.Models.Employee
@{
    ViewBag.Title = "Index";
}
<h2>
    Index</h2>
<div>
    Country: @ViewBag.Country
    <‪br />
    FirstName: @Model.FirstName
</div>

در اينجا در مقايسه با قبل، تنها يك سطر به اول فايل View اضافه شده است كه در آن نوع شيء Model تعيين مي‌گردد (كلمه model هم در اينجا با حروف كوچك شروع شده است). به اين ترتيب اينبار اگر سعي كنيم به خواص اين شيء دسترسي پيدا كنيم، Intellisense ويژوال استوديو ظاهر مي‌شود. به اين معنا كه شيء Model بكارگرفته شده اينبار ديگر dynamic نيست و دقيقا مي‌داند كه چه خواصي را بايد پيش از اجراي برنامه در اختيار استفاده كننده قرار دهد.
به اين روش، روش Strongly typed view هم گفته مي‌شود؛ چون View دقيقا مي‌داند كه چون نوعي را بايد انتظار داشته باشد؛ تحت نظر كامپايلر قرار گرفته و همچنين Intellisense نيز براي آن مهيا خواهد بود.
به همين جهت اين روش Strongly typed view، در بين تمام روش‌هاي مهيا، به عنوان روش توصيه شده و مرجح مطرح است.
به علاوه استفاده از Strongly typed views يك مزيت ديگر را هم به همراه دارد: فعال شدن يك code generator توكار در VS.NET به نام scaffolding. يك مثال ساده:
تا اينجا ما اطلاعات يك كارمند را نمايش داديم. اگر بخواهيم يك ليست از كارمندها را نمايش دهيم چه بايد كرد؟
روش كار با قبل تفاوتي نمي‌كند. اينبار در return View ما، يك شيء ليستي ارائه خواهد شد. در سمت View هم با يك حلقه foreach كار نمايش اين اطلاعات صورت خواهد گرفت. راه ساده‌تري هم هست. اجازه دهيم تا خود VS.NET، كدهاي مرتبط را براي ما توليد كند.
يك كلاس ديگر به پوشه مدل‌هاي برنامه اضافه كنيد به نام Employees با محتواي زير:

using System.Collections.Generic;

namespace MvcApplication1.Models
{
    public class Employees 
    {
        public IList<Employee> CreateEmployees()
        {
            return new[]
                {
                    new Employee { Email = "name1@site.com", FirstName = "name1", LastName = "LastName1" },
                    new Employee { Email = "name2@site.com", FirstName = "name2", LastName = "LastName2" },
                    new Employee { Email = "name3@site.com", FirstName = "name3", LastName = "LastName3" }
                };
        }
    }
}

سپس متد جديد زير را به كنترلر Home اضافه كنيد.

public ActionResult List()
{
    var employeesList = new Employees().CreateEmployees();
    return View(employeesList);
}

براي اضافه كردن View متناظر با آن، روي نام متد كليك راست كرده و گزينه Add view را انتخاب كنيد. در صفحه ظاهر شده:


تيك مربوط به Create a strongly typed view را قرار دهيد. سپس در قسمت Model class، كلاس Employee را انتخاب كنيد (نه Employees جديد را، چون از آن مي‌خواهيم به عنوان منبع داده ليست توليدي استفاده كنيم). اگر اين كلاس را مشاهده نمي‌كنيد، به اين معنا است كه هنوز برنامه را يكبار كامپايل نكرده‌ايد تا VS.NET بتواند با اعمال Reflection بر روي اسمبلي برنامه آن‌را پيدا كند. سپس در قسمت Scaffold template گزينه List را انتخاب كنيد تا Code generator توكار VS.NET فعال شود. اكنون بر روي دكمه Add كليك نمائيد تا View نهايي توليد شود. براي مشاهده نتيجه نهايي مسير http://localhost/Home/List بايد بررسي گردد.


ج) استفاده از ViewDataDictionary

ViewDataDictionary از نوع IDictionary با كليدي رشته‌اي و مقداري از نوع object است. توسط آن شيء‌ايي به نام ViewData در ASP.NET MVC به نحو زير تعريف شده است:

public ViewDataDictionary ViewData { get; set; }

اين روش در نگارش‌هاي اوليه ASP.NET MVC بيشتر مرسوم بود. براي مثال:

using System;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewData["DateTime"] = "<‪br/>" + DateTime.Now;
            return View();
        }
    }
}

و سپس جهت استفاده از اين ViewData تعريف شده با كليد دلخواهي به نام DateTime در View متناظر با اكشن Index خواهيم داشت:

@{
    ViewBag.Title = "Index";
}
<h2>
    Index</h2>
<div>
    DateTime: @ViewData["DateTime"]
</div>

يك نكته امنيتي:
اگر به مقدار انتساب داده شده به شيء ViewDataDictionary دقت كنيد، يك تگ br هم به آن اضافه شده است. برنامه را يكبار اجرا كنيد. مشاهده خواهيد كرد كه اين تگ به همين نحو نمايش داده مي‌شود و نه به صورت يك سطر جديد HTML . چرا؟ چون Razor به صورت پيش فرض اطلاعات را encode شده (فراخواني متد Html.Encode در پشت صحنه به صورت خودكار) در صفحه نمايش مي‌دهد و اين مساله از لحاظ امنيتي بسيار عالي است؛ زيرا جلوي بسياري از حملات cross site scripting يا XSS را خواهد گرفت.
احتمالا الان اين سؤال پيش خواهد آمد كه اگر «عالمانه» بخواهيم اين رفتار نيكوي پيش فرض را غيرفعال كنيم چه بايد كرد؟
براي اين منظور مي‌توان نوشت:
@Html.Raw(myString)

و يا:
<div>@MvcHtmlString.Create("<h1>HTML</h1>")</div>

به اين ترتيب خروجي Razor ديگر encode شده نخواهد بود.


د) استفاده از TempData

TempData نيز يك dictionary ديگر براي ذخيره سازي اطلاعات است و به نحو زير در فريم ورك تعريف شده است:

public TempDataDictionary TempData { get; set; }

TempData در پشت صحنه از سشن‌هاي ASP.NET جهت ذخيره سازي اطلاعات استفاده مي‌كند. بنابراين اطلاعات آن در ساير كنترلرها و View ها نيز در دسترس خواهد بود. البته TempData يك سري تفاوت هم با سشن معمولي ASP.NET دارد:
- بلافاصله پس از خوانده شدن، حذف خواهد شد.
- پس از پايان درخواست از بين خواهد رفت.
هر دو مورد هم به جهت بالابردن كارآيي برنامه‌هاي ASP.NET MVC و مصرف كمتر حافظه سرور درنظر گرفته‌ شده‌اند.
البته كساني كه براي بار اول هست با ASP.NET مواجه مي‌شوند، شايد سؤال بپرسند اين مسايل چه اهميتي دارد؟ پروتكل HTTP، ذاتا يك پروتكل «بدون حالت» است يا Stateless هم به آن گفته مي‌شود. به اين معنا كه پس از ارائه يك صفحه وب توسط سرور، تمام اشياء مرتبط با آن در سمت سرور تخريب خواهند شد. اين مورد متفاوت‌ است با برنامه‌هاي معمولي دسكتاپ كه طول عمر يك شيء معمولي تعريف شده در سطح فرم به صورت يك فيلد، تا زمان باز بودن آن فرم، تعيين مي‌گردد و به صورت خودكار از حافظه حذف نمي‌شود. اين مساله دقيقا مشكل تمام تازه واردها به دنياي وب است كه چرا اشياء ما نيست و نابود شدند. در اينجا وب سرور قرار است به هزاران درخواست رسيده پاسخ دهد. اگر قرار باشد تمام اين اشياء را در سمت سرور نگهداري كند، خيلي زود با اتمام منابع مواجه مي‌گردد. اما واقعيت اين است كه نياز است يك سري از اطلاعات را در حافظه نگه داشت. به همين منظور يكي از چندين روش مديريت حالت در ASP.NET استفاده از سشن‌ها است كه در اينجا به نحو بسيار مطلوبي، با سربار حداقل توسط TempData مديريت شده است.
يك مثال كاربردي در اين زمينه:
فرض كنيد در متد جاري كنترلر، ابتدا بررسي مي‌كنيم كه آيا ورودي دريافتي معتبر است يا خير. در غيراينصورت، كاربر را به يك View ديگر از طريق كنترلري ديگر جهت نمايش خطاها هدايت خواهيم كرد.
همين «هدايت مرورگر به يك View ديگر» يعني پاك شدن و تخريب اطلاعات كنترلر قبلي به صورت خودكار. بنابراين نياز است اين اطلاعات را در TempData قرار دهيم تا در كنترلري ديگر قابل استفاده باشد:

using System;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult InsertData(string name)
        {
            // Check for input errors.
            if (string.IsNullOrWhiteSpace(name))
            {
                TempData["error"] = "name is required.";
                return RedirectToAction("ShowError");
            }
            // No errors
            // ...
            return View();
        }

        public ActionResult ShowError()
        {
            var error = TempData["error"] as string;
            if (!string.IsNullOrWhiteSpace(error))
            {
                ViewBag.Error = error;
            }
            return View();
        }
    }
}

در همان HomeController دو متد جديد به نام‌هاي InsertData و ShowError اضافه شده‌اند. در متد InsertData ابتدا بررسي مي‌شود كه آيا نامي وارد شده است يا خير. اگر خير توسط متد RedirectToAction، كاربر به اكشن يا متد ShowError هدايت خواهد شد.
براي انتقال اطلاعات خطايي كه مي‌خواهيم در حين اين Redirect نمايش دهيم نيز از TempData استفاده شده است.
بديهي است براي اجرا اين مثال نياز است دو View جديد براي متدهاي InsertData و ShowError ايجاد شوند (كليك راست روي نام متد و انتخاب گزينه Add view براي اضافه كردن View مرتبط با آن اكشن).
محتواي View مرتبط با متد افزودن اطلاعات فعلا مهم نيست، ولي View نمايش خطاها در ساده‌ترين حالت مثلا مي‌تواند به صورت زير باشد:

@{
    ViewBag.Title = "ShowError";
}

<h2>Error</h2>

@ViewBag.Error

براي آزمايش برنامه هم مطابق مسيريابي پيش فرض و با توجه به قرار داشتن در كنترلري به نام Home، مسير http://localhost/Home/InsertData ابتدا بايد بررسي شود. چون آرگوماني وارد نشده، بلافاصله صفحه به آدرس http://localhost/Home/ShowError به صورت خودكار هدايت خواهد شد.


نكته‌اي تكميلي در مورد Strongly typed viewها:
عنوان شد كه Strongly typed view روش مرجح بوده و بهتر است از آن استفاده شود، زيرا اطلاعات اشياء و خواص تعريف شده در يك View تحت نظر كامپايلر قرار مي‌گيرند كه بسيار عالي است. يعني اگر در View بنويسم FirstName: @Model.FirstName1 چون FirstName1 وجود خارجي ندارد، برنامه نبايد كامپايل شود. يكبار اين را بررسي كنيد. برنامه بدون مشكل كامپايل مي‌شود! اما تنها در زمان اجرا است كه صفحه زرد رنگ معروف خطاهاي ASP.NET ظاهر مي‌شود كه چنين خاصيتي وجود ندارد (اين حالت پيش فرض است؛ يعني كامپايل يك View‌ در زمان اجرا). البته اين باز هم خيلي بهتر است از ViewBag، چون اگر مثلا ViewBag.Country1 را وارد كنيم، در زمان اجرا تنها چيزي نمايش داده نخواهد شد؛‌ اما با روش Strongly typed view، حتما خطاي Compilation Error به همراه نمايش محل مشكل نهايي، در صفحه ظاهر خواهد شد.
سؤال: آيا مي‌شود پيش از اجراي برنامه هم اين بررسي را انجام داد؟
پاسخ: بله. بايد فايل پروژه را اندكي ويرايش كرده و مقدار MvcBuildViews را كه به صورت پيش فرض false هست، true نمود. يا خارج از ويژوال استوديو با يك اديتور متني ساده مثلا فايل csproj را گشوده و اين تغيير را انجام دهيد. يا داخل ويژوال استوديو، بر روي نام پروژه كليك راست كرده و سپس گزينه Unload Project را انتخاب كنيد. مجددا بر روي اين پروژه Unload شده كليك راست نموده و گزينه edit را انتخاب نمائيد. در صفحه باز شده، MvcBuildViews را يافته و آن‌را true كنيد. سپس پروژه را Reload كنيد.
اكنون اگر پروژه را كامپايل كنيد، پيغام خطاي زير پيش از اجراي برنامه قابل مشاهده خواهد بود:

'MvcApplication1.Models.Employee' does not contain a definition for 'FirstName1' 
and no extension method 'FirstName1' accepting a first argument of type 'MvcApplication1.Models.Employee'
could be found (are you missing a using directive or an assembly reference?)
d:\Prog\MvcApplication1\MvcApplication1\Views\Home\Index.cshtml 10 MvcApplication1

البته بديهي است اين تغيير، زمان Build پروژه را مقداري افزايش خواهد داد؛ اما امن‌ترين حالت ممكن براي جلوگيري از اين نوع خطاهاي تايپي است.
يا حداقل بهتر است يكبار پيش از ارائه نهايي برنامه اين مورد فعال و بررسي شود.

و يك خبر خوب!
مجوز سورس كد ASP.NET MVC از MS-PL به Apache تغيير كرده و همچنين Razor و يك سري موارد ديگر هم سورس باز شده‌اند. اين تغييرات به اين معنا خواهند بود كه پروژه از حالت فقط خواندني MS-PL به حالت متداول يك پروژه سورس باز كه شامل دريافت تغييرات و وصله‌ها از جامعه برنامه نويس‌ها است، تغيير كرده است (^ و ^).

۱۳۹۱/۰۱/۰۸

ASP.NET MVC #4


بررسي نحوه ارتباطات بين اجزاي مختلف الگوي MVC در ASP.NET MVC

اينبار برخلاف قسمت قبل، قالب پروژه خالي ASP.NET MVC را در VS.NET انتخاب كرده (ASP.NET MVC 3 Web Application و بعد انتخاب قالب Empty نمايش داده شده) و سپس پروژه جديدي را شروع مي‌كنيم. View Engine را هم Razor در نظر خواهيم گرفت.
پس از ايجاد ساختار اوليه پروژه، بدون اعمال هيچ تغييري، برنامه را اجرا كنيد. بلافاصله با پيغام The resource cannot be found يا 404 يافت نشد، مواجه خواهيم شد.
همانطور كه در پايان قسمت دوم نيز ذكر شد، پردازش‌ها در ASP.NET MVC از كنترلرها شروع مي‌شوند و نه از صفحات وب. بنابراين براي رفع اين مشكل نياز است تا يك كلاس كنترلر جديد را اضافه كنيم. به همين جهت بر روي پوشه استاندارد Controllers كليك راست كرده، از منوي ظاهر شده قسمت Add، گزينه‌ي Controller را انتخاب كنيد:


در صفحه بعدي كه ظاهر مي‌شود، نام HomeController را وارد كنيد (با توجه به اينكه مطابق قراردادهاي ASP.NET MVC، نام كنترلر بايد به كلمه Controller ختم شود). البته لازم به ذكر است كه اين مراحل را به همان شكل متداول مراجعه به منوي Project و انتخاب Add Class و سپس ارث بري از كلاس Controller نيز مي‌توان انجام داد و طي اين مراحل الزامي نيست. كلاسي كه به صورت خودكار از طريق منوي Add Controller ياد شده ايجاد مي‌شود، به شكل زير است:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/
        public ActionResult Index()
        {
            return View();
        }
    }
}

سؤال:
در قسمت دوم عنوان شد كه كنترلر بايد كلاسي باشد كه اينترفيس IController را پياده سازي كرده است، اما در اينجا ارث بري از كلاس Controller را شاهد هستيم. جريان چيست؟!
سلسله مراتبي كه بكارگرفته شده به صورت زير است:
public abstract class ControllerBase : IController
public abstract class Controller : ControllerBase

ControllerBase، اينترفيس IController را پياده سازي كرده و سپس كلاس Controller از كلاس ControllerBase مشتق شده است. شايد بپرسيد كه اين همه پيچ و تاب براي چيست؟!
مشكلي كه با اينترفيس خالص وجود دارد، عدم نگارش پذيري آن است. به اين معنا كه اگر متدي يا خاصيتي در نگارش بعدي به اين اينترفيس اضافه شد، هيچكدام از پروژه‌هاي قديمي ديگر كامپايل نخواهند شد و بايد ابتدا اين متد يا خاصيت جديد را نيز لحاظ كنند. اينجا است كه كار كلاس‌هاي abstract شروع مي‌شود. در يك كلاس abstract مي‌توان پياده سازي پيش فرضي را نيز ارائه داد. به اين ترتيب مصرف كننده نهايي كلاس Controller متوجه اين تغييرات نخواهد شد.
اگر برنامه نويس «من» باشم، شما رو وادار خواهم كرد كه متدهاي جديد اينترفيس تعريفي‌ام را پياده سازي كنيد! همينه كه هست! اما اگر طراح مايكروسافت باشد، بلافاصله انبوهي از جماعت ايرادگير كه بالاي 100 تا از كنترلر‌هاي اون‌ها الان فقط در يك پروژه از كار افتاده،‌ ممكن است جلوي دفتر مايكروسافت دست به خود سوزي بزنند! اينجا است كه مايكروسافت مجبور است تا اين پيچ و تاب‌ها را اعمال كند كه اگر روزي متدي در اينترفيس IController بنابر نيازهاي جديد درنظر گرفته شد، بتوان سريع يك پياده سازي پيش فرض از آن‌را در كلاس‌هاي abstract ياد شده قرار داد (يكي از تفاوت‌هاي مهم كلاس‌هاي abstract با اينترفيس‌ها) تا جماعت ايرادگير و نق‌زن متوجه تغييري نشوند و باز هم پروژه‌هاي قديمي بدون مشكل كامپايل شوند. تابحال به فلسفه وجودي كلاس‌هاي abstract از اين ديدگاه فكر كرده بوديد؟!

بعد از اين توضيحات و كارها، اگر اينبار برنامه را اجرا كنيم، خطاي زير نمايش داده مي‌شود:

The view 'Index' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Home/Index.aspx
~/Views/Home/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
~/Views/Home/Index.cshtml
~/Views/Home/Index.vbhtml
~/Views/Shared/Index.cshtml
~/Views/Shared/Index.vbhtml

همانطور كه ملاحظه مي‌كنيد چون نام كنترلر ما Home است، فريم ورك در پوشه استاندارد Views در زير پوشه‌اي به همان نام Home، به دنبال يك سري فايل مي‌گردد. فايل‌هاي aspx مربوط به View Engine ايي به همين نام بوده و فايل‌هاي cshtml و vbhtml مربوط به View Engine ديگري به نام Razor هستند.
بنابراين نياز است تا يكي از اين فايل‌ها را در مكان‌هاي ياد شده ايجاد كنيم. براي اين منظور حداقل دو راه وجود دارد. يا دستي اينكار را انجام دهيم يا اينكه از ابزار توكار خود VS.NET براي ايجاد يك View جديد استفاده كنيم.
براي ايجاد View ايي مرتبط با متد Index (در ASP.NET MVC نام ديگر متدهاي قرار گرفته در يك كنترلر، Action نيز مي‌باشد)، روي خود متد كليك راست كرده و گزينه Add View را انتخاب كنيد:


در صفحه بعدي ظاهر شده، پيش فرض‌ها را پذيرفته و بر روي دكمه Add كليك نمائيد. اتفاقي كه رخ خواهد داد شامل ايجاد فايل Index.cshtml، با محتواي زير است (با توجه به اينكه زبان پروژه سي شارپ است و View Engine انتخابي Razor مي‌باشد، cshtml توليد گرديد و گرنه vbhtml ايجاد مي‌شد):

@{
    ViewBag.Title = "Index";
}
<h2>Index</h2>

مجددا برنامه را اجرا كنيد. اينبار بدون خطايي كلمه Index را در صفحه توليدي مي‌توان مشاهده كرد. نكته جالب اين فايل‌هاي View جديد، عدم مشاهده ويژگي‌هاي runat=server و ساير موارد مشابه است.

چند سؤال مهم:
در حين ايجاد اولين كنترلر جهت نمايش صفحه پيش فرض برنامه، نام HomeController انتخاب شد. چرا مثلا نام TestController وارد نشد؟ برنامه از كجا متوجه شد كه بايد حتما اين كنترلر را پردازش كند. نقش متد Index چيست؟ آيا حتما بايد Index باشد و در اينجا نام ديگري را نمي‌توان وارد كرد؟ «قرارداد» پردازشي اين‌ها كجا تنظيم مي‌شود؟ فريم ورك، اين سيم كشي‌ها را چگونه انجام مي‌دهد؟
پاسخ به تمام اين سؤال‌ها، در ويژگي «مسيريابي يا Routing» نهفته است. فايل Global.asax.cs برنامه را باز كنيد. تعاريف مرتبط با مسيريابي پيش فرض را در متد RegisterRoutes آن مي‌توان مشاهده كرد:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );
}



پروسه هدايت يك درخواست HTTP به يك كنترلر، در اينجا مسيريابي يا Routing ناميده مي‌شود. اين قابليت در فضاي نام System.Web.Routing تعريف شده است و بايد دقت داشت كه جزو ASP.NET MVC نيست. اين امكانات جزو ASP.NET Runtime است و به همراه دات نت 3.5 سرويس پك يك براي اولين بار ارائه شد. بنابراين جهت استفاده در ASP.NET Web forms نيز مهيا است. در ASP.NET MVC از اين امكانات براي ارسال درخواست‌ها به كنترلرها استفاده مي‌شود.
در متد routes.MapRoute فراخواني شده‌اي كه در كدهاي بالا ملاحظه مي‌كنيد، كار نگاشت يك URL به Actionهاي يك كنترلر صورت مي‌گيرد (يا همان متدهاي تعريف شده در كنترلرها). همچنين از اين URLها پارامترهاي اين متدها يا اكشن‌ها نيز قابل استخراج است.
در متد MapRoute، اولين پارامتر تعريف شده، يك نام پيش فرض است و در ادامه اگر آدرسي را به فرم «يك چيزي اسلش يك چيزي اسلش يك چيزي» يافت، اولين قسمت آن‌را به عنوان نام كنترلر تفسير خواهد كرد، دومين قسمت آن، نام متد عمومي موجود در كنترلر فرض شده و سومين قسمت به عنوان پارامتر ارسالي به اين متد پردازش مي‌شود.
براي مثال از آدرس زير اينطور مي‌توان دريافت كه:
http://hostname/home/about

Home نام كنترلي است كه فريم ورك به دنبال آن خواهد گشت تا اين درخواست رسيده را پردازش كند و about نام متدي عمومي در اين كلاس است كه به صورت خودكار فراخواني مي‌گردد. در اينجا پارامتر id ايي هم وجود ندارد. در يك برنامه امكان تعريف چندين و چند مسيريابي وجود دارد.
پارامتر سوم متد routes.MapRoute، يك سري پيش فرض را تعريف مي‌كند و اين مورد همانجايي است كه از اطلاعات آن جهت تعريف كنترلر پيش فرض استفاده كرديم. براي مثال به چهار آدرس زير دقت نمائيد:

http://localhost/
http://localhost/home
http://localhost/home/about
http://localhost/process/list

در حالت http://localhost/،‌ هر سه مقدار پيش فرض مورد استفاده قرار خواهند گرفت چون سه جزئي ({controller}/{action}/{id}) كه موتور مسيريابي به دنبال آن‌ها مي‌گردد، در اين آدرس وجود خارجي ندارد. بنابراين نام كنترلر پيش فرض در اين حالت همان Home مشخص شده در پارامتر سوم متد routes.MapRoute خواهد بود و متد پيش فرض نيز Index و پارامتري هم به آن ارسال نخواهد شد.
در حالت http://localhost/home نام كنترلر مشخص است اما دو جزء ديگر ذكر نشده‌اند، بنابراين مقادير آن‌ها از پيش فرض‌هاي صريح ذكر شده در متد routes.MapRoute گرفته مي‌شود. يعني نام متد اكشن مورد پردازش، Index خواهد بود به همراه هيچ آرگومان خاصي.
به علاوه بايد خاطرنشان كرد كه اين مقادير case sensitive يا حساس به بزرگي و كوچكي حروف نيستند. بنابراين مهم نيست كه http://localhost/HoMe باشد يا http://localhost/HOMe يا هر تركيب ديگري.
يا اگر آدرس رسيده http://localhost/process/list باشد، اين مسيريابي پيش فرض تعريف شده قادر به پردازش آن مي‌باشد. به اين معنا كه كنترلري به نام Process وهله سازي و سپس متدي به نام List در آن فراخواني خواهند شد.

يك نكته:
ترتيب routes.MapRouteهاي تعريف شده در اينجا مهم است و اگر اولين URL رسيده با الگوي تعريف شده مطابقت داشت، كار را تمام خواهد كرد و به ساير تعاريف نخواهد رسيد. مثلا اگر در اينجا يك مسيريابي ديگر را به نام Process تعريف كنيم:

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

    routes.MapRoute(
                "Process", // Route name
                "Process/{action}/{id}", // URL with parameters
                new { controller = "Process", action = "List", id = UrlParameter.Optional } // Parameter defaults
            );

    routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );
}

آدرسي به فرم http://localhost/Process، به صورت خودكار به كنترلر Process و متد عمومي List آن نگاشت خواهد شد و كار به مسيريابي بعدي نخواهد رسيد.


۱۳۹۱/۰۱/۰۶

ASP.NET MVC #3


تهيه پيش‌نيازهاي شروع به كار با ASP.NET MVC

در زمان نگارش اين مطلب، نگارش نهايي ASP.NET MVC 3 در دسترس است و همچنين نگارش بتاي 4 آن نيز قابل دريافت و نصب مي‌باشد. بنابراين فعلا اساس را بر مبناي نگارشي قرار خواهيم داد كه در محيط كاري قابل استفاده باشد.
ASP.NET MVC 3 پس از ارائه Visual Studio 2010، منتشر شد و VS.NET به صورت پيش فرض به همراه ASP.NET MVC 2 است. ساده‌ترين روش نصب ASP.NET MVC 3 بر روي VS 2010 استفاده از برنامه رايگاني است به نام Web Platform Installer. اين برنامه را از اين آدرس مي‌توان دريافت كرد: http://microsoft.com/web/downloads
پس از دريافت آن حداقل دو راه براي نصب ASP.NET MVC 3 وجود دارد. يا گزينه‌ي نصب ASP.NET MVC 3 Tools Update را انتخاب كنيد و يا سرويس پك يك VS 2010 را از طريق اين برنامه يا جداگانه (بسته كامل و مستقل) دريافت و نصب نمائيد. VS 2010 SP1 نيز به همراه ASP.NET MVC 3 است؛ همچنين IIS Express را كه نسخه ساده شده IIS 7.5 مخصوص توسعه دهنده‌ها است، مي‌توان با اين نگارش يكپارچه كرد.


بنابراين به صورت خلاصه بهترين كار اين است كه سرويس پك يك VS 2010 را يكبار نصب نمائيد. اگر اين نصب از طريق برنامه Web Platform Installer باشد، به صورت خودكار IIS Express را هم انتخاب و نصب خواهد كرد. اگر فقط SP1 را به صورت مستقل دريافت كرده‌ايد، حاوي IIS Express نيست و بايد جداگانه آن‌را دريافت و نصب نمائيد (^). البته نصب IIS Express در اينجا يك گزينه اختياري است و الزامي نيست.



مروري بر ساختار يك پروژه ASP.NET MVC

پس از نصب پيش نيازها، امكان انتخاب يك پروژه وب ASP.NET MVC 3 در VS 2010 ميسر خواهد شد:


در اينجا گزينه‌ي ASP.NET MVC 3 Web Application را انتخاب مي‌كنيم. در صفحه بعدي كه ظاهر مي‌شود:


حالت Internet Application به همراه يك سري مدل و كنترلر از پيش نوشته شده جهت مديريت ورود به سايت و ثبت نام در سايت است و حالت Empty تنها به همراه ساختار پيش فرض پوشه‌هاي يك پروژه ASP.NET MVC است.
فعلا جهت توضيحات اوليه بيشتر، گزينه‌ي Internet Application و نوع View Engine را هم ASPX انتخاب مي‌كنيم. كار View Engine، رندر يك View به شكل HTML و ارائه نهايي اطلاعات آن به كاربر است. اين نوع‌هاي متفاوت هم فقط در Syntax تفاوت دارند (به آن templating language هم گفته مي‌شود). نوع ASPX همان Syntax متداول قديمي ASP.NET را تداعي مي‌كند و نوع Razor به صورت اختصاصي براي ASP.NET MVC تهيه شده است.
بايد در نظر داشت كه گزينه مرجح از نگارش 3 به بعد، Razor است (البته اين هم سليقه‌اي است. اگر هيچكدام از اين دو را هم نخواهيد استفاده كنيد مشكلي نيست! مي‌شود كلا آن را عوض كرد). هدفم هم از انتخاب ASPX نمايش يك سري ريزه كاري است كه شايد براي برنامه نويس‌هاي ASP.NET Web forms جالب باشد. اين موارد را در حالت انتخاب Razor به اين وضوح مشاهده نخواهيد كرد و محيط خيلي ساده شده است.


همانطور كه ملاحظه مي‌كنيد اين فريم ورك يك سري پوشه پيش فرض را توصيه مي‌كند. بديهي است كه ضرورتي ندارد تا پوشه Models يا پوشه Controllers حتما در همين پروژه قرار داشته باشند؛ چون زمانيكه پروژه كامپايل شد، محل اين پوشه بندي‌ها آنچنان اهميتي ندارد.
نكته جالب در اين تصوير، فايل Site.Master است. بله، اين فايل شبيه به همان فايل master page موجود در ASP.NET Web form است كه قالب كلي سايت را به همراه داشته و ساير صفحات، قالب خود را از آن به ارث مي‌برند. حتي ويژگي runat=server هم به وضوح در اين فايل، در چندين جاي آن قابل مشاهده است. تنها تفاوت آن نداشتن فايل code behind است. asp:ContentPlaceHolder نيز در آن تعريف شده است. خلاصه اين محيط جديد به معناي دور ريختن تمام آنچيزي كه در Web forms وجود دارد نيست. براي نمونه اگر فايل ChangePassword.aspx موجود در پوشه Account را باز كنيد، باز هم همان asp:Content معروف به همراه ويژگي runat=server قابل مشاهده است. براي مثال اين محتواي صفحه Error.aspx پيش فرض آن است:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
 Inherits="System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>" %>

<asp:Content ID="errorTitle" ContentPlaceHolderID="TitleContent" runat="server">
    Error
</asp:Content>

<asp:Content ID="errorContent" ContentPlaceHolderID="MainContent" runat="server">
    <h2>
        Sorry, an error occurred while processing your request.
    </h2>
</asp:Content>

اگر از قسمت Inherits آن صرفنظر كنيم، «هيچ» تفاوتي با ASP.NET Web forms ندارد؛ علت هم به اين بر مي‌گردد كه موتوري كه Web forms و MVC از آن استفاده مي‌كنند، يكي است. هر دو بر فراز موتور ASP.NET معنا پيدا خواهند كرد.


قرار دادهاي پوشه‌هاي پيش فرض يك پروژه ASP.NET MVC

  • پوشه Controllers حاوي كلاس‌هاي كنترلري است كه درخواست‌هاي رسيده را مديريت مي‌كنند.
  • پوشه Models حاوي كلاس‌هايي است كه اشياء تجاري و همچنين كار با اطلاعات را تعريف و مديريت مي‌كنند.
  • در پوشه Views، فايل‌هاي قالب‌هاي رابط كاربري كه مسئول ارائه خروجي به كاربر هستند قرار مي‌گيرند. همچنين مطابق قرارداد ديگري، اگر نام كنترلر ما مثلا ProductController باشد (با توجه به اينكه نام كلاس آن هم مطابق قرارداد، مختوم به كلمه Controller است)، فايل‌هاي Viewهاي مرتبط با آن در پوشه Views/Product قرار خواهند گرفت.
  • در پوشه Scripts،‌ فايل‌هاي جاوا اسكريپت مورد استفاده در سايت قرار خواهند گرفت.
  • پوشه Content محل قرارگيري فايل‌هاي CSS و تصاوير است.
  • پوشه App_Data جايي است كه فايل‌هايي با قابليت read/write در آن قرار مي‌گيرند (و بايد دقت داشت كه فقط همينجا هم بايد قرار گيرند و گرنه اين نوشتن‌ها در مكان‌هاي متفرقه، ممكن است سبب ري استارت شدن برنامه شوند:(^)).

۱۳۹۱/۰۱/۰۴

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 مديريت خواهد شد.

۱۳۹۱/۰۱/۰۳

ASP.NET MVC #1


چرا ASP.NET MVC ؟

با وجود فريم ورك پخته‌اي به نام ASP.NET web forms، اولين سؤالي كه حين سوئيچ به ASP.NET MVC مطرح مي‌شود اين است: «براي چي؟». بنابراين تا به اين سؤال پاسخ داده نشود، هر نوع بحث فني در اين مورد بي فايده است.

مزاياي ASP.NET MVC نسبت به ASP.NET web forms

1) سادگي نوشتن آزمون‌هاي واحد
مهم‌ترين دليل استفاده از ASP.NET MVC صرفنظر از تمام دلايل ديگر، بحث طراحي ويژه آن جهت ساده سازي تهيه آزمون‌هاي واحد است. مشكل اصلي نوشتن آزمون‌هاي واحد براي برنامه‌هاي ASP.NET web forms، درگير شدن مستقيم با تمام جزئيات طول عمر يك صفحه است. به علاوه فايل‌هاي code behind هر چند به ظاهر كدهاي منطق يك صفحه را از كدهاي HTML مانند آن جدا مي‌كنند اما در عمل حاوي ارجاعات مستقيمي به تك تك عناصر بصري موجود در صفحه هستند (حس غلط جدا سازي كدها از اجزاي يك فرم). اگر قرار باشد براي اين وب فرم‌ها و صفحات، آزمون واحد بنويسيم بايد علاوه بر شبيه سازي چرخه طول عمر صفحه و همچنين رخدادهاي رسيده، كار وهله سازي تك تك عناصر بصري را نيز عهده دار شويم. اينجا است كه ASP.NET web forms گزينه‌ي مطلوبي براي اين منظور نخواهد بود و اگر نوشتن آزمون واحد براي آن غيرممكن نباشد، به همين دلايل آنچنان مرسوم هم نيست.
البته شايد بپرسيد كه اين مساله چه اهميتي دارد؟ امكان نوشتن ساده‌تر آزمون‌هاي واحد مساوي است با امكان ساده‌تر اعمال تغييرات به يك پروژه بزرگ. تغييرات در پروژه‌هاي بزرگي كه آزمون واحد ندارند واقعا مشكل است. يك قسمت را تغيير مي‌دهيد، 10 قسمت ديگر به هم مي‌ريزند. اينجا است كه مدام بايد به كارفرما گفت: «نه!»، «نميشه!» يا به عبارتي «نمي‌تونم پروژه رو جمع كنم!» چون نمي‌تونم سريع برآورد كنم كه اين تغييرات كدام قسمت‌ها را تحت تاثير قرار مي‌دهند، كجا به هم ريخت. من بايد خودم سريع بتونم مشخص كنم با اين تغيير جديد چه قسمت‌هايي به هم ريخته تا اينكه دو روز بعد زنگ بزنند: «باز جايي رو تغيير دادي، يكجاي ديگر كار نمي‌كنه!»

2) دستيابي به كنترل بيشتر بر روي اجزاي فريم ورك
در طراحي ASP.NET MVC همه‌جا interface ها قابل مشاهد هستند. همين مساله به معناي افزونه پذيري اكثر قطعات تشكيل دهنده ASP.NET MVC است؛ برخلاف ASP.NET web forms. براي مثال تابحال چندين view engine، routing engine و غيره توسط برنامه نويس‌هاي مستقل براي ASP.NET MVC طراحي شده‌اند كه هيچكدام با ASP.NET web forms ميسر نيست. براي مثال از view engine پيش فرض آن خوشتان نمي‌آيد؟ عوضش كنيد! سيستم اعتبار سنجي توكار آن‌را دوست نداريد؟ آن‌را با يك نمونه بهتر تعويض كنيد و الي آخر ...
به علاوه طراحي بر اساس interface ها يك مزيت ديگر را هم به همراه دارد و آن هم ساده سازي mocking (تقليد) آن‌ها است جهت ساده سازي نوشتن آزمون‌هاي واحد.

3) سرعت بيشتر اجرا
ASP.NET MVC يك سري از قابليت‌هاي ذاتي ASP.NET web forms را مانند ViewState حذف كرده است. اگر وب را جستجو كنيد، برنامه نويس‌هاي ASP.NET web forms مدام از اين مساله شكايت دارند و راه‌ حل‌هاي مختلفي را جهت حذف يا فشرده سازي آن ارائه مي‌دهند. ViewState در ابتداي امر جهت شبيه سازي محيط دسكتاپ در وب درنظر گرفته شده بود و مهاجرت ساده‌تر برنامه نويس‌هاي VB6 به وب، اما واقعيت اين است كه اگر يك برنامه نويس ASP.NET web forms به اندازه آن توجهي نداشته باشد، ممكن است حجم آن در يك صفحه پيچيده تا 500 كيلوبايت يا بيشتر هم برسد. همين مساله بر روي سرعت دريافت و اجرا تاثير گذار خواهد بود.

4) كنترل‌هاي ASP.NET web forms آنچنان آش دهن‌سوزي هم نيستند!
خوب، ViewState حذف شده، بنابراين اكثر كنترل‌هاي ASP.NET web forms هم كاربرد آنچناني در ASP.NET MVC نخواهند داشت؛ اما واقعيت اين است كه اكثر اوقات اگر شروع به سفارشي سازي يك كنترل توكار ASP.NET web forms كنيد تا مطابق نيازهاي كاري شما رفتار كند، پس از مدتي به يك كنترل كاملا از نو بازنويسي شده خواهيد رسيد! بنابراين در ابتداي امر تا 80 درصد كار اينطور به نظر مي‌رسد كه به عجب سرعت بالايي در توسعه دست يافته‌ايم، اما هنگاميكه قرار است اين 20 درصد پاياني را پر كنيم، به اين نتيجه خواهيم رسيد كه اين كنترل‌ها با اين وضع ابتدايي كه دارند قابل استفاده نيستند و نياز به دستكاري قابل ملاحظه‌اي دارند تا نيازهاي واقعي كاري را برآورده كنند.

5) كنترل كامل بر روي HTML نهايي توليدي
اگر علاقمند به كار با jQuery باشيد، مدام نياز خواهيد تا با ID كنترل‌ها و عناصر صفحه كار كنيد. پيشتر ASP.NET web forms اين ID را يك طرفه و به صورت مقدار منحصربفردي توليد مي‌كرد كه جهت كار با فريم ورك‌هاي جاوا اسكريپتي عموما مشكل ساز بود. البته ASP.NET web forms در نگارش‌هاي جديد خود مشكل عدم امكان مقدار دهي ClientId سفارشي را براي كنترل‌هاي وب خود برطرف كرده است و اين مورد را مي‌توان دستي هم تنظيم كرد ولي در كل باز هم آنچنان كنترلي رو خروجي HTML نهايي كنترل‌هاي توليدي نيست مگر اينكه مانند مورد چهارم ياد شده يك كنترل را از صفر بازنويسي كنيد!
همچنين اگر باز هم بيشتر با jQuery و ASP.NET web forms كار كرده باشيد مي‌دانيد كه jQuery آنچنان سنخيتي با ViewState و Postback وب فرم‌ها ندارد و همين مساله عموما مشكل‌زا است. علاوه بر آن اخيرا مايكروسافت توسعه ASP.NET Ajax خود را تقريبا در حالت تعليق و واگذار شده به شركت‌هاي ثالث درآورده است و توصيه آن‌ها استفاده از jQuery Ajax است. اينجا است كه مدل ASP.NET MVC سازگاري كاملي را با jQuery Ajax دارد هم از لحاظ نبود ViewState و هم از جنبه‌ي كنترل كامل بر روي markup نهايي توليدي.
يا براي مثال خروجي پيش فرض يك GridView، جدول HTML ايي است كه اين روزها همه‌جا عليه آن صحبت مي‌شود. البته يك سري آداپتور CSS friendly براي اكثر اين كنترل‌ها موجود است و ... باز هم دستكاري بيش از حد كنترل‌هاي پيش فرض جهت رسيدن به خروجي دلخواه. تمام اين‌ها را در ASP.NET MVC مي‌شود با معادل‌هاي بسيار باكيفيت افزونه‌هاي jQuery جايگزين كرد و از همه مهم‌تر چون ViewState و مفاهيمي مانند PostBack حذف شده، استفاده از اين افزونه‌ها مشكل ساز نخواهد بود.

6) استفاده از امكانات جديد زبان‌هاي دات نتي
طراحي اصلي ASP.NET web forms مربوط است به دوران دات نت يك؛ زمانيكه نه Generics وجود داشت، نه LINQ و نه آنچنان مباحث TDD يا استفاده از ORMs متداول بود. براي مثال شايد ايجاد يك strongly typed web form الان كمي دور از ذهن به نظر برسد، زمانيكه اصل آن بر مبناي بكارگيري گسترده datatable و dataset بوده است (با توجه به امكانات زبان‌هاي دات نتي در آن دوران). بنابراين اگر علاقمند هستيد كه اين امكانات جديد را بكاربگيريد، ASP.NET MVC براي استفاده از آن‌ها طراحي شده است!

7) از ASP.NET web forms ساده‌تر است
طراحي ASP.NET MVC بر اساس ايده Convention over configuration است. به اين معنا كه اجزاي آن بر اساس يك سري قرار داد در كنار هم مشغول به كار هستند. مشخص است View بايد كجا باشد، نام كنترلرها چگونه بايد تعيين شوند و قرار داد مرتبط به آن چيست، مدل بايد كجا قرار گيرد، قرار داد پردازش آدرس‌هاي صفحات سايت به چه نحوي است و الي آخر. خلاصه در بدو امر با يك فريم ورك حساب شده كه شما را در مورد نحوه استفاده صحيح از آن راهنمايي مي‌كند، مواجه هستيد.
به همين ترتيب هر پروژه MVC ديگري را هم كه مشاهده كنيد، سريع مي‌توانيد تشخيص دهد قراردادهاي بكارگرفته شده در آن چيست. بنابراين اگر قرار است ASP.NET را امروز شروع كنيد و هيچ سابقه‌اي هم از وب فرم‌ها نداريد، يك راست با ASP.NET MVC شروع كنيد.

8) محدود به پياده سازي مايكروسافت نيست
پياده سازي‌هاي مستقلي هم از ASP.NET MVC توسط اشخاص و گروه‌هاي خارج از مايكروسافت وجود دارد: ^، ^، ^، ^ و ...


و در پايان يكي ديگر از دلايل سوئيچ به ASP.NET MVC ، «ياد گرفتن يك چيز جديد است» يا به عبارتي فرا گرفتن يك روش ديگر براي حل مسايل، هيچگاه ضرري را به همراه نخواهد داشت كه هيچ، بلكه باعث بازتر شدن ميدان ديد نيز خواهد گرديد.


يك ديدگاه ديگر
ASP.NET MVC براي شما مناسب نخواهد بود اگر ...
1) با پلي‌مرفيزم مشكل داريد.
ASP.NET MVC پر است از interfaces، abstract classes، virtual methods و امثال آن. بنابراين اگر تازه كار هستيد، ابتدا بايد مفاهيم شيءگرايي را تكميل كنيد.

2) اگر نمي‌توانيد فريم ورك خودتون رو بر پايه ASP.NET MVC بنا كنيد!
ASP.NET MVC برخلاف وب فرم‌ها به همراه آنچنان تعداد بالايي كنترل و افزونه از پيش مهيا شده نيست. در بدو امر شما فقط يك سري url helper، html helper و ajax helper ساده را خواهيد ديد؛ اين نقطه ضعف ASP.NET MVC نيست. عمدا به اين نحو طراحي شده است. همانطور كه عنوان شد اكثر اجزاي اين فريم ورك قابل تعويض است. بنابراين دست شما را باز گذاشته است تا با پياده سازي اين اينترفيس‌ها، امكانات جديدي را خلق كنيد. البته پس از اين چندين و چند سال كه از ارائه آن مي‌گذرد، به اندازه كافي افزونه براي ASP.NET MVC طراحي شده است كه به هيچ عنوان احساس كمبود نكنيد يا اينكه نيازي هم نداشته باشيد تا آنچنان فريم ورك خاصي را بر پايه ASP.NET MVC تهيه كنيد. براي مثال پروژه MvcContrib موجود است يا شركت telerik يك مجموعه سورس باز كامل مخصوص ASP.NET MVC را ارائه داده است و الي آخر.

3) اگر نمي‌توانيد از كتابخانه‌هاي سورس باز استفاده كنيد.
همانطور كه عنوان شد ASP.NET MVC به همراه كوهي از كنترل‌ها ارائه نشده است. اكثر افزونه‌هاي آن سورس باز هستند و كار با آن‌ها هم دنياي خاص خودش را دارد. چگونه بايد كتابخانه‌هاي مناسب را پيدا كرد، كجا سؤال پرسيد، كجا باگ گزارش داد، چگونه مشاركت كرد و غيره. خلاصه منتظر يك بسته شكيل حاضر و آماده نبايد بود. خود ASP.NET MVC هم تحت مجوز MSPL به صورت سورس باز در دسترس است.


و يك نكته تكميلي
مايكروسافت مدتي است شروع كرده به پرورش و زمزمه ايده «يك ASP.NET واحد». به عبارتي قصد دارند در يكي دو نگارش بعد، اين دو (وب فرم و MVC) را يكي كنند. هم اكنون اگر مطالب وبلاگ‌ها را مطالعه كنيد زيرساخت آن به نام ASP.NET Web API آماده شده است و در مرحله بتا است. نكته جالب اينجا است كه اين Web API امكان تعريف يكپارچه و مستقيم كنترلر‌هاي MVC را در وب فرم‌ها ميسر مي‌كند. ولي باز هم نام آن Controller است يعني جزئي از ASP.NET MVC و كسي مي‌تواند از آن استفاده كند كه با MVC‌ مشكلي نداشته باشد. بنابراين يادگيري MVC هيچ ضرري نخواهد داشت و جاي دوري نخواهد رفت!



۱۳۹۱/۰۱/۰۱

نوروز مبارك!


مي‌گن لحظه تحويل سال نو، مشغول به هر كاري كه باشي تا پايان سال سرت به همان گرم خواهد بود. به همين جهت سال نو رو با يك سري ويديوي رايگان ASP.NET MVC 3 كه به سفارش و هزينه مايكروسافت توسط Pluralsight تهيه شده، شروع مي‌كنيم. باشد تا پايان سال به همين منوال ادامه يابد!
سال نو مبارك!

۱۳۹۰/۱۲/۲۷

tesseract-ocr و پشتيباني از زبان عربي


tesseract-ocr، يك OCR سورس باز توسعه يافته توسط شركت HP در بين سال‌هاي 1985 تا 1995 است و اكنون شركت گوگل كار نگهداري و توسعه آن‌را به عهده دارد. كيفيت نويسه خواني انگليسي آن فوق‌العاده بالا است. در آخرين نگارش آن پشتيباني از زبان عربي هم را اضافه كرده است.
براي نصب آن ابتدا نگارش قابل حمل آن‌را دريافت و سپس فايل‌هاي مرتبط با زبان عربي را نيز بايد دريافت كنيد. پس از دريافت اين‌دو، فايل‌هاي زبان عربي را در پوشه tessdata كپي كنيد.

كار كردن با آن هم به سادگي اجراي فرمان زير است:

tesseract.exe image.tif file -l ara

پارامتر اول نام تصوير، پارامتر دوم نام فايل متني خروجي است (خودش يك txt را به صورت خودكار به فايل توليدي اضافه مي‌كند) و در آخر زبان عربي مشخص شده است.
براي نمونه تصوير زير را


به صورت متن زير نويسه خواني كرد:

«برا ي اي ذسث است»

فعلا ابزاري را براي ويرايش فايل‌هاي مرتبط با تشخيص زبان عربي ارائه نداده‌اند. بنابراين براي استفاده از آن جهت تشخيص متون فارسي مشكل وجود دارد چون «گچ پژ» را نمي‌تواند تشخيص دهد و به اينجا كه مي‌رسد كلا سيستمش به هم مي‌ريزد.
انجمن پرسش و پاسخ آن هم در اينجا قرار دارد.

فايل‌هاي اجرايي و زبان عربي اين برنامه را از آدرس‌هاي زير هم مي‌توان دريافت كرد:
Mirror: tesseract-ocr-3.01-win32-portable.zip & tesseract-ocr-3.01.ara.tar.gz

۱۳۹۰/۱۲/۲۵

استفاده از SQL-CE به كمك NHibernate


خلاصه‌اي را در مورد SQL Server CE قبلا در اين سايت مطالعه‌ كرده‌ايد. در ادامه خلاصه‌اي كاربردي را از تنظيمات و نكات مرتبط به كار با SQL-CE به كمك NHibernate ملاحظه خواهيد نمود:

1) دريافت SQL-CE 4.0


همين مقدار براي استفاده از SQL-CE 4.0 به كمك NHibernate كفايت مي‌كند و حتي نيازي به نصب سرويس پك يك VS 2010 هم نيست.

2) ابزار سازي جهت ايجاد يك بانك اطلاعاتي خالي SQL-CE

using System;
using System.IO;

namespace NHibernate.Helper.DbSpecific
{
    public class SqlCEDbHelper
    {
        const string engineTypeName = "System.Data.SqlServerCe.SqlCeEngine, System.Data.SqlServerCe";

        /// <summary>
        /// note: this method will delete existing db and then creates a new one.
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="password"></param>
        public static void CreateEmptyDatabaseFile(string filename, string password = "")
        {
            if (File.Exists(filename))
                File.Delete(filename);

            var type = System.Type.GetType(engineTypeName);
            var localConnectionString = type.GetProperty("LocalConnectionString");
            var createDatabase = type.GetMethod("CreateDatabase");

            var engine = Activator.CreateInstance(type);

            string connectionStr = string.Format("Data Source='{0}';Password={1};Encrypt Database=True", filename, password);
            if (string.IsNullOrWhiteSpace(password))
                connectionStr = string.Format("Data Source='{0}'", filename);

            localConnectionString.SetValue(
                obj: engine,
                value: connectionStr,
                index: null);
            createDatabase.Invoke(engine, new object[0]);
        }

        /// <summary>
        /// use this method to compact or encrypt existing db or decrypt it to a new db with all records
        /// </summary>
        /// <param name="sourceConnection"></param>
        /// <param name="destConnection"></param>
        public static void CompactDatabase(string sourceConnection, string destConnection)
        {
            var type = System.Type.GetType(engineTypeName);
            var engine = Activator.CreateInstance(type);

            var localConnectionString = type.GetProperty("LocalConnectionString");
            localConnectionString.SetValue(
                obj: engine,
                value: sourceConnection,
                index: null);

            var compactDatabase = type.GetMethod("Compact");
            compactDatabase.Invoke(engine, new object[] { destConnection });
        }
    }
}

كلاس فوق، يك كلاس عمومي است و مرتبط به NHibernate نيست و در همه جا قابل استفاده است.
متد CreateEmptyDatabaseFile يك فايل بانك اطلاعاتي خالي با فرمت مخصوص SQL-CE را براي شما توليد خواهد كرد. به اين ترتيب مي‌توان بدون نياز به ابزار خاصي، سريعا يك بانك خالي را توليد و شروع به كار كرد. در اين متد اگر كلمه عبوري را وارد نكنيد، بانك اطلاعاتي رمزنگاري شده نخواهد بود و اگر كلمه عبور را وارد كنيد، ديتابيس اوليه به همراه كليه اعمال انجام شده بر روي آن در طول زمان، با كمك الگوريتم AES به صورت خودكار رمزنگاري خواهند شد. كل كاري را هم كه بايد انجام دهيد ذكر اين كلمه عبور در كانكشن استرينگ است.
متد CompactDatabase، يك متد چند منظوره است. اگر بانك اطلاعاتي SQL-CE رمزنگاري نشده‌اي داريد و مي‌خواهيد كل آن‌را به همراه تمام اطلاعات درون آن رمزنگاري كنيد، مي‌توانيد جهت سهولت كار از اين متد استفاده نمائيد. آرگومان اول آن به كانكشن استرينگ بانكي موجود و آرگومان دوم به كانكشن استرينگ بانك جديدي كه توليد خواهد شد، اشاره مي‌كند.
همچنين اگر يك بانك اطلاعاتي SQL-CE رمزنگاري شده داريد و مي‌خواهيد آن‌را به صورت يك بانك اطلاعاتي جديد به همراه تمام ركوردهاي آن رمزگشايي كنيد، باز هم مي‌توان از اين متد استفاده كرد. البته بديهي است كه كلمه عبور را بايد داشته باشيد و اين كلمه عبور جايي درون فايل بانك اطلاعاتي ذخيره نمي‌شود. در اين حالت در كانكشن استرينگ اول بايد كلمه عبور ذكر شود و كانكشن استرينگ دوم نيازي به كلمه عبور نخواهد داشت.

فرمت كلي كانكشن استرينگ SQL-CE هم به شكل زير است:

Data Source=c:\path\db.sdf;Password=1234;Encrypt Database=True

البته اين براي حالتي است كه قصد داشته باشيد بانك اطلاعاتي مورد استفاده را رمزنگاري كنيد يا از يك بانك اطلاعاتي رمزنگاري شده استفاده نمائيد. اگر بانك اطلاعاتي شما كلمه عبوري ندارد، ذكر Data Source=c:\path\db.sdf كفايت مي‌كند.

اين كلاس هم از اين جهت مطرح شد كه NHibernate مي‌تواند ساختار بانك اطلاعاتي را بر اساس تعاريف نگاشت‌ها به صورت خودكار توليد و اعمال كند، «اما» بر روي يك بانك اطلاعاتي خالي SQL-CE از قبل تهيه شده (در غيراينصورت خطاي The database file cannot be found. Check the path to the database را دريافت خواهيد كرد).

نكته:
اگر دقت كرده باشيد در اين كلاس engineTypeName به صورت رشته ذكر شده است. چرا؟
علت اين است كه با ذكر engineTypeName به صورت رشته، مي‌توان از اين كلاس در يك كتابخانه عمومي هم استفاده كرد، بدون اينكه مصرف كننده نيازي داشته باشد تا ارجاع مستقيمي را به اسمبلي SQL-CE به برنامه خود اضافه كند. اگر اين ارجاع وجود داشت، متدهاي ياد شده كار مي‌كنند، در غيراينصورت در گوشه‌اي ساكت و بدون دردسر و بدون نياز به اسمبلي خاصي براي روز مبادا قرار خواهند گرفت.


3) ابزار مرور اطلاعات بانك اطلاعاتي SQL-CE

با استفاده از management studio خود SQL Server هم مي‌شود با بانك‌هاي اطلاعاتي SQL-CE كار كرد، اما ... اينبار برخلاف نگارش كامل اس كيوال سرور، با يك نسخه‌ي بسيار بدوي، كه حتي امكان rename فيلدها را هم ندارد مواجه خواهيد شد. به همين جهت به شخصه برنامه SqlCe40Toolbox را ترجيح مي‌دهم و اطمينان داشته باشيد كه امكانات آن براي كار با SQL-CE از امكانات ارائه شده توسط management studio مايكروسافت، بيشتر و پيشرفته‌تر است!



4) تنظيمات NHibernate جهت كار با SQL-CE

الف) پس از نصب SQL-CE ، فايل‌هاي آن‌را در مسير C:\Program Files\Microsoft SQL Server Compact Edition\v4.0 مي‌توان يافت. درايور ADO.NET آن هم در مسير C:\Program Files\Microsoft SQL Server Compact Edition\v4.0\Desktop قرار دارد. بنابراين در ابتدا نياز است تا ارجاعي را به اسمبلي System.Data.SqlServerCe.dll به برنامه خود اضافه كنيد (نام پوشه desktop آن هم غلط انداز است. از اين جهت كه نگارش 4 آن، به راحتي در برنامه‌هاي ذاتا چند ريسماني ASP.Net بدون مشكل قابل استفاده است).
نكته مهم: در اين حالت NHibernate قادر به يافتن فايل درايور ياد شده نخواهد بود و پيغام خطاي «Could not create the driver from NHibernate.Driver.SqlServerCeDriver» را دريافت خواهيد كرد. براي رفع آن، اسمبلي System.Data.SqlServerCe.dll را در ليست ارجاعات برنامه يافته و در برگه خواص آن، خاصيت «Copy Local» را true كنيد. به اين معنا كه NHibernate اين اسمبلي را در كنار فايل اجرايي برنامه شما جستجو خواهد كرد.

ب) مطلب بعد، تنظيمات ابتدايي NHibernate‌ است جهت شناساندن SQL-CE . مابقي مسايل (نكات mapping، كوئري‌ها و غيره) هيچ تفاوتي با ساير بانك‌هاي اطلاعاتي نخواهد داشت و يكي است. به اين معنا كه اگر برنامه شما از ويژگي‌هاي خاص بانك‌هاي اطلاعاتي استفاده نكند (مثلا اگر از رويه‌هاي ذخيره شده اس كيوال سرور استفاده نكرده باشد)، فقط با تغيير كانكشن استرينگ و معرفي dialect و driver جديد، به سادگي مي‌تواند به يك بانك اطلاعاتي ديگر سوئيچ كند؛ بدون اينكه حتي بخواهيد يك سطر از كدهاي اصلي برنامه خود را تغيير دهيد.



تنها نكته جديد آن اين متد است:

private Configuration getConfig()
{
            var configure = new Configuration();
            configure.SessionFactoryName("BuildIt");

            configure.DataBaseIntegration(db =>
            {
                db.ConnectionProvider<DriverConnectionProvider>();
                db.Dialect<MsSqlCe40Dialect>();
                db.Driver<SqlServerCeDriver>();
                db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
                db.IsolationLevel = IsolationLevel.ReadCommitted;
                db.ConnectionString = ConnectionString;
                db.Timeout = 10;

                //for testing ...
                db.LogFormattedSql = true;
                db.LogSqlInConsole = true;
            });

            return configure;
}

كه در آن نحوه تعريف MsSqlCe40Dialect و SqlServerCeDriver مشخص شده است.

نكته حاشيه‌اي!
در اين مثال primary key از نوع identity تعريف شده و بدون مشكل كار كرد. همين را اگر با EF تست كنيد، اين خطا را دريافت مي‌كنيد: «Server-generated keys and server-generated values are not supported by SQL Server Compact». بله، EF نمي‌تواند با primary key از نوع identity حين كار با SQL-CE كار كند. براي رفع آن توصيه شده است كه از Guid استفاده كنيد!

نكته تكميلي:
استفاده از Dialect سفارشي در NHibernate


نكته پاياني!
و در پايان بايد اشاره كرد كه SQL-CE يك بانك اطلاعاتي نوشته شده با دات نت نيست (با CPP نوشته شده است و نصب آن هم نياز به ران تايم به روز VC را دارد). به اين معنا كه جهت سيستم‌هاي 64 بيتي و 32 بيتي بايد نسخه مناسب آن‌را توزيع كنيد. يا اينكه Target platform پروژه جاري دات نت خود را بر روي X86 قرار دهيد (نه بر روي Any CPU پيش فرض) و در اين حالت تنها يك نسخه X86 بانك اطلاعاتي SQL-CE و همچنين برنامه خود را براي تمام سيستم‌ها توزيع كنيد.

۱۳۹۰/۱۲/۲۳

عبارات باقاعده و نياز به Timeout


يكبار سعي كنيد مثال ساده زير را اجرا كنيد:

using System;
using System.Text.RegularExpressions;

namespace RegexLoop
{
    class Program
    {
        static void Main(string[] args)
        {
            var emailAddressRegex = new Regex(@"^[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\@[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\.[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*$|^$");
            if (emailAddressRegex.IsMatch("an.infinite.loop.sample.just_for.test"))
            {
                Console.WriteLine("Matched!");
            }

            var input = "The quick brown fox jumps";
            var pattern = @"([a-z ]+)*!";
            if (Regex.IsMatch(input, pattern))
            {
                Console.WriteLine("Matched!");
            }
        }
    }
}


پس از اجرا، برنامه هنگ خواهد كرد يا به عبارتي برنامه در يك حلقه بي‌نهايت قرار مي‌گيرد (در هر دو مثال؛ اطلاعات بيشتر و آناليز كامل در اينجا). بنابراين نياز به مكانيزمي امنيتي جهت محافظت در برابر اين نوع ورودي‌ها وجود خواهد داشت؛ مثلا يك Timeout . اگر تا 2 ثانيه به جواب نرسيديم، اجراي Regex متوقف شود. تا دات نت 4، چنين timeout ايي پيش بيني نشده؛ اما در دات نت 4 و نيم آرگوماني جهت تعريف حداكثر مدت زمان قابل قبول اجراي يك عبارت باقاعده در نظر گرفته شده است (^) و اگر در طي مدت زمان مشخص شده، كار انجام محاسبات به پايان نرسد، استثناي RegexMatchTimeoutException صادر خواهد شد.
خيلي هم خوب. به اين ترتيب كسي نمي‌تونه با يك ورودي ويژه، CPU Usage سيستم رو تا مدت زمان نامحدودي به 100 درصد برساند و عملا استفاده از سيستم رو غيرممكن كنه.
اما تا قبل از دات نت 4 و نيم چكار بايد كرد؟ روش كلي حل اين مساله به اين ترتيب است كه بايد اجراي Regex را به يك ترد ديگر منتقل كرد؛ اگر مدت اجراي عمليات، از زمان تعيين شده بيشتر گرديد، آنگاه مي‌شود ترد را Abort كرد و به عمليات خاتمه داد. روش پياده سازي و نحوه استفاده از آن‌را در ادامه ملاحظه خواهيد نمود:

using System;
using System.Text.RegularExpressions;
using System.Threading;

namespace RegexLoop
{
    public static class TimedRunner
    {
        public static R RunWithTimeout<R>(Func<R> proc, TimeSpan duration)
        {
            using (var waitHandle = new AutoResetEvent(false))
            {
                var ret = default(R);
                var thread = new Thread(() =>
                {
                    ret = proc();
                    waitHandle.Set();
                }) { IsBackground = true };
                thread.Start();

                bool timedOut = !waitHandle.WaitOne(duration, false);
                waitHandle.Close();

                if (timedOut)
                {
                    try
                    {
                        thread.Abort();
                    }
                    catch { }
                    return default(R);
                }
                return ret;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var emailAddressRegex = new Regex(@"^[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\@[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\.[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*$|^$");
            if (TimedRunner.RunWithTimeout(
                 () => emailAddressRegex.IsMatch("an.infinite.loop.sample.just_for.test"),
                 TimeSpan.FromSeconds(2)))
            {
                Console.WriteLine("Matched!");
            }

            var input = "The quick brown fox jumps";
            var pattern = @"([a-z ]+)*!";
            if (TimedRunner.RunWithTimeout(() => Regex.IsMatch(input, pattern), TimeSpan.FromSeconds(2)))
            {
                Console.WriteLine("Matched!");
            }
        }
    }
}

اينبار به هر كدام از عبارات باقاعده 2 ثانيه زمان براي اتمام كار داده شده است. در غيراينصورت مقدار پيش فرض خروجي متد فراخواني شده، بازگشت داده مي‌شود كه در اينجا false است.

۱۳۹۰/۱۲/۲۱

نحوه تبديل نگارش SQL Server 2012 RTM مدت دار، به نگارش كامل


Microsoft® SQL Server® 2012 Evaluation از اين آدرس قابل دريافت است. همچنين اگر به سايت‌هاي وارز مراجعه كنيد، به ازاي هر نگارش SQL Server 2012، يك بسته دريافتي 4 گيگابايتي را به شما ارائه مي‌دهند. يعني اگر كسي بخواهد نسخه developer و نسخه enterprise را دريافت كند بيش از 8 گيگ را بايد دريافت نمايد!
اما واقعيت اين است كه نيازي به دريافت هيچكدام نيست. يك فايل ISO مربوط به SQL Server 2012 بيشتر وجود خارجي ندارد. تمام اين نگارش‌ها هم فقط براساس Product key است كه مشخص مي‌شوند. اگر سريال مرتبط با نگارش developer را وارد كنيد، اين نگارش نصب خواهد شد. اگر سريال نگارش enterprise را وارد كنيد، نگارش سازماني نصب خواهد شد؛ و تمام اين‌ها هم فقط با همان يك فايل ISO اصلي ارائه شده توسط مايكروسافت ميسر مي‌شوند.
اين فايل ISO اصلي را از اينجا مي‌توان دريافت كرد. بديهي است Product key توكار و پيش فرض آن كه در اختيار عموم است، مدت دار مي‌باشد. بنابراين حين نصب تنها نياز به سريال معتبر وجود دارد.

۱۳۹۰/۱۲/۱۹

Microsoft® SQL Server® 2012


نگارش نهايي Microsoft® SQL Server® 2012 چند روزي هست كه ارائه شده. فعلا نسخه آزمايشي RTM آن در اختيار عموم است.
در ادامه جمع آوري لينك‌هاي مرتبط به اين ارائه را مشاهده خواهيد نمود:


و يك جدول مقايسه‌اي بين امكانات نگارش‌هاي رايگان SQL Server 2012 در اينجا

۱۳۹۰/۱۲/۱۲

تعريف نوع جنريك به صورت متغير


در تهيه مثال Auto Mapping به كمك امكانات توكار NH 3.2 به اين مورد نياز پيدا كردم:
بتوان نوع متد جنريك را به صورت متغير تعريف كرد و اين نوع در زمان كامپايل برنامه مشخص نباشد. مثلا چيزي شبيه به اين مثال:

using System;

namespace GenericsSample
{
    class TestGenerics
    {
        public static void Print<T>(T data)
        {
            Console.WriteLine("Print<T>");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var type = typeof(Nullable<int>);
            TestGenerics.Print<type>(1);
        }
    }
}

اين نوع فراخواني متد Print در دات نت به صورت پيش فرض غيرمجاز است و نوع جنريك را نمي‌توان به صورت متغير معرفي كرد.
كه البته اين هم راه حل دارد و به كمك Reflection قابل حل است:

using System;

namespace GenericsSample
{
    class TestGenerics
    {
        public static void Print<T>(T data)
        {
            Console.WriteLine("Print<T>");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var nullableIntType = typeof(Nullable<>).MakeGenericType(typeof(int));
            var method = typeof(TestGenerics).GetMethod("Print");
            var genericMethod = method.MakeGenericMethod(new[] { nullableIntType });
            genericMethod.Invoke(null, new object[] { 1 });
        }
    }
}

دو متد MakeGenericType و MakeGenericMethod براي ساخت پوياي نوع‌هاي جنريك و همچنين ارسال آن‌ها به متدهاي جنريك در دات نت وجود دارند كه مثالي از نحوه استفاده از آن‌ها را در بالا ملاحظه مي‌كنيد.

مثال دوم:
اگر كلاس TestGenerics نسخه غيرجنريك متد Print را هم داشت، ‌چطور؟ مثلا:

class TestGenerics
{
    public static void Print<T>(T data)
    {
         Console.WriteLine("Print<T>");
    }

    public static void Print(object data)
    {
         Console.WriteLine("Print");
    }
}

اينبار اگر برنامه فوق را اجرا كنيم، پيغام Ambiguous match found را حين فراخواني GetMoethod دريافت خواهيم كرد؛ چون دو متد با يك نام در كلاس ياد شده وجود دارند. براي حل اين مشكل بايد به نحو زير عمل كرد:

using System;
using System.Linq;

namespace GenericsSample
{
    class TestGenerics
    {
        public static void Print<T>(T data)
        {
            Console.WriteLine("Print<T>");
        }

        public static void Print(object data)
        {
            Console.WriteLine("Print");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var nullableIntType = typeof(Nullable<>).MakeGenericType(typeof(int));
            var method = typeof(TestGenerics).GetMethods()
                         .First(x => x.Name == "Print" && (x.GetParameters()[0]).ParameterType.IsGenericParameter);
            var genericMethod = method.MakeGenericMethod(new[] { nullableIntType });
            genericMethod.Invoke(null, new object[] { 1 });
        }
    }
}

GetMethods تمام متدها را بازگشت داده و سپس بر اساس متاديتاي متدها، ‌مي‌توان تشخيص داد كه كدام يك جنريك است.

گروه‌هاي گوگل، اينترفيس جديد و زبان فارسي


گوگل اخيرا شروع كرده به اعمال قالب جديد مترو مانند خودش به گروه‌هاي قديمي موجود در آن. اين مساله چند مزيت رو براي فارسي زبان‌ها مي‌تونه به همراه داشته باشه:
پيشتر اين گروه‌ها براي فارسي زبان‌ها آنچنان/«اصلا» دلچسب نبود. چون نه از زبان فارسي پشتيباني مي‌كرد، نه از راست به چپ و نه از فونت‌هاي سفارشي مطلوب (قلم پيش فرض آن courier new بود). هرچند يك سري style توسط افزونه استايليش به آن قابل اعمال بود ولي خوب، به يك سري مرورگر خاص محدود مي‌شد. الان اين گروه‌هاي جديد (كه در آدرس آن‌ها بجاي groups از كلمه forum استفاده شده) هم از زبان فارسي پشتيباني مي‌كنند و هم اينكه با انتخاب اين زبان، كل مجموعه راست به چپ خواهد شد و براي تمام مرورگرها به شكل يكساني قابل استفاده خواهد بود. به علاوه اين قالب جديد گروه‌ها/انجمن‌هاي گوگل،‌ اديتور متني پيشرفته‌اي را هم به همراه دارد؛ به علاوه امكان الصاق فايل.





به اين ترتيب اين گروه‌ها براي كساني‌ كه مي‌خواهند يك انجمن فارسي رايگان هاست شده توسط گوگل، به همراه قابليت الصاق فايل و پشتيباني از زبان فارسي و راست به چپ را داشته باشند، بسيار مناسب شده است. همچنين سطح دسترسي اين گروه‌ها به عمومي، فقط خواندني و همچنين خصوصي (فقط اعضاي دعوت شده قابليت خواندن يا ارسال مطلب را داشته باشند)‌،‌ قابل تنظيم است.