کالبد شکافی Template پیش فرض یک پروژه MVC (نسخه های 4 و 5) - قسمت دوم
مطابق با ترمینولوژی MVC ، کلاس Controller نقش Dispatcher و توزیع کننده پردازش را با استفاده از متدهای Public خود که action نامیده میشوند، به عهده دارد. در بهترین حالت Controller موظف است که اطلاعات مورد نیاز برنامه را با استفاده از فراخوانی متد های کلاس Model یا شکل مناسب تر آن یعنی ViewModel آماده نموده و آنها را به منظور ایجاد یک خروجی قابل درک توسط انسان به یک View مشخص هدایت نموده تا View آنها را Render نموده و نمایش دهد .
جالب است بدانید که کل ادامه مطلب در حقیقت توضیح وضعیت های متفاوتی است که در پاراگراف قبل به صورت بسیار کلی ذکر شد . در حقیقت هدف من این است که Controller را معرفی نموده حالت های مختلف در متدهای action و تنوعی که در بحث انتخاب View وجود دارد را تشریح نموده و روشهای تبادل اطلاعات Controller و View را بررسی کنم.
اما با بررسی متد نمونه Index در کنترلر Home متوجه خواهیم شد که با توجه به آنچه در این متد مشاهده میکنیم :
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
نوع خروجی متد Index از نوع ActionResult تعیین شده است که کلاس جد انواع متعدد View هایی است که در مورد آنها صحبت خواهیم کرد.
همچنین وجود View() به عنوان مقدار خروجی به طور مشخص ذکر نمیکند که کدام View برای عملیات Rendering در Browser تعیین شده است . رایج ترین شکل استفاده از View حالتی است که در آن، نام View در قالب پارامتر، صریحا ذکر شده و چنانچه در ادامه خواهیم دید حتی نوع اطلاعاتی که به آن Import میشود می تواند به عنوان پارامتر دوم (در قالب مفهوم Model) مشخص شود .
برای شروع پیشنهاد میکنم یک پروژه Internet در MVC 4 ایجاد نمایید . سپس در فولدر Controller کلاس HomeController را باز نموده و متدی به نام NoView به صورت زیر به این کلاس اضافه نمایید. بعد از اضافه کردن این متد کلاس زیر شبیه به کد زیر خواهد بود :
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
return View();
}
public string NoView()
{
return "What's going on?";
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
در ادامه برای تکمیل این مثال در فولدر Views/Shared فایل _Layout.cshtml را باز نموده و محتویات داخل <ul id="menu"> را به صورت زیر تغییر دهید . در حقیقت تنها یک سطر به محتویات تگ ul اضافه نموده ایم که نتیجه مطابق با وضعیت زیر خواهد بود :
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
<li>@Html.ActionLink("No View", "NoView", "Home")</li>
</ul>
نتیجه این عملیات این است که ما ابتدا یک action بسیار ساده به نام NoView به کنترلر پیش فرض خودمان یعنی HomeController اضافه می کنیم. در MVC پسوند Controller باید مطابق با یک قاعده (که وحی منزل نیست و قابل تغییر است) همیشه به کلاس Controller ما اضافه شود . به عنوان مثال کنترلر Home به صورت کلاس HomeController ایجاد میشود ولی در هنگام رجوع به آن از نام اصلی یعنی Home استفاده می کنیم .
ما سپس در _Layout.cshtml با استفاده از متد Helper بسیار مهمی به نام ActionLink یک لینک استاندارد با عبارت No View (پارامتر اول) به متد action تازه ایجاد شده خود یعنی NoView (پارامتر دوم) در کنترلر Home (پارامتر سوم) ایجاد کردیم. نتیجه این کار را می توان با اجرای مجدد پروژه، ملاحظه کرد .
اما هدف من از این مثال این است که:
در ارزش هر قطعه ای گویند هر نوعی سخن
من خود به چشم خویشتن دیدم که کنترلر مهم تر است.!!!!:)
در منابع مختلفی که توسط نویسندگان و برنامه نویسان خبره مطرح شده، گاهی مشاهده می کنیم که مقایسه ای در مورد اهمیت هر یک از اجزای Model و View و Controller انجام شده است.
در اینجا شاید ورود به این بحث که اهمیت و ارزش وزنی کدامیک از این اجزا در یک پروژه MVC مهمتر می باشد ، فایده ای نداشته باشد ولی همین مثال ساده ثابت میکند که Controller ما قادر است به طور کاملا مستقل و بدون نیاز به View و Model با استفاده از این action جدید خروجی هر چند ساده و بی اهمیتی را به Browser ارسال کند .
به طور دقیق تر در اینجا عبارت Whats Going On مستقیما و بدون نیاز به View به Response.Stream ارسال میشود که بدون هیچ فرمت خاصی در Browser ظاهر میشود .
چنانچه قبلا هم اشاره شد در مورد action ی که با نام Index در پروژه پیش فرض وجود دارد مشخصا از یک View برای ارسال خروجی و Rendering آن در Browser استفاده شده است اما در متد Index نام این View صریحا ذکر نشده است .
در این حالت MVC به صورت پیش فرض جستجویی در فولدر Views/Home یا Views/Shared به دنبال یک View هم نام با action مذکور انجام داده و در صورت موفقیت آمیز بودن این جستجو، View یافت شده برای ارسال خروجی نهایی انتخاب میشود.
در اینجا این شرط محقق شده است . یعنی با ملاحظه در داخل فولدر Views/Home مشاهده میکنیم که index.cshtml وجود دارد که از دیدگاه MVC یک View ایجاد شده با استفاده از Razor می باشد .
Razor نیز از دیدگاه MVC یک ViewEngine یا موتور ارائه View می باشد که در MVC 3 معرفی شد و از Syntax بسیار ساده ای برخوردار است . کماکان ViewEngine سنتی Asp .NET یعنی aspx در پروژه های MVC قابل استفاده است اما به دلیل سادگی و مقبولیت Razor استفاده از Razor به جای aspx در اغلب منابع توصیه شده است . در Razor ترکیب متجانسی از کد سی شارپ و دستورات HTML را مشاهده خواهیم کرد که در این صورت پسوند چنین فایلهایی cshtml خواهد بود . علاقمندان به VB همچنان به دلیل ارادت مالیخولیایی بیل گیتس به Visual Basic امکان استفاده از vb در Razor را دارند که در این صورت نتیجه فایلهایی با پسوند vbhtml را شاهد خواهیم بود .
از شما چه پنهان من هیچ تعجب نخواهم کرد اگر در آینده ای نزدیک ،Xaml به عنوان یک View Engine مستقل در MVC معرفی و قابل استفاده باشد .
در اینجا لازم به تذکر است که در هنگام انتخاب نوع View به عنوان خروجی متدهای action در Controller ما از طیف متنوعی از انتخاب ها روبرو هستیم که هر یک در موارد خاصی کابرد داشته و استفاده از آنها در MVC رایج می باشد. از مهمترین آنها می توان به موارد زیر اشاره کرد:
ViewResult که رایج ترین و پر استفاده ترین انتخاب در این مورد تلقی میشود و از آنجاییکه ViewResult یکی از کلاسهای منشعب از ActionResult می باشد، قادر است با ارسال View به عنوان خروجی متد action امکان انتخاب View پیش فرض را فراهم نماید. در بیشتر مواقع با استفاده از یک پارامتر نام View مورد نظر در هنگام ارسال View به خروجی ذکر میشود . مثلا ارسال View(“ThatOne”) معادل با انتخاب یک View به نام ThatOne می باشد که در فولدر View با نام ThatOne.cshtml وجود دارد .
PartialViewResult : این کلاس هم مستقیما از ActionResult منشعب شده و برای انتخاب یک Partial View به عنوان مسئول Render کردن بخش یا قسمت مشخصی از یک View بزرگتر کاربرد دارد . برای درک بهتر این مفهوم می توان آن را با MasterPage و Content در ASP .NET Web Form مقایسه کرد. جاییکه Content ها وظیفه Render نمودن قسمت های از پیش تعیین شده MasterPage را بر عهده داشتند .
FileResult که کاربرد آن در زمانیست که نیاز به دانلود مستقیم یک فایل وجود داشته باشد . همچنین از این کلاس به عنوان خروجی یک متد action در مواقعی که قصد نمایش Dynamic تصاویر را داشته باشیم میتوان استفاده نمود .
HttpUnauthorizedResult: که همانطوریکه از نام آن مشخص است برای ارسال کد 401 به Browser استفاده میشود که به معنی Not Authorize بوده و به طور معمول منجر به Redirect شدن درخواست به سمت Page یا صفحه Login خواهد شد که پروسه های Authentication و Authorization انجام شوند.
HttpStatusCodeResult که مشخصا برای ارسال کد های رایج Http به Browser مورد استفاده قرار میگیرد. به عنوان مثال از آن می توان برای ارسال کد 404 به عنوان Page Not Found استفاده نمود .
JsonResult با توجه به شهرت و کاربردهای متنوع فرمت Javascript Object Notation ، بخصوص در وب سرویس ها پیش بینی شده و استفاده میشود .
بعد از یک مرور اجمالی در انتخابهای موجود اینک وقت آن رسیده که جزییات بیشتری را در View بررسی کرده و آنها را بر اساس کاربردهای متنوعی که دارند طبقه بندی کنیم . برای انجام این کار دوباره به قالب استاندارد پروژه بازگشته و توصیه میکنم به محتویات فایل _Layout.cshtml در فولدر Views/Shared با دقت بیشتری نگاه کنید .
نقش _Layout.cshtml بسیار شبیه به نقش MasterPage در ASP .NET Web Form می باشد. معنی دقیق این گفته این است که این فایل (یا مفهوم) معمولا برای تامین اطلاعات قسمت های تکراری در یک Page مثل Header و Logo و Footer و منو های اصلی برنامه کاربرد دارد. این یک روش ایده آل برای انجام این کار و پرهیز از تکرار این قسمتها در همه View های تخصصی برنامه است .
در داخل این فایل مخصوصا فراخوانی @RenderSection("featured", required: false) و @RenderBody() از اهمیت بیشتری برخوردار هستند .
مفهوم Section یک مفهوم نوظهور و ویژه در MVC می باشد که البته هیچ ارتباطی با تگ Sectin در HTML5 ندارد .
فراخوانی RenderSection در این فایل با استفاده از یک پارامت،ر به این معنی می باشد که Layout اصلی Page یک Section و قسمت ویژه ای به نام featured را مشخص نموده که View مسئولیت Render کردن محتویات آن Section را به عهده میگیرد . در صورت استفاده از یک پارامتر در این متد انجام این وظیفه به صورت اجباری به View محول میشود.
در صورت استفاده از پارامتر دوم که نوع آن bool می باشد می توان انجام این عمل را با ارائه مقدار false اختیاری نمود . در صورت عدم ذکر پارامتر دوم مقدار پیش فرض true بوده و معنای آن این است که View موظف است به صورت اجباری یک Section با نام تعیین شده در پارامتر اول پیش بینی نماید .
خود View نیز می تواند قبل از اقدام به تعریف یک Section ابتدا با استفاده از IsSectionDefined که پارامتری از نوع String دریافت میکند از وجود یا عدم وجود این Section مطمئن بشود. پارامتر IsSectionDefined در حقیقت نام Section می باشد.
در داخل View (مثلا در Index.cshtml) ملاحظه خواهیم کرد که ویوی Index اطلاعات مشخصی را برای نمایش در Section رزرو شده به صورت زیر تدارک دیده است :
@section featured {
<section class="featured">
<div class="content-wrapper">
<hgroup class="title">
<h1>@ViewBag.Title.</h1>
<h2>@ViewBag.Message</h2>
</hgroup>
<p>
To learn more about ASP.NET MVC visit
<a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
The page features <mark>videos, tutorials, and samples</mark> to help you get the most from ASP.NET MVC.
If you have any questions about ASP.NET MVC visit
<a href="http://forums.asp.net/1146.aspx/1?MVC" title="ASP.NET MVC Forum">our forums</a>.
</p>
</div>
</section>
}
همچنین توجه به این نکته ضروریست که یک View می تواند صریحا با ذکر
@{
Layout = null;
{
به صورت مستقل (بدون نیاز به Layout) وظیفه ارسال خروجی به Browser را تقبل نماید . در شکل دیگری از این دستور ، View می تواند یک Layout متفاوت با Layout پیش فرض را به عنوان Layout وابسته خود انتخاب نماید و در نهایت اینکه اگر مقدار null در Layout (حالت اول) ذکر نشود MVC با مراجعه به _ViewStart.cshtml نام و جایگاه Layout پیش فرض را مشخص نموده و از آن به عنوان Layout مورد نظر View استفاده خواهد کرد .
پس انتخابها کاملا روشن و واضح می باشند و نکته مهم در اینجا توجه به رابطه بین یک View و Layout همچنین امکان استفاده از Partial View و Section هاست که باز در مقایسه با ASP .NET Web Form به صورت Nested Master Page و یا حتی شبیه به مفهوم User Control از آنها استفاده نمود .
در انتهای این قسمت باید اشاره کنم که RenderBody در حقیقت نوع خاصی از RenderSection است که محتویات و اطلاعات مشخص شده در View را که در هیچ Section خاصی قرار ندارند ، انتخاب نموده و در محل مشخص شده در فراخوانی RenderBody نمایش داده و یا به طور دقیق تر Render میکند . در برخی از منابع از آن به عنوان Unnamed Section یا Section بدون نام، اسم برده شده است .
در قسمت بعد، به سه مکانیسم مشخص و تعریف شده MVC برای تبادل اطلاعات بین Controller و View اشاره خواهم کرد و مثالهایی در این باره بررسی خواهیم نمود تا زمینه مساعدی برای معرفی و آشنایی با Model و شکل تخصصی تر آن یعنی ViewModel فراهم شود.