۱۳۹۱/۰۱/۱۲

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/ قابل دريافت است.