مطالب
ASP.NET MVC #12

تولید خودکار فرم‌های ورود و نمایش اطلاعات در ASP.NET MVC بر اساس اطلاعات مدل‌ها

در الگوی MVC، قسمت M یا مدل آن یک سری ویژگی‌های خاص خودش را دارد:
شما را وادار نمی‌کند که مدل را به نحو خاصی طراحی کنید. شما را مجبور نمی‌کند که کلاس‌های مدل را برای نمونه همانند کلاس‌های کنترلرها، از کلاس خاصی به ارث ببرید. یا حتی در مورد نحوه‌ی دسترسی به داده‌ها نیز، نظری ندارد. به عبارتی برنامه نویس است که می‌تواند بر اساس امکانات مهیای در کل اکوسیستم دات نت، در این مورد آزادانه تصمیم گیری کند.
بر همین اساس ASP.NET MVC یک سری قرارداد را برای سهولت اعتبار سنجی یا تولید بهتر رابط کاربری بر اساس اطلاعات مدل‌ها، فراهم آورده است. این قراردادها هم چیزی نیستند جز یک سری metadata که نحوه‌ی دربرگیری اطلاعات را در مدل‌ها توضیح می‌دهند. برای دسترسی به آن‌ها پروژه جاری باید ارجاعی را به اسمبلی‌های System.ComponentModel.DataAnnotations.dll و System.Web.Mvc.dll داشته باشد (که VS.NET به صورت خودکار در ابتدای ایجاد پروژه اینکار را انجام می‌دهد).

یک مثال کاربردی

یک پروژه جدید خالی ASP.NET MVC را آغاز کنید. در پوشه مدل‌های آن، مدل اولیه‌ای را با محتوای زیر ایجاد نمائید:

using System;

namespace MvcApplication8.Models
{
public class Employee
{
public int Id { set; get; }
public string Name { set; get; }
public decimal Salary { set; get; }
public string Address { set; get; }
public bool IsMale { set; get; }
public DateTime AddDate { set; get; }
}
}

سپس یک کنترلر جدید را هم به نام EmployeeController با محتوای زیر به پروژه اضافه نمائید:

using System;
using System.Web.Mvc;
using MvcApplication8.Models;

namespace MvcApplication8.Controllers
{
public class EmployeeController : Controller
{
public ActionResult Create()
{
var employee = new Employee { AddDate = DateTime.Now };
return View(employee);
}
}
}

بر روی متد Create کلیک راست کرده و یک View ساده را برای آن ایجاد نمائید. سپس محتوای این View را به صورت زیر تغییر دهید:

@model MvcApplication8.Models.Employee
@{
ViewBag.Title = "Create";
}

<h2>Create An Employee</h2>

@using (Html.BeginForm(actionName: "Create", controllerName: "Employee"))
{
@Html.EditorForModel()
<input type="submit" value="Save" />
}

اکنون اگر پروژه را اجرا کرده و مسیر http://localhost/employee/create را وارد نمائید، یک صفحه ورود اطلاعات تولید شده به صورت خودکار را مشاهده خواهید کرد. متد Html.EditorForModel بر اساس اطلاعات خواص عمومی مدل، یک فرم خودکار را تشکیل می‌دهد.
البته فرم تولیدی به این شکل شاید آنچنان مطلوب نباشد، از این جهت که برای مثال Id را هم لحاظ کرده، در صورتیکه قرار است این Id توسط بانک اطلاعاتی انتساب داده شود و نیازی نیست تا کاربر آن‌را وارد نماید. یا مثلا برچسب AddDate نباید به این شکل صرفا بر اساس نام خاصیت متناظر با آن تولید شود و مواردی از این دست. به عبارتی نیاز به سفارشی سازی کار این فرم ساز توکار ASP.NET MVC وجود دارد که ادامه بحث جاری را تشکیل خواهد داد.



سفارشی سازی فرم ساز توکار ASP.NET MVC با کمک Metadata خواص

برای اینکه بتوان نحوه نمایش فرم خودکار تولید شده را سفارشی کرد، می‌توان از یک سری attribute و data annotations توکار دات نت و ASP.NET MVC استفاده کرد و نهایتا این metadata توسط فریم ورک، مورد استفاده قرار خواهند گرفت. برای مثال:

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace MvcApplication8.Models
{
public class Employee
{
//[ScaffoldColumn(false)]

[HiddenInput(DisplayValue=false)]
public int Id { set; get; }

public string Name { set; get; }

[DisplayName("Annual Salary ($)")]
public decimal Salary { set; get; }

public string Address { set; get; }

[DisplayName("Is Male?")]
public bool IsMale { set; get; }

[DisplayName("Start Date")]
[DataType(DataType.Date)]
public DateTime AddDate { set; get; }
}
}

در اینجا به کمک ویژگی HiddenInput از نمایش عمومی خاصیت Id جلوگیری خواهیم کرد یا توسط ویژگی DisplayName، برچسب دلخواه خود را به عناصر فرم تشکیل شده، انتساب خواهیم داد. اگر نیاز باشد تا خاصیتی کلا از رابط کاربری حذف شود می‌توان از ویژگی ScaffoldColumn با مقدار false استفاده کرد. یا توسط DataType، مشخص کرده‌ایم که نوع ورودی فقط قرار است Date باشد و نیازی به قسمت Time آن نداریم.
DataType شامل نوع‌های از پیش تعریف شده دیگری نیز هست. برای مثال اگر نیاز به نمایش TextArea بود از مقدار MultilineText، ‌استفاده کنید:

[DataType(DataType.MultilineText)]

یا برای نمایش PasswordBox از مقدار Password می‌توان کمک گرفت. اگر نیاز دارید تا آدرس ایمیلی به شکل یک لینک mailto نمایش داده شود از مقدار EmailAddress استفاده کنید. به کمک مقدار Url، متن خروجی به صورت خودکار تبدیل به یک آدرس قابل کلیک خواهد شد.
اکنون اگر پروژه را مجددا کامپایل کنیم و به آدرس ایجاد یک کارمند جدید مراجعه نمائیم، با رابط کاربری بهتری مواجه خواهیم شد.



سفارشی سازی ظاهر فرم ساز توکار ASP.NET MVC

در ادامه اگر بخواهیم ظاهر این فرم را اندکی سفارشی‌تر کنیم، بهتر است به سورس صفحه تولیدی در مرورگر مراجعه کنیم. در اینجا یک سری عناصر HTML محصور شده با div را خواهیم یافت. هر کدام از این‌ها هم با classهای css خاص خود تعریف شده‌اند. بنابراین اگر علاقمند باشیم که رنگ و قلم و غیره این موارد تغییر دهیم، تنها کافی است فایل css برنامه را ویرایش کنیم و نیازی به دستکاری مستقیم کدهای برنامه نیست.



انتساب قالب‌های سفارشی به خواص یک شیء

تا اینجا در مورد نحوه سفارشی سازی رنگ، قلم، برچسب و نوع داده‌های هر کدام از عناصر نهایی نمایش داده شده، توضیحاتی را ملاحظه نمودید.
در فرم تولیدی نهایی، خاصیت bool تعریف شده به صورت خودکار به یک checkbox تبدیل شده است. چقدر خوب می‌شد اگر امکان تبدیل آن مثلا به RadioButton انتخاب مرد یا زن بودن کارمند ثبت شده در سیستم وجود داشت. برای اصلاح یا تغییر این مورد، باز هم می‌توان از متادیتای خواص، جهت تعریف قالبی خاص برای هر کدام از خواص مدل استفاده کرد.
به پوشه Views/Shared مراجعه کرده و یک پوشه جدید به نام EditorTemplates را ایجاد نمائید. بر روی این پوشه کلیک راست کرده و گزینه Add view را انتخاب کنید. در صفحه باز شده، گزینه «Create as a partial view» را انتخاب نمائید و نام آن‌را هم مثلا GenderOptions وارد کنید. همچنین گزینه «Create a strongly typed view» را نیز انتخاب کنید. مقدار Model class را مساوی bool وارد نمائید. فعلا یک hello داخل این صفحه جدید وارد کرده و سپس خاصیت IsMale را به نحو زیر تغییر دهید:

[DisplayName("Gender")]
[UIHint("GenderOptions")]
public bool IsMale { set; get; }

توسط ویژگی UIHint، می‌توان یک خاصیت را به یک partial view متصل کرد. در اینجا خاصیت IsMale به partial view ایی به نام GenderOptions متصل شده است. اکنون اگر برنامه را کامپایل و اجرا کرده و آدرس ایجاد یک کارمند جدید را ملاحظه کنید، بجای Checkbox باید یک hello نمایش داده شود.
محتویات این Partial view هم نهایتا به شکل زیر خواهند بود:

@model bool
<p>@Html.RadioButton("", false, !Model) Female</p>
<p>@Html.RadioButton("", true, Model) Male</p>

در اینجا Model که از نوع bool تعریف شده، به خاصیت IsMale اشاره خواهد کرد. دو RadioButton هم برای انتخاب بین حالت زن و مرد تعریف شده‌اند.

یا یک مثال جالب دیگر در این زمینه می‌تواند تبدیل enum به یک Dropdownlist باشد. در این حالت partial view ما شکل زیر را خواهد یافت:

@model Enum
@Html.DropDownListFor(m => m, Enum.GetValues(Model.GetType())
.Cast<Enum>()
.Select(m => {
string enumVal = Enum.GetName(Model.GetType(), m);
return new SelectListItem() {
Selected = (Model.ToString() == enumVal),
Text = enumVal,
Value = enumVal
};
}))

و برای استفاده از آن، از ویژگی زیر می‌توان کمک گرفت (مزین کردن خاصیتی از نوع یک enum دلخواه، جهت تبدیل خودکار آن به یک دراپ داون لیست):

[UIHint("Enum")]


سایر متدهای کمکی تولید و نمایش خودکار اطلاعات از روی اطلاعات مدل‌های برنامه

متدهای دیگری نیز در رده‌ی Templated helpers قرار می‌گیرند. اگر از متد Html.EditorFor استفاده کنیم، از تمام این اطلاعات متادیتای تعریف شده نیز استفاده خواهد کرد. همانطور که در قسمت قبل (قسمت 11) نیز توضیح داده شد، صفحه استاندارد Add view در VS.NET به همراه یک سری قالب تولید فرم‌های Create و Edit هم هست که دقیقا کد نهایی تولیدی را بر اساس همین متد تولید می‌کند.
استفاده از Html.EditorFor انعطاف پذیری بیشتری را به همراه دارد. برای مثال اگر یک طراح وب، طرح ویژه‌ای را در مورد ظاهر فرم‌های سایت به شما ارائه دهد، بهتر است از این روش استفاده کنید. اما خروجی نهایی Html.EditorForModel به کمک تعدادی متادیتا و اندکی دستکاری CSS، از دیدگاه یک برنامه نویس بی نقص است!
به علاوه، متد Html.DisplayForModel نیز مهیا است. بجای اینکه کار تولید رابط کاربری اطلاعات نمایش جزئیات یک شیء را انجام دهید، اجازه دهید تا متد Html.DisplayForModel اینکار را انجام دهد. سفارشی سازی آن نیز همانند قبل است و بر اساس متادیتای خواص انجام می‌شود. در این حالت، مسیر پیش فرض جستجوی قالب‌های UIHint آن، Views/Shared/DisplayTemplates می‌باشد. همچنین Html.DisplayFor نیز جهت کار با یک خاصیت مدل تدارک دیده شده است. البته باید درنظر داشت که استفاده از پوشه Views/Shared اجباری نیست. برای مثال اگر از پوشه Views/Home/DisplayTemplates استفاده کنیم، قالب‌های سفارشی تهیه شده تنها جهت Viewهای کنترلر home قابل استفاده خواهند بود.
یکی دیگر از ویژگی‌هایی که جهت سفارشی سازی نحوه نمایش خودکار اطلاعات می‌تواند مورد استفاده قرار گیرد، DisplayFormat است. برای مثال اگر مقدار خاصیت در حال نمایش نال بود، می‌توان مقدار دیگری را نمایش داد:

[DisplayFormat(NullDisplayText = "-")]

یا اگر علاقمند بودیم که فرمت اطلاعات در حال نمایش را تغییر دهیم، به نحو زیر می‌توان عمل کرد:

[DisplayFormat(DataFormatString  = "{0:n}")]

مقدار DataFormatString در پشت صحنه در متد string.Format مورد استفاده قرار می‌گیرد.
و اگر بخواهیم که این ویژگی در حالت تولید فرم ویرایش نیز درنظر گرفته شود، می‌توان خاصیت ApplyFormatInEditMode را نیز مقدار دهی کرد:

[DisplayFormat(DataFormatString  = "{0:n}", ApplyFormatInEditMode = true)]



بازنویسی قالب‌های پیش فرض تولید فرم یا نمایش اطلاعات خودکار ASP.NET MVC

یکی دیگر از قرارداهای بکارگرفته شده در حین استفاده از قالب‌های سفارشی، استفاده از نام اشیاء می‌باشد. مثلا در پوشه Views/Shared/DisplayTemplates، اگر یک Partial view به نام String.cshtml وجود داشته باشد، از این پس نحوه رندر کلیه خواص رشته‌ای تمام مدل‌ها، بر اساس محتوای فایل String.cshtml مشخص می‌شود؛ به همین ترتیب در مورد datetime و سایر انواع مهیا.
برای مثال اگر خواستید تمام تاریخ‌های میلادی دریافتی از بانک اطلاعاتی را شمسی نمایش دهید، فقط کافی است یک فایل datetime.cshtml سفارشی را تولید کنید که Model آن تاریخ میلادی دریافتی است و نهایتا کار این Partial view، رندر تاریخ تبدیل شده به همراه تگ‌های سفارشی مورد نظر می‌باشد. در این حالت نیازی به ذکر ویژگی UIHint نیز نخواهد بود و همه چیز خودکار است.
به همین ترتیب اگر نام مدل ما Employee باشد و فایل Partial view ایی به نام Employee.cshtml در پوشه Views/Shared/DisplayTemplates قرار گیرد، متد Html.DisplayForModel به صورت پیش فرض از محتوای این فایل جهت رندر اطلاعات نمایش جزئیات شیء Employee استفاده خواهد کرد.
داخل Partial viewهای سفارشی تعریف شده به کمک خاصیت ViewData.TemplateInfo.FormattedModelValue مقدار نهایی فرمت شده قابل استفاده را فراهم می‌کند. این مورد هم از این جهت حائز اهمیت است که نیازی نباشد تا ویژگی DisplayFormat را به صورت دستی پردازش کنیم. همچنین اطلاعات ViewData.ModelMetadata نیز دراینجا قابل دسترسی هستند.



سؤال: Partial View چیست؟

همانطور که از نام Partial view بر‌می‌آید، هدف آن رندر کردن قسمتی از صفحه است به همراه استفاده مجدد از کدهای تولید رابط کاربری در چندین و چند View؛ چیزی شبیه به User controls در ASP.NET Web forms البته با این تفاوت که Page life cycle و Code behind و سایر موارد مشابه آن در اینجا حذف شده‌اند. همچنین از Partial viewها برای به روز رسانی قسمتی از صفحه حین فراخوانی‌های Ajaxایی نیز استفاده می‌شود. مهم‌ترین کاربرد Partial views علاوه بر استفاده مجدد از کدها، خلوت کردن Viewهای شلوغ است جهت ساده‌تر سازی نگهداری آن‌ها در طول زمان (یک نوع Refactoring فایل‌های View محسوب می‌شوند).
پسوند این فایل‌ها نیز بسته به موتور View مورد استفاده تعیین می‌شود. برای مثال حین استفاده از Razor، پسوند Partial views همان cshtml یا vbhtml می‌باشد. یا اگر از web forms view engine استفاده شود، پسوند آن‌ها ascx است (همانند User controls در وب فرم‌ها).
البته چون در حالت استفاده از موتور Razor، پسوند View و Partial viewها یکی است، مرسوم شده است که نام Partial viewها را با یک underline شروع کنیم تا بتوان بین این دو تمایز قائل شد.
اگر این فایل‌ها را در پوشه Views/Shared تعریف کنیم، در تمام Viewها قابل استفاده خواهند بود. اما اگر مثلا در پوشه Views/Home آن‌هارا قرار دهیم، تنها در Viewهای متعلق به کنترلر Home، قابل بکارگیری می‌باشند.
Partial views را نیز می‌توان strongly typed تعریف کرد و به این ترتیب با مشخص سازی دقیق نوع model آن، علاوه بر بهره‌مندی از Intellisense خودکار، رندر آن‌را نیز تحت کنترل کامپایلر قرار داد.
مقدار Model در یک View بر اساس اطلاعات مدلی که به آن ارسال شده است تعیین می‌گردد. اما در یک Partial view که جزئی از یک View را نهایتا تشکیل خواهد داد، بر اساس مقدار ارسالی از طریق View معین می‌گردد.

یک مثال
در ادامه قصد داریم کد حلقه نمایش لیستی از عناصر تولید شده توسط VS.NET را به یک Partial view منتقل و Refactor کنیم.
ابتدا یک منبع داده فرضی زیر را در نظر بگیرید:
using System;
using System.Collections.Generic;

namespace MvcApplication8.Models
{
public class Employees
{
public IList<Employee> CreateEmployees()
{
return new[]
{
new Employee { Id = 1, AddDate = DateTime.Now.AddYears(-3), Name = "Emp-01", Salary = 3000},
new Employee { Id = 2, AddDate = DateTime.Now.AddYears(-2), Name = "Emp-02", Salary = 2000},
new Employee { Id = 3, AddDate = DateTime.Now.AddYears(-1), Name = "Emp-03", Salary = 1000}
};
}
}
}

سپس از آن در یک کنترلر برای بازگشت لیستی از کارکنان استفاده خواهیم کرد:

public ActionResult EmployeeList()
{
var list = new Employees().CreateEmployees();
return View(list);
}

View متناظر با این متد را هم با کلیک راست بر روی متد، انتخاب گزینه Add view و سپس ایجاد یک strongly typed view از نوع کلاس Employee، ایجاد خواهیم کرد.
در ادامه قصد داریم بدنه حلقه زیر را refactor کنیم و آن‌را به یک Parial view منتقل نمائیم تا View ما اندکی خلوت‌تر و مفهوم‌تر شود:

@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Salary)
</td>
<td>
@Html.DisplayFor(modelItem => item.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.IsMale)
</td>
<td>
@Html.DisplayFor(modelItem => item.AddDate)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
@Html.ActionLink("Details", "Details", new { id=item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}

سپس بر روی پوشه Views/Employee کلیک راست کرده و گزینه Add|View را انتخاب کنید. در اینجا نام _EmployeeItem را وارد کرده و همچنین گزینه Create as a partial view و create a strongly typed view را نیز انتخاب کنید. نوع مدل هم Employee خواهد بود. به این ترتیب فایل زیر تشکیل خواهد شد:
\Views\Employee\_EmployeeItem.cshtml

ابتدای نام فایل‌را با underline شروع کرده‌ایم تا بتوان بین Viewها و Partial views تفاوت قائل شد. همچنین این Partial view چون داخل پوشه Employee تعریف شده، فقط در Viewهای کنترلر Employee در دسترس خواهد بود.
در ادامه کل بدنه حلقه فوق را cut کرده و در این فایل جدید paste نمائید. مرحله اول refactoring یک view به همین نحو آغاز می‌شود. البته در این حالت قادر به استفاده از Partial view نخواهیم بود چون اطلاعاتی که به این فایل ارسال می‌گردد و مدلی که در دسترس آن است از نوع Employee است و نه لیستی از کارمندان. به همین جهت باید item را با Model جایگزین کرد:

@model MvcApplication8.Models.Employee

<tr>
<td>
@Html.DisplayFor(x => x.Name)
</td>
<td>
@Html.DisplayFor(x => x.Salary)
</td>
<td>
@Html.DisplayFor(x => x.Address)
</td>
<td>
@Html.DisplayFor(x => x.IsMale)
</td>
<td>
@Html.DisplayFor(x => x.AddDate)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
@Html.ActionLink("Details", "Details", new { id = Model.Id }) |
@Html.ActionLink("Delete", "Delete", new { id = Model.Id })
</td>
</tr>


سپس برای استفاده از این Partial view در صفحه نمایش لیست کارمندان خواهیم داشت:

@foreach (var item in Model) {
@Html.Partial("_EmployeeItem", item)
}

متد Html.Partial، اطلاعات یک Partial view را پردازش و تبدیل به یک رشته کرده و در اختیار Razor قرار می‌دهد تا در صفحه نمایش داده شود. پارامتر اول آن نام Partial view مورد نظر است (نیازی به ذکر پسوند فایل نیست) و پارامتر دوم، اطلاعاتی است که به آن ارسال خواهد شد.
متد دیگری هم وجود دارد به نام Html.RenderPartial. کار این متد نوشتن مستقیم در Response است، برخلاف Html.Partial که فقط یک رشته را بر می‌گرداند.



نمایش اطلاعات از کنترلر‌های مختلف در یک صفحه

Html.Partial بر اساس اطلاعات مدل ارسالی از یک کنترلر، کار رندر قسمتی از آن‌را در یک View خاص عهده دار خواهد شد. اما اگر بخواهیم مثلا در یک صفحه یک قسمت را به نمایش آخرین اخبار و یک قسمت را به نمایش آخرین وضعیت آب و هوا اختصاص دهیم، از روش دیگری به نام RenderAction می‌توان کمک گرفت. در اینجا هم دو متد Html.Action و Html.RenderAction وجود دارند. اولی یک رشته را بر می‌گرداند و دومی اطلاعات را مستقیما در Response درج می‌کند.

یک مثال:
کنترلر جدیدی را به نام MenuController به پروژه اضافه کنید:
using System.Web.Mvc;

namespace MvcApplication8.Controllers
{
public class MenuController : Controller
{
[ChildActionOnly]
public ActionResult ShowMenu(string options)
{
return PartialView(viewName: "_ShowMenu", model: options);
}
}
}

سپس بر روی نام متد کلیک راست کرده و گزینه Add view را انتخاب کنید. در اینجا قصد داریم یک partial view که نامش با underline شروع می‌شود را اضافه کنیم. مثلا با محتوای زیر ( با توجه به اینکه مدل ارسالی از نوع رشته‌ای است):

@model string

<ul>
<li>
@Model
</li>
</ul>

حین فراخوانی متد Html.Action، یک متد در یک کنترلر فراخوانی خواهد شد (که شامل ارائه درخواست و طی سیکل کامل پردازشی آن کنترلر نیز خواهد بود). سپس آن متد با بازگشت دادن یک PartialView، اطلاعات پردازش شده یک partial view را به فراخوان بازگشت می‌دهد. اگر نامی ذکر نشود، همان نام متد در نظر گرفته خواهد شد. البته از آنجائیکه در این مثال در ابتدای نام Partial view یک underline قرار دادیم، نیاز خواهد بود تا این نام صریحا ذکر گردد (چون دیگر هم نام متد یا ActionName آن نیست). ویژگی ChildActionOnly سبب می‌شود تا این متد ویژه تنها از طریق فراخوانی Html.Action در دسترس باشد.
برای استفاده از آن هم در Viewایی دیگر خواهیم داشت:

@Html.Action(actionName: "ShowMenu", controllerName: "Menu", 
routeValues: new { options = "some data..." })

در اینجا هم پارامتر ارسالی به کمک anonymously typed objects مشخص و مقدار دهی شده است.


سؤال مهم: چه تفاوتی بین RenderPartial و RenderAction وجود دارد؟ به نظر هر دو یک کار را انجام می‌دهند، هر دو مقداری HTML را پس از پرداش به صفحه تزریق می‌کنند.
پاسخ: اگر View والد، دارای کلیه اطلاعات لازم جهت نمایش اطلاعات Partial view است، از RenderPartial استفاده کنید. به این ترتیب برخلاف حالت RenderAction درخواست جدیدی به ASP.NET Pipeline صادر نشده و کارآیی نهایی بهتر خواهد بود. صرفا یک الحاق ساده به صفحه انجام خواهد شد.
اما اگر برای رندر کردن این قسمت از صفحه که قرار است اضافه شود، نیاز به دریافت اطلاعات دیگری خارج از اطلاعات مهیا می‌باشد، از روش RenderAction استفاده کنید. برای مثال اگر در صفحه جاری قرار است لیست پروژه‌ها نمایش داده شود و در کنار صفحه مثلا منوی خاصی باید قرار گیرد، اطلاعات این منو در View جاری فراهم نیست (و همچنین مرتبط به آن هم نیست). بنابراین از روش RenderAction برای حل این مساله می‌توان کمک گرفت.
به صورت خلاصه برای نمایش اطلاعات تکراری در صفحات مختلف سایت در حالتیکه این اطلاعات از قسمت‌های دیگر صفحه ایزوله است (مثلا نمایش چند ویجت مختلف در صفحه)، روش RenderAction ارجحیت دارد.


یک نکته
فراخوانی متدهای RenderAction و RenderPartial در حین کار با Razor باید به شکل فراخوانی یک متد داخل {} باشند:

@{ Html.RenderAction("About"); }
And not @Html.RenderAction("About")

علت این است که @ به تنهایی به معنای نوشتن در Response است. متد RenderAction هم خروجی ندارد و مستقیما در Response اطلاعات خودش را درج می‌کند. بنابراین این دو با هم همخوانی ندارند و باید به شکل یک متد معمولی با آن رفتار کرد.
اگر حجم اطلاعاتی که قرار است در صفحه درج شود بالا است، متدهای RenderAction و RenderPartial نسبت به Html.Action و Html.Partial کارآیی بهتری دارند؛ چون یک مرحله تبدیل کل اطلاعات به رشته و سپس درج نتیجه در Response، در آن‌ها حذف شده است.


نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها
ساده شدن امکان تعریف ایندکس‌ها با Attributes از EF-Core 5x

ویژگی جدید Index که در اسمبلی Microsoft.EntityFrameworkCore.Abstractions واقع شده‌است، امکان تعریف انواع و اقسام ایندکس‌ها را میسر می‌کند. این ویژگی باید به خود کلاس اعمال شود و نه تک تک خواص. چند مثال:
الف) تعریف ایندکس بر روی خاصیت Url یک کلاس
[Index(nameof(Url))]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}
که امکان تعریف نام سفارشی آن نیز میسر است:
[Index(nameof(Url), Name = "Index_Url")]

ب) ایندکس‌های ترکیبی
[Index(nameof(FirstName), nameof(LastName))]
public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

ج) ایندکس‌های منحصربفرد
[Index(nameof(Url), IsUnique = true)]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}
مطالب
کار با Visual Studio در ASP.NET Core
پیش نویس: این مقاله ترجمه شده فصل 6 کتاب Pro Asp.Net Core MVC2 می‌باشد.

کار با Visual Studio

در این مقاله، یکسری توضیحاتی در مورد ویژگی‌های کلیدی ویژوال استودیو به برنامه نویس‌های (توسعه دهنده‌های) پروژه‌های Asp.net Core MVC ارائه می‌دهیم.

 
ایجاد یک پروژه

در ابتدا یک پروژه‌ی وب جدید Asp.net core را به نام Working و بر اساس قالب Empty ایجاد می‌کنیم. سپس در کلاس startup، قابلیت MVC را فعال میکنیم (کدهای این قسمت، در فصل 5 کامل شرح داده شده‌است)
 

namespace Working
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.Run(async (context) =>
            //{
            //    await context.Response.WriteAsync("Hello World!");
            //});
        }
    }
}

ایجاد مدل:

یک پوشه جدید را به نام Models ایجاد می‌کنیم و بعد در این پوشه یک کلاس جدید را به نام Product ایجاد می‌کنیم و کدهای زیر را در کلاس ایجاد شده قرار میدهیم (این قسمت در فصل 5 نیز شرح داده شده‌است):
namespace Working.Models
{
    public class Product
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}
برای ایجاد یک فروشگاه ساده از اشیاء محصول، من یک فایل کلاس را به نام SimpleRepository.cs به پوشه Models اضافه و از آن برای تعریف کلاس استفاده کردم.
 

namespace WorkingWithVisualStudio.Models
{
    public class SimpleRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products = new Dictionary<string, Product>();
        public static SimpleRepository SharedRepository => sharedRepository;

        public SimpleRepository()
        {
            var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } };
            foreach (var p in initialItems)
            {
                AddProduct(p);
            }
        }
        public IEnumerable<Product> Products => products.Values;
        public void AddProduct(Product p) => products.Add(p.Name, p);

    }
}
کلاس Stores، اشیا مدل را در حافظه ذخیره می‌کند. یعنی هر تغییری را که در Model داده باشید، زمانیکه نرم افزار متوقف یا Restart شود، از بین می‌رود. یک فروشگاه ناپیوسته برای مثال در این فصل کافی است. اما این رویکردی نیست که در بسیاری از پروژه‌های واقعی استفاده شود. در فصل 8 یک مثال را پیاده سازی می‌کنیم تا اطلاعات مدل Store را به صورت مداوم در بانک اطلاعاتی ذخیره کند.

نکته: من یک مشخصه (Property) استاتیک را به نام SharedRepository تعریف کردم که دسترسی به SimpleRepository را فراهم می‌کند و می‌تواند در طول برنامه از آن استفاده شود. این بهترین کار نیست، ولی میخواهم یک مشکل رایج را که در توسعه MVC روبرو میشوید، نشان دهم. من راه بهتری را برای کار با اجزای مشترک، در فصل 18 توضیح می‌دهم.


ایجاد Controller و View

در پوشه Controllers، یک فایل جدید را به نام HomeController.cs ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index() => View(SimpleRepository.SharedRepository.Products);
    }
}
این یکی Action Method ایی به نام Index است که اطلاعات مدل را دریافت می‌کند و برای View پیشفرض، جهت نمایش ارسال می‌کند. برای ایجاد View هم بر روی پوشه Views/Home راست کلیک کرده و یک View را به نام index.cshtml ایجاد کنید؛ با کدهای زیر:
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <table>
    <thead>
        <tr>
        <td>Name</td>
        <td>Price</td>
        </tr>
        </thead>
    <tbody>            @foreach (var p in Model)
    {<tr>
        <td>@p.Name</td>
        <td>@p.Price</td>
        </tr>}
        </tbody>
    </table>
</body>
</html>
این View شامل یک جدول است که از حلقه foreach Razor، برای ایجاد ردیف‌هایی برای هر شیء مدل استفاده می‌کند. جایی که هر ردیف، حاوی سلول‌هایی برای خواص نام و قیمت است. اگر شما برنامه کاربردی را اجرا کنید، نتایج حاصل را در شکل خواهید دید:

مدیریت بسته‌های نرم افزاری

دو نوع مختلف از بسته‌های نرم افزاری مورد نیاز برای Asp.Net Core MVC وجود دارند.

معرفی NuGet 

ویژوال استودیو به همراه یک ابزار گرافیکی برای مدیریت بسته‌های NET. است که در یک پروژه گنجانده شده‌است. برای باز کردن این ابزار، گزینه Management NuGet Packages for Solution را از منوی Tools ➤ NuGet Package Manager انتخاب کنید. به این ترتیب ابزار NuGet باز می‌شود و لیستی از بسته‌هایی که قبلا نصب شده‌اند، نمایش داده می‌شود؛ همانطور که در شکل زیر نشان داده شده‌است:
 


برگه‌ی Installed، خلاصه‌ای از بسته‌هایی را که قبلا در پروژه نصب شده‌اند، نشان می‌دهد. از برگه‌ی Browse، برای یافتن و نصب بسته‌های جدید می‌توان استفاده کرد و برگه‌ی Updates، فهرست package هایی را که نسخه‌های اخیر آن‌ها منتشر شده‌اند، نمایش می‌دهد.

 
معرفی بسته‌ی MICROSOFT.ASPNETCORE.ALL

اگر شما از نسخه‌های قبلی Asp.Net Core استفاده کرده باشید، باید یک لیست طولانی از بسته‌های NuGet را به پروژه جدید خود اضافه نمایید. Asp.Net Core2 یک بسته‌ی متفاوت را به نام Microsoft.AspNetCore.All معرفی می‌کند.

پکیچ Microsoft.AspNetCore.All یک meta-package است که شامل تمام بسته‌های Nuget مورد نیاز Asp.net Core و MVC Framework است. یعنی شما دیگر نیازی به نصب تک به تک این نوع بسته‌ها ندارید و هنگامیکه برنامه خود را منتشر می‌کنید، هر بسته‌ای از بسته‌های Meta-package که مورداستفاده قرار نمی‌گیرند، حذف خواهند شد. البته این بسته در نگارش 2.1، قسمت All آن به App تغییر نام یافته‌است.
 
معرفی بسته‌های Nuget و موقعیت ذخیره سازی آن‌ها

ابزار NuGet لیست بسته‌های پروژه را در فایل projectname.csproj نگهداری می‌کند. در اینجا <projectname> با نام پروژه جایگزین میشود. برای مثال در پروژه فوق اطلاعات Nuget، در فایل WorkingWithVisualStudio.csproj ذخیره می‌شوند. ویژوال استودیو محتویات فایل csproj را در پنجره‌ی Solution Explorer نمایش نمی‌دهد. برای ویرایش این فایل، روی پروژه در پنجره‌ی Solution Explorer راست کلیک کنید و گزینه‌ی Edit WorkWithVisualStudio.csproj را از منوی باز شده، انتخاب کنید. ویژوال استودیو فایل را برای ویرایش باز می‌کند. فایل csproj یک فایل XML است و شما در آن عنصری را مانند قطعه کد زیر در آن می‌بینید که Asp.net Core Meta package را به پروژه اضافه می‌کند:
<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
در اینجا یک بسته با نام و شماره نسخه مورد نیاز مشخص شده‌است. اگرچه بسته Meta-Package شامل تمام ویژگی‌های مورنیاز Asp.Net Core MVC می‌باشد، اما شما هنوز هم باید بسته‌های دیگری را به پروژه اضافه کنید تا بتوانید از ویژگی‌های اضافی خاص آن‌ها استفاده کنید. این بسته‌ها را می‌توان توسط رابط‌های خط فرمان و یا ابزار گرافیکی آن اضافه کرد. حتی شما می‌توانید فایل Csproj را به صورت مستقیم ویرایش کنید و ویژوال استودیو میتواند تغییرات بسته‌ها را شناسایی کرده، دانلود و نصب کند.
 


هنگامیکه از NuGet برای اضافه کردن یک بسته به پروژه‌ی خود استفاده می‌کنید، به صورت خودکار به همراه هر بسته‌ای که به آن وابستگی دارد، نصب می‌شود. شما می‌توانید بسته‌های Nuget و وابستگی‌های آن‌ها را در SolutionExpolrer از طریق گزینه‌ی Dependencies -> Nuget مشاهده کنید که هر یک از بسته‌های موجود در فایل csproj و وابستگی‌های آن‌ها را نشان می‌دهد. برای نمونه بسته Meta-Package ASP.Net Core دارای تعداد زیادی وابستگی است؛ برخی از آنها در شکل زیر دیده میشوند:


 
معرفی Bower

یک بسته Client-Side، شامل محتوایی است که به مشتری ارسال می‌شود؛ مانند فایل‌های جاوا اسکریپت، Css Stylesheets و یا تصاویر. از Nuget برای مدیریت این نوع فایل‌ها در پروژه نیز استفاده میشود. اما اکنون Asp.Net Core MVC پشتیبانی توکاری را از یک ابزار مدیریت بسته‌های سمت کاربر، به نام Bower نیز ارائه می‌دهد. Bower یک ابزار منبع باز ( Open Source ) است که در خارج از مایکروسافت و دنیای NET. توسعه داده شده و نگهداری می‌شود.

نکته: Bower به تازگی منسوخ شده اعلام گردیده‌است. ممکن است هشدارهایی را که ابزارهای جایگزین را پیشنهاد می‌کنند نیز مشاهده کنید. با این حال پشتیبانی از Bower با ویژوال استودیو یکپارچه شده‌است و در نگارش 2.1 ابزار مدیریت سمت کلاینت جدید دیگری را نیز بجای آن معرفی کرده‌اند.
 

معرفی لیست بسته‌های Bower

بسته‌های Bower از طریق فایل ویژه‌ی bower.json مشخص می‌شوند. برای ایجاد این فایل در پنجره Solution Explorer روی پروژه WorkingWithVisualStudio راست کلیک کنید و Add -> New Item را از منوی باز شده انتخاب کنید. سپس قالب مورد نظر Bower Configuration File را از Asp.net Core -> Web -> General  Category انتخاب نمائید؛ مانند تصویر زیر:
 


ویژوال استودیو نام bower.json را برای آن قرار میدهد. برروی ok کلیک می‌کنیم و یک فایل جدید، با محتویات پیشفرض زیر به پروژه اضافه میشود:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {}
}
به علاوه برای فایل Bower.json، تصویر زیر بسته‌های Client Side وابسته به Bower را نشان میدهد. از این قسمت برای اضافه کردن وابستگی‌های برنامه نیز استفاده میشود.


نکته: منبع بسته‌های Bower در لینک http://bower.io/search وجود دارد. شما می‌توانید بسته‌ها مورنظر را در اینجا جستجو و به پروژه اضافه کنید.

بعد از اینکه بسته‌ها نصب شدند، محتویات فایل bower.json به صورت زیر می‌باشد:
{
  "name": "asp.net",
  "private": true,
  "resolutions": {
    "jquery": "3.3.1"
  }
}

در ادامه بسته Bootstrap CSS به پروژه اضافه شده‌است. زمانیکه شما فایل Bower.json را ویرایش می‌کنید، ویژوال استادیو لیستی از نام بسته‌ها و نسخه‌های بسته‌های موجود را نمایش می‌دهد؛ مانند تصویر زیر:


در زمان نوشتن این مطلب، آخرین نسخه‌ی پایدار بسته بوت استرپ، 3،3،7 است. البته اگر در دقت کنید، در اینجا سه گزینه‌ی ارائه شده‌ی توسط ویژوال استودیو وجود دارند: 3.3.7 و 3.3.7^ و 3.3.7~. شماره نسخه می‌تواند در طیف وسیعی از روش‌های مختلف در فایل bower.json مشخص شود. مفیدترین آنها در جدول زیر شرح داده شده‌اند. استفاده از شماره نسخه صریح یک بسته، امن‌ترین راه برای مشخص کردن یک بسته است. این تضمین می‌کند که شما همیشه با همان نسخه کار می‌کنید؛ مگر اینکه عمدا فایل bower.json را برای پاسخ گویی به درخواست‌های دیگری به روز رسانی کنید:
  فرمت    توضیحات 
  3.3.7  بیان شماره مستقیم بسته نصب شده و تطبیق دقیق آن با شمار نسخه ، e.g ، 3.3.7 
  *  با استفاده از یک ستاره به Bower اجازه نصب آخرین نسخه را می‌دهد
3.3.7 =<3.3.7<
پیشوند یک شماره نسخه با < یا =< به Bower اجازه می‌دهد تا هر نسخه از بسته‌ای که بزرگتر یا بزرگتر مساوی آن نسخه‌ی معین است، نصب شود 
3.3.7 =>3.3.7
پیشوند یک شماره نسخه با > یا => به Bower اجازه می‌دهد تا هر نسخه از بسته‌ای را که کوچکتر یا کوچکتر و مساوی نسخه‌ی معین است، نصب شود 
  3.3.7~  پیشوند یک شماره نسخه با یک tilde (با کاراکتر ~ ) به نسخه‌هایی که دو شماره اولیه آن‌ها مشابه باشند، اجازه نصب میدهد؛ حتی اگر شماره آخر آن نسخه متفاوت باشد. مانند نسخه‌های 3.3.9 و 3.3.8 و اجازه نصب نسخه 3.4.0 را نمیدهد؛ چون شماره دوم آن متفاوت است.
  3.3.7^  پیشوند یک شماره نسخه با یک قلم (کاراکتر ^) به نسخه‌هایی که شماره اول آنها مشابه باشند، اجازه نصب می‌دهد؛ حتی اگر شماره دوم آن‌ها متفاوت باشد. مانند نسخه‌های 3.3.1 و 3.4.1 و 3.5.1 اما نسخه 4.0.0 اجازه نصب ندارد 
 
نکته: برای مثال در این کتاب، من فایل bower.json را مستقیما ایجاد و ویرایش می‌کنم. ویرایش این فایل ساده است و به شما کمک می‌کند تا اطمینان حاصل کنید که نتایج مورد انتظار را در صورت پیگیری به همراه داشته باشد. همچنین ویژوال استودیو ابزار گرافیکی را نیز برای مدیریت بسته‌های bower فراهم می‌کند. شما می‌توانید با کلیک راست بر روی فایل bower.json و انتخاب Manage Bower packages به منوی باز شده دسترسی داشته باشید. ویژوال استادیو فایلهای bower.json را برای تغییرات نظارت می‌کند و به صورت خودکار از ابزار Bower برای دانلود و نصب بسته‌ها استفاده می‌کند. هنگامیکه شما تغییرات فایل را ذخیره می‌کنید، ویژوال استودیو بسته‌ی BootStrap را دانلود می‌کند و در پوشه‌ی wwwroot/lib ذخیره می‌کند.


مانند Nuget نیز Bower وابستگی‌های مرتبط با بسته‌های اضافه شده‌ی به یک پروژه را مدیریت می‌کند. BootStrap برای دسترسی به برخی از ویژگی‌های پیشرفته، به JQuery که یک کتابخانه‌ی جاوا اسکریپتی است، تکیه می‌کند. به همین دلیل است که دو بسته را در شکل فوق نشان داده است. شما می‌توانید لیست بسته‌ها و وابستگی‌های آنها را به صورت باز شده در بخش مورد نظر در Solution Explorer مشاهده کنید.

به روزرسانی بسته Bootstrap

در ادامه کتاب، من از نسخه قبلی Bootstrap CSS framework استفاده می‌کنم. هنگامی که دارم این را می‌نویسم، تیم Bootstrap در حال توسعه‌ی نسخه‌ی 4 bootStrap است و چندین بار منتشر شده‌است. این نسخه‌ها به عنوان "آلفا" برچسب گذاری شده‌اند، اما کیفیت آن‌ها بالا است و برای استفاده در نمونه‌های این کتاب به اندازه کافی پایدار است. با توجه به انتخاب نوشتن این کتاب با استفاده از Bootstrap 3 و نسخه پیش از نسخه بوت استرپ 4 و به زودی بایگانی شدن آن، تصمیم گرفتم از نسخه جدید استفاده کنم؛ حتی اگر برخی از نام‌های کلاس‌ها که برای شیوه نامه‌های عناصر HTML استفاده می‌شوند، احتمالا قبل از انتشار نهایی تغییر یابند. این مورد به این معنا است که شما باید همان نسخه از Bootstrap را که برای گرفتن نتایج موردنظر از خروجی نیاز دارید، استفاده  کنید.

برای به روزرسانی بسته Bootstrap، شماره نسخه را در فایل bower.json تغییر دهید. مانند کد زیر:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "4.0.0-alpha.6"
  }
}
زمانی که شما تغییرات فایل bower.json را ذخیره می‌کنید، ویژوال استودیو نسخه جدید BootStrap را دانلود می‌کند.
معرفی توسعه و کامپایل مداوم

توسعه نرم افزار وب اغلب می‌تواند یک فرآیند تکراری باشد، جایی که تغییرات کوچکی را به ویووها یا کلاس‌ها می‌دهید و برنامه را اجرا می‌کنید تا اثرات آن را آزمایش کنید. MVC و ویژوال استودیو همکاری می‌کنند تا از این رویکرد مداوم استفاده کنند تا تغییرات را سریع‌تر و آسان‌تر ببینید.

 اعمال تغییرات در Razor Views  
در زمان توسعه، تغییراتی که به Razor View اعمال میشوند، به محض رسیدن درخواست‌های HTTP، از مرورگر دریافت میشوند. برای اینکه ببینید چطور کار می‌کند، برنامه را با انتخاب گزینه Start Debugging از منوی Debug اجرا کنید و هنگامیکه یک برگه‌ی مرورگر باز شد و اطلاعات نمایش داده شد، تغییراتی را که در زیر نمایش میدهم در فایل Index.cshtml اعمال کنید.

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
            <tr>
                <td>@p.Name</td>
                <td>@($"{p.Price:C2}")</td>
            </tr>}
        </tbody>
    </table>
</body>
</html>
تغییرات را در فایل Index ذخیره می‌کنیم و صفحه وب را با استفاده از دکمه browser Reload مجددا بارگذاری می‌کنیم. تغییرات در View (یک عنوان و فرمت را برای مشخصه Price به عنوان ارز وارد کردیم) در مرورگر هم اعمال شده است؛ مانند تصویر زیر:



اعمال تغییرات در کلاس‌های #C

برای کلاس‌های #C، از جمله کنترلرها و مدل‌ها، دو رویکرد موجود را که از طریق آیتم‌های مختلف در منوی Debug انتخاب می‌شوند، شرح می‌دهم:

Start Without Debugging
تغییرات در کلاس‌ها در پروژه به صورت خودکار زمانیکه یک درخواست HTTP دریافت می‌شود، برای مشاهده‌ی یک تجربه‌ی توسعه‌ی پویا، کامپایل می‌شوند. در این حالت برنامه بدون امکانات دیباگ و اشکال‌زادیی اجرا می‌شود.

Start Debugging
به شما اجزا میدهد صریح تغییرات را کامپایل کنید و برنامه را اجرا کنید ، بررسی مشکلات هم در زمان اجرا پروژه انجام میگیرد.به شما اجرا بررسی و تجزیه و تحلیل هر گونه مشکل در کد را میدهد.

 
کامپایل خودکار کلاس ها

در طول توسعه عادی، این چرخه کامپایل سریع به شما اجازه می‌دهد تا فورا تاثیر تغییرات خود را ببینید؛ حالا می‌تواند این تغییر اضافه نمودن یک اکشن جدید و یا ویرایش نمایش اطلاعات یک Model باشد. برای ارائه‌ی این نوع از توسعه، ویژوال استودیو به محض رسیدن درخواست HTTP از مرورگر، تغییرات را دریافت و کلاس‌ها را به صورت خودکار کامپایل می‌کند. برای دیدن اینکه چگونه کار می‌کند، گزینه Start Without Debugging را از منوی Debug در ویژوال استودیو انتخاب کنید. هنگامیکه مرورگر داده‌های برنامه را نمایش می‌دهد، تغییرات زیر را در فایل Home controller ایجاد کنید:
namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index() => View(SimpleRepository.SharedRepository.Products
            .Where(p => p.Price < 50));
    }
}
در این تغییرات با استفاده از LINQ محصولات را فیلتر می‌کنیم به طوری که فقط کالاهایی را که price آنها کمتر از 50 است، نمایش داده می‌شوند. تغییرات را در فایل کلاس controller ذخیره کنید و پنجره مرورگر را دوباره باز کنید. بدون توقف و یا راه اندازی مجدد برنامه در ویژوال استادیو، درخواست HTTP از مرورگر باعث عملیات کامپایل میشود و برنامه با استفاده از تغییرات کلاس Controller، دوباره راه اندازی خواهد شد و نتیجه را در زیر میتوانید ببینیدکه محصولات Kayak را از جدول حذف می‌کند.

ویژگی کامپایل خودکار زمانی مفید است که همه چیز برنامه ریزی شود. مشکل این است که خطاهای کامپایلر، در زمان اجرا و در مرورگر بجای ویژوال استودیو نمایش داده می‌شوند. در این حالت زمانیکه یک مشکل وجود دارد، سخت می‌توان متوجه شد که چه مشکلی ایجاد شده است. برای مثال، کدهای زیر اضافه کردن یک مقدار Null را به مجموعه نمایش میدهد. 
namespace WorkingWithVisualStudio.Models
{
    public class SimpleRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products = new Dictionary<string, Product>();
        public static SimpleRepository SharedRepository => sharedRepository;

        public SimpleRepository()
        {
            var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } };
            foreach (var p in initialItems)
            {
                AddProduct(p);
            }
            products.Add("Error", null);
        }
        public IEnumerable<Product> Products => products.Values;
        public void AddProduct(Product p) => products.Add(p.Name, p);

    }
}
مشکلی مانند ورودی Null تا زمانیکه برنامه اجرا نشود، نمایش داده نمیشود. بارگذاری صفحه مرورگر باعث می‌شود کلاس SimpleRepository به صورت خودکار کامپایل شود و برنامه دوباره راه اندازی خواهد شد. هنگامیکه MVC نمونه‌ای از کلاس Controller را برای پردازش درخواست HTTP از مرورگر ایجاد می‌کند، سازنده HomeController کلاس SimpleRepository را ایجاد خواهد کرد که به نوبه خود سعی می‌کند مقدار Null اضافه شده در لیست را پردازش کند. مقدار Null باعث بروز یک مشکل می‌شود، اما مشخص نیست مشکل چیست. مرورگر یک پیام مفید را نمایش نمی‌دهد.
توانایی نمایش صفحات خطاها  
زمانیکه مشکلی در پنجره‌ی مرورگر ایجاد شد، می‌توان یک راهنمای با اطلاعات مفید را نمایش داد. این قابلیت را می‌توانید با فعال کردن نمایش صفحات انجام داد که باید در تنظیمات کلاس Startup تغییرات زیر را اعمال کنید.

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDeveloperExceptionPage();
            }
        }
    }
}
اگر پنجره مرورگر را دوباره بارگذاری کنید، فرآیند کامپایل خودکار به صورت خودکار برنامه را بازسازی می‌کند و یک پیام خطای مفید‌تری را در مرورگر ایجاد می‌کند. مانند تصویر زیر:

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

 
استفاده از Debugger

ویژوال استادیو از اجرای یک برنامه MVC با استفاده از Debugger نیز پشتیبانی می‌کند که اجازه می‌دهد برنامه برای بررسی وضعیت نرم افزار و دنبال کردن درخواستی که به برنامه ارسال میشود، متوقف و از این طریق، پیگیری شود. این مورد نیاز به یک سبک متفاوت از توسعه را دارد. زیرا تغییراتی را در کلاس‌های #C میدهیم، تا زمانیکه برنامه مجددا راه اندازی نشود، اعمال نمی‌شوند ( هرچند تغییرات Razor View هنوز هم به صورت خودکار اعمال میشوند). این سبک توسعه به همراه استفاده‌ی از ویژگی کامپایل خودکار نیست؛ اما Debugger ویژوال استودیو عالی است و می‌تواند بینش عمیق‌تری را در مورد نحوه‌ی کارکرد برنامه داشته باشد. برای اجرای برنامه با استفاده Debugger، در ویژوال استودیو از منوی Debug گزینه‌ی Start Debugging را انتخاب کنید. ویژوال استودیو کلاسهای #C در پروژه را قبل از اجرای برنامه کامپایل می‌کند. اما شما همچنان می‌توانید با استفاده از موارد موجود در منوی Build، کد خود را به صورت دستی نیز کامپایل کنید.

مثال فوق حاوی مقدار NULL است که سبب می‌شود یک NullReferenceException توسط کلاس SimpleRepository پرتاب شود. این حالت برنامه را قطع و کنترل اجرا را به توسعه دهنده منتقل می‌کند؛ همانطور که در شکل زیر نشان داده شده است



نکته: اگر Debugger خطا را نفهمد، گزینه‌ی Windows ➤ exception settings را از منوی Debugger ویژوال استودیو انتخاب کنید و اطمینان حاصل کنید که تمام انواع خطاهای در لیست خطاهای زمان اجرای زبان مشترک، تایید شده‌است.
تنظیم یک Break-point

Debugger عامل اصلی خطا را نمایش نمی‌دهد؛ تنها مکان آن‌را آشکار می‌کند. عبارتیکه ویژوال استودیو برجسته می‌کند نشان می‌دهد که این مشکل زمانی رخ می‌دهد که فیلتر کردن اشیاء با استفاده از LINQ انجام شود، اما یک کار کوچک لازم است تا از جزئیات کاسته شود و به علت اصلی برسد.
Breakpoint عبارتی است که به Debugger می‌گوید تا برنامه را متوقف کند و کنترل دستی برنامه را به برنامه نویس میدهد. شما می‌توانید وضعیت برنامه را بازبینی کنید و ببینید چه اتفاقی می‌افتد و به صورت اختیاری روند کاری را دوباره ادامه دهید.
برای ایجاد Breakpoint، روی عبارت راست کلیک کنید و در منوی باز شده، گزینه Breakpoint -> Insert Breakpoint را انتخاب کنید.

به عنوان مثال: یک Breakpoint به خط کد AddProduct در کلاس SimpleRepository اعمال کنید. همانطور که در شکل زیر نمایش داده میشود:
 


برنامه را اجرا کنید؛ با استفاده از Debug -> Start Debugging و یا با استفاده از Debug -> Restart برنامه را Restart می‌کنیم. در طی درخواست اولیه HTTP، برنامه اجرا میشود تا به نقطه‌ای که Break Point دارد برسد و در آنجا برنامه متوقف میشود. در این نقطه، شما می‌توانید از آیتم‌های منوی Debug ویژوال استودیو یا کنترل‌ها در بالای پنجره، برای کنترل اجرای برنامه استفاده کنید؛ یا از نمایش‌های مختلف Debugger موجود از طریق Debug -> Windows برای بررسی وضعیت برنامه استفاده می‌کنیم.
مشاهده مقادیر داده در ویرایشگر کد
رایج‌ترین استفاده Break Point، ردیابی مشکلات در کد شماست. قبل از اینکه بتوانید یک مشکل را رفع کنید، باید بدانید چه اتفاقی در حال رخ دادن است و یکی از ویژگیهای مفید ویژوال استودیو این است که توانایی مشاهده و کنترل ارزش متغیرها را درست در ویرایشگر کد، میدهد.
اگر اشاره‌گر ماوس را بر روی پارامتر p به متد AddProduct که توسط Debugger برجسته شده‌است، حرکت دهید، یک فرم ظاهر خواهد شد که ارزش فعلی p را نشان می‌دهد؛ همانطور که در شکل زیر نشان داده شده‌است. من یک نمونه بزرگ شده از محتویات فرم ظاهر شده را نمایش میدهم تا به راحتی بتوانید متن در آن را بخوانید.
 


این مورد ممکن است مؤثر به نظر نرسد، چون شیء داده در یک سازنده همانند BreakPoint تعریف شده‌است. اما این ویژگی‌ها برای هر متغیری کار می‌کند. شما می‌توانید مقادیر را مشاهده کنید تا مقادیر خود و فیلد آنها را ببینید. هر مقدار دارای یک دکمه پین​​ کوچک به سمت راست است. برای زمانیکه کد در حال اجراست، برای نظارت بر مقدار، از آن استفاده کنید.
اشاره‌گر ماوس را بر روی متغیر P قرار دهید و مرجع محصول را پین کنید. مرجع پیوست شده را باز کنید تا بتوانید نام و قیمت را نیز ببینید؛ مانند شکل زیر:
 


گزینه Continue را از منوی Debug در ویژوال استادیو انتخاب کنید تا برنامه ادامه پیدا کند. از آنجا که در برنامه حلقه Foreach وجود دارد، برنامه که دوباره اجرا میشود، وقتی مجددا به BreakPoint رسید، برنامه متوقف میشود. مقادیر پین شده در شکل زیر نشان میدهند که چگونه متغیر P و خواص آن تغییر می‌کنند.
 


استفاده از پنجره متغیرهای محلی ( Local Windows )

یکی از ویژگی‌های مرتبط، پنجره Locals است که با انتخاب گزینه‌ی منوی Debug ➤ Windows ➤ Locals باز می‌شود. پنجره‌ی Locals، مقدار متغیرها را به شکلی مشابه پنل پین شده نمایش می‌دهد، اما در اینجا تمام اشیاء محلی را نسبت به Break Point نمایش می‌دهد؛ همانطور که در شکل زیر نشان داده شده‌است:
 


هربار که Continue را انتخاب می‌کنید، اجرای برنامه ادامه یافته و یک شیء دیگر توسط حلقه foreach پردازش می‌شود.
اگر ادامه دهید، در زمان ویرایش کد، در هر دو پنجره Locals و در مقادیر پنل پین شده، شما مرجع Null را می‌بینید. برای کنترل اجرای برنامه، می‌توانید جریان را از طریق کد خود در دیباگر دنبال کنید و احساس کنید که چه اتفاقی می‌افتد.

برای غیرفعال کردن BreakPoint، روی  عبارت راست کلیک کنید و از منوی باز شده گزینه Delete BreakPoint را انتخاب کنید. برنامه را دوباره راه اندازی کنید و جدول داده ساده‌ای را که در شکل نشان داده شده، مشاهده خواهید کرد.


 
استفاده از Browser Link

ویژگی Browser Link می‌تواند روند توسعه را با قرار دادن یک یا چند مرورگر تحت کنترل ویژوال استودیو، ساده سازی کند. این ویژگی مخصوصا مفید است اگر شما نیاز به دیدن اثر تغییرات را در طیف وسیعی از مرورگرها دارید. قابلیت Browser Link با و یا بدون Debugger کار می‌کند و به این معنا است که می‌توانیم هر فایلی را در پروژه تغییر دهیم و تاثیر تغییر را بدون نیاز به تغییری در مرورگر مشاهده کنیم.

 
راه اندازی BrowserLink

برای فعال کردن Browser Link باید در کلاس Startup، تنظیمات را تغییر دهید. مانند کد زیر:

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}


استفاده از Browser Link

برای درک اینکه Browser Link چگونه کار می‌کند، در ویژوال استودیو گزینه Start Without Debugging را از منوی Debug انتخاب می‌کنیم. ویژوال استودیو برنامه را اجرا می‌کند و یک برگه جدید مرورگر را برای نمایش نتیجه باز می‌کند. با بازبینی HTML ارسال شده به مرورگر، شما خواهید دید که حاوی بخش دیگری مانند این است:
 

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <table>
        <thead>
            <tr><td>Name</td><td>Price</td></tr>
        </thead>
        <tbody>
            <tr><td>Lifejacket</td><td>&#xA3;48.95</td></tr>
            <tr><td>Soccer ball</td><td>&#xA3;19.50</td></tr>
            <tr><td>Corner flag</td><td>&#xA3;34.95</td></tr>
        </tbody>
    </table>
    <!-- Visual Studio Browser Link -->
    <script type="application/json" id="__browserLink_initializationData">
        {"requestId":"968949d8affc47c4a9c6326de21dfa03","requestMappingFromServer":false}
    </script>
    <script type="text/javascript" src="http://localhost:55356/d1a038413c804e178ef009a3be07b262/browserLink" async="async"></script> <!-- End Browser Link -->
</body>
</html>
نکته: اگر قسمت اضافی را نمی‌بینید، لینک مرورگر را از منوی نشان داده شده‌ی در شکل زیر فعال کنید و مرورگر را دوباره بارگذاری کنید.


ویژوال استادیو یک جفت عناصر اسکریپت را به HTML فرستاده شده‌ی به مرورگر اضافه می‌کند که برای بازکردن یک اتصال طولانی مدت HTTP با سرور برنامه کاربردی است؛ تا زمانیکه ویژوال استودیو مجددا برنامه را ری‌استارت کند. کد زیر تغییر در فایل Index و تاثیر استفاده از Browser Link را نشان میدهد.
 

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{p.Price:C2}")</td>
                </tr>}
        </tbody>
    </table>
</body>
</html>
تغییر در فایل View را ذخیره کنید و Refresh Linked Browsers را از منوی Browser Link در نوار ابزار ویژوال استودیو انتخاب کنید؛ همانطور که در شکل نشان داده شده است.  اگر Browser Link کار نمی‌کند، بارگیری مجدد مرورگر یا راه اندازی مجدد ویژوال استادیو را امتحان کنید).
 


کد جاوا اسکریپتی که در HTML ارسال شده به مرورگر جاسازی شده، صحفه را دوباره بارگذاری می‌کند؛ برای دیدن تاثیرات کد اضافه شده که اضافه کردن  یک timestamp ساده است.
 
نکته: عناصر اسکریپت Browser Link فقط در پاسخ‌های موفق جاسازی شده است. به این معنا که اگر یک خطا هنگام کامپایل در هنگام اجرا کردن یک Razor View یا مدیریت یک درخواست ایجاد شود، اتصال بین مرورگر و ویژوال استودیو از بین میرود و شما بعد از حل مشکل باید صفحه را مجدد بارگذاری کنید.

 
استفاده از مرورگرهای متعدد

Browser Link می‌تواند برای نمایش یک برنامه در مرورگرهای متعددی به طور همزمان استفاده شود و می‌تواند زمانی مفید باشد که شما می‌خواهید تفاوت‌های پیاده سازی را بین مرورگرهای مختلف کنترل کنید و یا ببینید که چگونه یک برنامه بر روی ترکیبی از مرورگرهای دسکتاپ و تلفن همراه ارائه می‌شود.
برای انتخاب مرورگرهایی که استفاده می‌شوند، مرورگر را با استفاده از دکمه IIS Express در نوار ابزار ویژوال استودیو، انتخاب کنید؛ همانطور که در شکل زیر نشان داده شده است.
 


ویژوال استودیو لیستی از مرورگرهایی را که در مورد آنها اطلاعاتی دارد، نمایش میدهد. در عکس زیر مرورگرهایی را که من در سیستم خود نصب کرده‌ام، نشان می‌دهد. برخی از آنها با ویندوز مانند Internet Explorer و Edge نصب می‌شوند.

 
ویژوال استادیو معمولا مرورگرهای رایجی را که نصب میشوند، نمایش میدهد. اما شما می‌توانید با استفاده از دکمه‌ی Add، برای اضافه کردن مرورگری که به صورت خودکار لیست نشده نیز استفاده کنید. همچنین می‌توانید ابزار تست شخص ثالث مانند Browser Stack را نیز راه اندازی کنید که مرورگرها را بر روی سرویس‌های ابری میزبان ( cloud-hosted ) و ماشین‌های مجازی اجرا می‌کند.

من سه مرورگر را در شکل انتخاب کردم: Chrome ، Internet Explorer و Edge. با کلیک بر روی دکمه Browse، فعالیت هر سه مرورگر شروع می‌شود و باعث می‌شود URL مثال برنامه را بارگذاری کند؛ همانطور که در شکل نشان داده شده است.
 


با استفاده از منوی Browser Link Dashboard، شما می‌توانید ببینید که چه مرورگرهایی در Browser Link انتخاب شده‌اند. داشبورد آن نشانی اینترنتی نمایش داده شده توسط هر مرورگر را نشان می‌دهد و در اینجا هر مرورگر را می‌توان به صورت جداگانه رفرش کرد.
 


آماده سازی جاوا اسکریپت و CSS برای استقرار

هنگامی که Client-Side بخشی از یک برنامه وب را ایجاد می‌کنید، معمولا تعدادی از فایل‌های جاوا اسکریپت و CSS سفارشی را تهیه می‌کنید که برای تکمیل آن‌ها، از بسته‌های نصب شده‌ی توسط Bower استفاده می‌شود. این فایل‌ها نیاز به پردازش دارند تا آنها را برای تحویل در یک محیط تولید، بهینه سازی کنند تا تعداد درخواستهای HTTP و میزان پهنای باند شبکه مورد نیاز برای ارسال آنها به مشتری، به حداقل برسد. این فرآیند به عنوان بسته بندی شناخته می‌شود.
 

فعال کردن تحویل محتوای استاتیک

ASP.Net Core شامل پشتیبانی از ارائه فایل‌های استاتیک از پوشه wwwroot به مشتریان است. اما این امکان به صورت پیشفرض در زمان ایجاد یک پروژه‌ی خالی جدید فعال نیست و شما باید با قرار دادن عبارتی در فایل StartUp آن را فعال کنید؛ مانند کد زیر:
 

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                app.UseStaticFiles();
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}


اضافه کردن محتوای استاتیک به پروژه

برای نشان دادن فرآیند بسته بندی، من نیاز به اضافه کردن تعدادی محتوای استاتیک به پروژه و یکی کردن آن‌ها با برنامه‌ی نمونه را دارم. برای این منظور ابتدا یک پوشه‌ی جدید را به نام wwwroot/css ایجاد کنید که محل متداولی برای فایل‌های سفارشی CSS است. من فایلی را به نام First.css با استفاده از قالب آیتم Style Sheet اضافه کردم؛ همانطور که در شکل زیر نشان داده شده است. قالب Style Sheet در مسیر Asp.Net Core -> Web -> Content Section وجود دارد.
 


فایل First.Css را ویرایش کنید و محتوای زیر را در آن قرار دهید.
h3 {
}

table, td {
    border: 2px solid black;
    border-collapse: collapse;
    padding: 5px;
}
من این روند را تکرار کردم و یک فایل دیگر را نیز به نام second.css در پوشه wwwroot/css ایجاد کردم.

فایل‌های جاوا اسکریپت معمولا در پوشه wwwroot/js قرار میگیرند. من این پوشه را ایجاد کردم. فایل‌های جاوا اسکریپت را می‌توانید در مسیر Asp.Net Core -> Web -> Script انتخاب کنید. همانطور که در شکل زیر نشان داده شده است.


من کد جاوا اسکریپتی ساده زیر را به این فایل جدید اضافه کردم؛ همانطور که در لیست نشان داده شده است.
document.addEventListener("DOMContentLoaded", function ()
{
    var element = document.createElement("p");
    element.textContent = "This is the element from the third.js file";
    document.querySelector("body").appendChild(element);
});

من به بیش از یک فایل جاوا اسکریپت نیاز دارم. بنابراین فایل دیگری را به نام fourth.js نیز در پوشه wwwroot ایجاد می‌کنم و محتوای زیر را در آن قرار میدهم.
document.addEventListener("DOMContentLoaded", function ()
{
    var element = document.createElement("p");
    element.textContent = "This is the element from the fourth.js file";
    document.querySelector("body").appendChild(element);
});


به روز رسانی View

گام نهایی، به روز رسانی فایل Index.cshtml برای استفاده از Css و فایل جاوا اسکریپت است. کد‌های آن در زیر نشان داده شده است:
@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
    <link rel="stylesheet" href="css/first.css" />
    <link rel="stylesheet" href="css/second.css" />
    <script src="js/third.js"></script>
    <script src="js/fourth.js"></script>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{p.Price:C2}")</td>
                </tr>}
        </tbody>
    </table>
</body>
</html>
اگر برنامه کاربردی را اجرا کنید، محتویات نشان داده شده‌ی در شکل زیر را مشاهده خواهید کرد. محتوای موجود توسط شیوه نامه‌های CSS شبیه سازی شده است و کد جاوا اسکریپتی جدیدی را اضافه کرده است.


یکی کردن فایل‌های سمت کلاینت در برنامه‌های MVC

در حال حاضر چهار فایل استاتیک وجود دارند و مرورگر باید چهار درخواست را برای دریافت فایل‌های استاتیک ایجاد کند و هر یک از این فایل‌ها نیازمند پهنای باند بیشتری است که باید به مشتری تحویل داده شود؛ زیرا آنها حاوی فضای سفید و نام متغیرها هستند که برای توسعه دهنده‌ها معنا دار هستند؛ اما برای مرورگرها اهمیتی ندارند.
ترکیب فایل‌هایی هم نوع، تلفیق نامیده می‌شود و در آن کار ساختن فایل‌ها به صورتی کوچکتر انجام می‌شود. هر دوی این کارها در برنامه Asp.Net Core MVC توسط  Bundler & Minifier مخصوص ویژوال استودیو انجام میشود.


نصب افزونه‌های ویژوال استودیو

اولین قدم برای نصب افزونه، انتخاب از منوی Tools -> Extensions and Update و کلیک بر روی مجموعه Online است تا افزونه‌های ویژوال استودیو را در مجموعه نمایش بدهد. نام افزونه را در جعبه جستجوی در گوشه‌ی سمت راست بالای پنجره وارد کنید؛ همانطور که در شکل زیر نشان داده شده است. محل نصب افزونه را مشخص می‌کنیم و بر روی دانلود کلیک می‌کنیم تا آن را به ویژوال استودیو اضافه کند. ویژوال استودیو را مجدد راه اندازی کنید تا فرآیند نصب تکمیل شود.


دسته بندی و یکی کردن فایل‌ها

پس از نصب افزونه، ویژوال استودیو را مجددا راه اندازی کنید و پروژه نمونه را باز کنید. با افزودن افزونه، می‌توانید چندین فایل هم نوع را در Solution Explorer انتخاب کنید. آنها را با یکدیگر ترکیب کرده و محتویات آنها را کوچکتر کنید. به عنوان مثال فایل‌های First.css و Second.css را در Solution Explorer را انتخاب و کلیک راست کرده و سپس Bundler & Minifier -> Bundle and Minify Files را از منوی باز شده انتخاب کنید . همانطور که در شکل زیر نشان داده شده است.
 


فایل خروجی را با عنوان bundle.css ذخیره کنید. در Solution Explorer یک بسته جدید ایجاد میشود. اگر شما این فایل را باز کنید، خواهید دید که محتویات هر دو فایل CSS جداگانه ترکیب شده‌اند و تمام فضای سفید آن‌ها حذف شده‌است. البته شما نمی‌خواهید به طور مستقیم با این فایل کار کنید؛ اما این فایل کوچکتر است و فقط یک اتصال HTTP را برای ارائه CSS styles به مشتری نیاز دارد.

مراحل قبل را برای فایل‌های third.js و fourth.js تکرار کنید تا فایل‌های جدید bundle.js و bundle.min.js در پوشه wwwroot ایجاد شوند.

احتیاط: اطمینان حاصل کنید که فایل‌ها را به ترتیبی که توسط مرورگر بارگیری می‌شوند، انتخاب کنید تا ترتیب دستورات Style‌ها یا دستورات کد را در فایل‌های خروجی حفظ کنید. به عنوان مثال دقت کنید که فایل third.js قبل از فایل fourth.js انتخاب شده باشد تا مطمئن باشید دستورات به ترتیب و به درستی اجرا می‌شوند.
کد زیر، عناصر پیوند فایل‌های جداگانه‌ای را که باید در فایل Index.cshtml قرار گیرند، نمایش میدهد:
@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
    <link rel="stylesheet" href="css/bundle.min.css" />
    <script src="js/bundle.min.js"></script>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{p.Price:C2}")</td>
                </tr>}
        </tbody>
    </table>
</body>
</html>
اگر برنامه را اجرا کنید، هیچ تغییر بصری وجود نخواهد داشت؛ اما فایلهای آن یکی شده‌اند و با حجم کمتر و با تعداد اتصالات کمتری از سرور دریافت می‌شوند.

همان زمان که عملیات جمع آوری و یکی کردن را انجام می‌دهید، رکورد عملیات انجام شده را در فایلی به نام bundleconfig.json در پوشه‌ی wwwroot پروژه نگهداری می‌کند. در اینجا یک نمونه از فایل تولیدی را مشاهده می‌کنید:
[
  {
    "outputFileName": "Views/wwwroot/css/bundle.css",
    "inputFiles": [
      "Views/wwwroot/css/First.css",
      "Views/wwwroot/css/second.css"
    ]
  },
  {
    "outputFileName": "Views/wwwroot/js/bundle.js",
    "inputFiles": [
      "Views/wwwroot/js/fourth.js",
      "Views/wwwroot/js/third.js"
    ]
  }
]
 

خلاصه
در این بخش من توضیحاتی را در مورد ویژگی‌هایی که ویژوال استودیو برای طراحی برنامه‌های وب به توسعه دهنده‌ها ارائه میدهد، شرح دادم که شامل کامپایل خودکار کلاس‌ها، Browser Link و یکی کردن فایل‌های سمت کلاینت ( bundling and minification ) بود. 
مطالب
مدیریت ساده چهار عمل اصلی تکراری داده در صفحات
در بیشتر مواردی که در قسمت ادمین سایت می‌گذرد، مثل ریاضیات که شامل چهار عمل اصلی است، افزودن، ویرایش، حذف، و انتخاب نیز، همین وظیفه را در عملیات‌های کار با داده، انجام می‌دهند. البته در این بین اعتبارسنجی را نباید فراموش کرد:
0. Validation
1. Insert
2. Edit
3. Delete
4. Select
برای مثال میتوان از یک کلاس مشترک برای این کار که از Page ارث‌بری کرده است، استفاده و بصورت یک کلاس abstract وظیفه پیاده سازی را به صفحه مورد نظر محول کرد. برای  نمونه کلاس ساده زیر را در نظر بگیرید:
public abstract class PageStateMachine : Page
    {
      ...
        protected abstract ... Save();
        protected abstract ... Select();
        protected abstract ... Delete();
        protected abstract ... Validation();
      ...
و برای روال گردانی هم مطمئنا این متد‌ها باید به ترتیب یکسان در همه صفحات مورد استفاده قرار گیرد، پس لازم است حالت‌های صفحه به این کلاس پاس شود تا بر اساس حالت صفحه، متد‌ها بترتیب صدا زده شوند:
    public enum PageState
    {
        Save = 0,
        Select = 2,
        Delete = 8,
        Cancel = 16
    }
 که در همین کلاس می‌توان متدی برای این کار در نظر گرفت:
public void Go(PageState pageState)
        {
            switch (pageState)
            {
                case PageState.Save:
                    {
                        var error = Validation();
                        Message = error.HasError ? error.Message : Save().Message;
                        if (!error.HasError)
                        {
                            ClearControls();
                            RefreshList();
                        }

                        break;
                    }
                case PageState.Select:
                    {
                        Message = Select().Message;
                        break;
                    }
                    break;
                case PageState.Delete:
                    {
                        Message = Delete().Message;
                        ClearControls();
                        RefreshList();
                        break;
                    }
                case PageState.Cancel:
                    {
                        Message = string.Empty;
                        ClearControls();
                        break;
                    }
            }
        }
ادامه دارد
مطالب
الگوی Chain Of Responsibility در #C
در این مطلب قصد داریم الگوی Chain Of Responsibility را تحت یک مثال کاربردی در زبان سی شارپ، با هم بررسی کنیم. اجازه دهید با یک مثال کار را شروع کنیم. سناریوی گرفتن وام دانشجویی را در نظر بگیرید؛ به این صورت که دانشجو وارد سامانه شده، رمز خود یا شماره دانشجویی خود را زده و درخواست خود را ثبت می‌کند و پاسخی را از سیستم دریافت میکند. فرض کنید سلسله مراتب سیستم به این صورت باشد که ابتدا بررسی میکند که دانشجو فعال باشد. مرحله بعد رمز دانشجو صحیح باشد. مرحله بعد اینکه مقدار وامی که قبلا گرفته است، از حداکثر وام ثبت شده در سیستم بیشتر نباشد و مرحله آخر هم ثبت درخواست وام. سناریوی ذکر شده صرفا جهت کار با الگوی مورد نظر در نظر گرفته شده است؛ چون قطعا روند کار بر پایه چارچوب طولانی‌تری پیش می‌رود.

اولین راه حلی که به ذهن میرسد
  1. if else
  2. switch case

بله مورد اولی که به ذهن خود من رسید، استفاده از if else هست. شاید خروجی مناسبی را از نظر کدنویسی داشته باشد؛ ولی خوانایی مناسبی را ندارد. حالا چطور اثبات کنیم خوانایی و قابلیت توسعه‌ی پایینی را دارد؟
فرض کنید شما برنامه را نوشته‌اید و تحویل مدیر خود داده‌اید. بعد از دو ماه به شما گفته می‌شود که مراحل 1 و 2 را جابجا کنید و یا یک step را اضافه کنید که بعد از مرحله دو (بررسی رمز) است تا یک منطق جدید را دنبال کند. اینجاست که دچار دردسر و اتلاف زمان میشویم؛ چون باید بیزینس را مجددا review کنیم و بدتر از آن کدها را هم تغییر دهیم که امکان رخ دادن خطا به شدت بالا می‌رود.

هدف از این الگو
  1. انجام کار در چند مرحله
  2. حذف پیچیدگی‌های پیاده سازی

حالا بیایید با هم با الگوی Chain Of Responsibility، این مثال را پیاده سازی کنیم. منطق کار به صورت زیر است:


به این شکل که مراحل بصورت سلسله مراتبی، تحت successor‌های یکدیگر پیش می‌روند. اگر بخواهم successor را در این مثال توضیح دهم من به‌عنوان دانشجو (successor اول) بعد از چک شدن موارد مربوط به دانشجو، درخواست به سمت مسئول مربوطه رفته (successor دوم ) و الی اخر.

پیاده سازی
ابتدا باید یک مدل را برای دانشجویان یا مشتریان بسازیم:
public class Customer
{
    public string Password { get; set; }
    public string Stno { get; set; }
    public int value { get; set; }
    public bool Active { get; set; }
}
همانطور که از دیاگرام مشخص است، ما یک requestContext لازم داریم که در سلسله مراتب بیزینس جابجا شده و منطق‌های ما بر روی این کلاس انجام میشود:
public class RequestContext
{
    public int VamValue { get; set; }
    public Customer student { get; set; }
}
که شامل یک مقدار وام (مقدار حداکثر وام درخواستی برای هر دانشجو) ،ضمن اینکه فرض کنید value در Customer، مقدار حداکثر وام در نظر گرفته شده‌ی در سیستم، برای دانشجو است. حال که ما یک درخواست را ایجاد میکنیم، باید یک کلاس response هم داشته باشیم:
public class ResponseContext
{
    public string Response { get; set; }
}

حال طبق شکل بالا باید handler خود را که گرفتن وام است، پیاده سازی نماییم:
public abstract class GetVam
{
    protected readonly GetVam successor;
    
    public GetVam(GetVam _getVam)
    {
        this.successor = _getVam;
    }

    public abstract ResponseContext execute(RequestContext requestContext);
}

حالا باید مراحل چندگانه‌‌ای را که عرض کردم، بصورت کلاس پیاده سازی نماییم:
1-چک کردن فعال بودن دانشجو :
public class CheckUseractive : GetVam
{
    public CheckUseractive(GetVam _getVam) : base(_getVam)
    {
    }

    public override ResponseContext execute(RequestContext requestContext)
    {
        if (requestContext.student.Active == true)
        {
            return successor.execute(requestContext);
        }

        else
        {

            return new ResponseContext
            {
                Response = "student is inactive"
            };
        }
    }
}

2-بررسی رمز کاربر :

public class ChechPassword : GetVam
{
    public ChechPassword(GetVam _getVam) : base(_getVam)
    {
    }

    public override ResponseContext execute(RequestContext requestContext)
    {
        if (requestContext.student.Password == "123")
        {
            return successor.execute(requestContext);
        }
        else
        {
            return new ResponseContext
            {
                Response = "invalid pass",
            };
        }
    }
}

3-بررسی میزان بدهکاری دانشجو :

public class ChechUserBedehkar : GetVam
{
    public ChechUserBedehkar(GetVam _getVam) : base(_getVam)
    {
    }

    public override ResponseContext execute(RequestContext requestContext)
    {
        if (requestContext.student.value < requestContext.VamValue)
        {
            return successor.execute(requestContext);
        }
        else
        {
            return new ResponseContext
            {
                Response = "you are dont permission"
            };
        }
    }
}

4-و مرحله آخر که در صورتیکه تمامی مراحل قبلی پاس شوند چک کردن مقدار وامی است که به دانشجو باید داده شود :

public class AssignVam : GetVam
{
    public AssignVam(GetVam _getVam) : base(_getVam)
    {
    }

    public override ResponseContext execute(RequestContext requestContext)
    {
        return new ResponseContext
        {
            Response = "value of vam: " + (requestContext.VamValue - requestContext.student.value).ToString();
        };
    }
}
که مابه التفاوت مقدار وام صندوق و مقدار وام گرفته شده دانشجو را به‌عنوان وام، به دانشجو برمی‌گردانیم.

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

partial class Program
{
    static void Main(string[] args)
    {
        Customer customer = new Customer()
        {
            Active = true,
            Password = "123",
            Stno = "111",
            value = 2000

        };

        RequestContext requestContext = new RequestContext()
        {

            student = customer,
            VamValue = 3000,
        };

        var GetVam = new CheckUseractive(new ChechPassword(new ChechUserBedehkar(new AssignVam(null))));
        var res = GetVam.execute(requestContext);
        Console.Write(res.Response);
        Console.ReadKey();
    }
}
خروجی:

حال اگر به نحوه فراخوانی دقت کنید، دقیقا سلسله مراتب، تحت کنترل ما است و در صورت تغییر و یا جابجایی stepهای برنامه، به سادگی قابل توسعه است.
مطالب
یکسان سازی "ی" و "ک" دریافتی در حین استفاده از WCF RIA Services

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

راه حل یکسان سازی هم شاید به نظر این باشد: رخداد فشرده شدن کلید را کنترل کنید و سپس جایگزینی را انجام دهید (مثلا ی عربی را با ی فارسی جایگزین کنید). این روش چند ایراد دارد:
الف) Silverlight به دلایل امنیتی اصلا چنین اجازه‌ای را به شما نمی‌دهد! (تا نتوان کلیدی را جعل کرد)
ب) همیشه با یک TextBox ساده سر و کار نداریم. کنترل‌های دیگری هم هستند که امکان ورود اطلاعات در آن‌ها وجود دارد و آن وقت باید برای تمام آن‌ها کد نوشت. ظاهر کدهای برنامه در این حالت در حجم بالا، اصلا جالب نخواهد بود و ضمنا ممکن است یک یا چند مورد فراموش شوند.

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

در مورد ‌مقدمات WCF RIA Services که درSilverlight و ASP.NET کاربرد دارد می‌توانید به این مطلب مراجعه کنید: +

جهت تکمیل این بحث متدی تهیه شده که کار یکسان سازی ی و ک دریافتی از کاربر را حین ثبت توسط امکانات WCF RIA Services انجام می‌دهد (دقیقا پیش از فراخوانی متد SubmitChanges باید بکارگرفته شود):


namespace SilverlightTests.RiaYeKe
{
public static class PersianHelper
{
public static string ApplyUnifiedYeKe(this string data)
{
if (string.IsNullOrEmpty(data)) return data;
return data.Replace("ی", "ی").Replace("ک", "ک");
}
}
}

using System.Linq;
using System.Windows.Controls;
using System.Reflection;
using System.ServiceModel.DomainServices.Client;

namespace SilverlightTests.RiaYeKe
{
public class RIAHelper
{
/// <summary>
/// یک دست سازی ی و ک در عبارات ثبت شده در بانک اطلاعاتی پیش از ورود به آن
/// این متد باید پیش از فراخوانی متد
/// SubmitChanges
/// استفاده شود
/// </summary>
/// <param name="dds"></param>
public static void ApplyCorrectYeKe(DomainDataSource dds)
{
if (dds == null)
return;

if (dds.DataView.TotalItemCount <= 0)
return;

//پیدا کردن موجودیت‌های تغییر کرده
var changedEntities = dds.DomainContext.EntityContainer.GetChanges().Where(
c => c.EntityState == EntityState.Modified ||
c.EntityState == EntityState.New);

foreach (var entity in changedEntities)
{
//یافتن خواص این موجودیت‌ها
var propertyInfos = entity.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance
);

foreach (var propertyInfo in propertyInfos)
{
//اگر این خاصیت رشته‌ای است ی و ک آن را استاندارد کن
if (propertyInfo.PropertyType != typeof (string)) continue;
var propName = propertyInfo.Name;
var val = new PropertyReflector().GetValue(entity, propName);
if (val == null) continue;
new PropertyReflector().SetValue(
entity,
propName,
val.ToString().ApplyUnifiedYeKe());
}
}
}
}
}

توضیحات:
از آنجائیکه حین فراخوانی متد SubmitChanges فقط موجودیت‌های تغییر کرده جهت ثبت ارسال می‌شوند، ابتدا این موارد یافت شده و سپس خواص عمومی تک تک این اشیاء توسط عملیات Reflection بررسی می‌گردند. اگر خاصیت مورد بررسی از نوع رشته‌ای بود، یکبار این یک دست سازی اطلاعات ی و ک دریافتی صورت خواهد گرفت (و از آنجائیکه این تعداد همیشه محدود است عملیات Reflection سربار خاصی نخواهد داشت).
اگر در کدهای خود از DomainDataSource استفاده نمی‌کنید باز هم تفاوتی نمی‌کند. متد ApplyCorrectYeKe را از قسمت DomainContext.EntityContainer به بعد دنبال کنید.
اکنون تنها مورد باقیمانده بحث جستجو است که با اعمال متد ApplyUnifiedYeKe به مقدار ورودی متد جستجوی خود، مشکل حل خواهد شد.

کلاس PropertyReflector بکارگرفته شده هم از اینجا به عاریت گرفته شد.
دریافت کدهای این بحث

نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 11 - بررسی رابطه‌ی Self Referencing

یک نکته‌ی تکمیلی: تاثیر فراخوانی متد AsNoTracking بر روی کوئری‌های خود ارجاعی

همانطور که در مطلب «مباحث تکمیلی مدل‌های خود ارجاع دهنده در EF Code first» هم مشاهده کردید، خود EF، قابلیت تشکیل درخت نهایی خود ارجاع دهنده را دارد و به این ترتیب کوئری گرفتن از نتیجه‌ی آن، بسیار ساده می‌شود. اما ... اگر در این بین، از متد AsNoTracking برای بهینه سازی، کاهش میزان حافظه و حذف پروکسی‌های ردیابی تغییرات EF استفاده شود، دیگر این درخت خودکار، تشکیل نخواهد شد. برای پوشش این حالت می‌توان به صورت زیر عمل کرد:

الف) تشکیل یک کلاس پایه برای تعریف ساده‌تر و مشخص رابطه‌های خود ارجاعی

public abstract class BaseEntity
{
    public int Id { get; set; }
}

public abstract class BaseSelfReferencingEntity<TSelfEntity> : BaseEntity
    where TSelfEntity : BaseEntity
{
    public virtual TSelfEntity? Reply { set; get; }

    public int? ReplyId { get; set; }

    public virtual ICollection<TSelfEntity>? Children { get; set; }
}

که ساختار معرفی شده‌ی در اینجا، با توضیحات موجود در متن، انطباق دارد.

ب) پر کردن درخت نهایی حاصل به صورت دستی:

چون دیگر EF این درخت را برای ما تشکیل نمی‌دهد، اکنون باید خودمان کار تشکیل آن‌را به صورت زیر انجام دهیم:

public static class SelfReferencingExtensions
{ 
    public static List<TEntity> ToSelfReferencingTree<TEntity>(this ICollection<TEntity>? originalList)
        where TEntity : BaseSelfReferencingEntity<TEntity>
    {
        var results = new List<TEntity>();

        if (originalList is null || originalList.Count == 0)
        {
            return results;
        }

        foreach (var rootItem in originalList.Where(x => !x.ReplyId.HasValue))
        {
            results.Add(rootItem);
            AppendChildren(originalList, rootItem);
        }

        return results;
    }

    private static void AppendChildren<TEntity>(ICollection<TEntity> originalList, TEntity parentItem)
        where TEntity : BaseSelfReferencingEntity<TEntity>
    {
        foreach (var kid in originalList.Where(x => x.ReplyId.HasValue && x.ReplyId.Value == parentItem.Id))
        {
            parentItem.Children ??= new List<TEntity>();
            parentItem.Children.Add(kid);
            AppendChildren(originalList, kid);
        }
    }
}

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

پس از این مقدمات، نحوه‌ی استفاده از آن به صورت زیر است:

var comments = await _comments.AsNoTracking()
            .Where(x => x.ParentId == postId)
            .OrderBy(x => x.Id)
            .Take(count)
            .ToListAsync();

var commentsTree = comments.ToSelfReferencingTree();

کوئری نویسی ابتدایی آن، کاملا استاندارد و بدون هیچگونه نکته‌ی خاصی است. ابتدا تمام نظرات یک مطلب (به صورت AsNoTracking) بازگشت داده می‌شوند و سپس متد ToSelfReferencingTree کار اتصالات نهایی درخت پاسخ‌ها را به صورت خودکار انجام می‌دهد.

مطالب
چرخه‌ی حیات یک درخواست در ASP.NET MVC

در Asp.net دو چرخه‌ی حیات مهم وجود دارند که اساس چارچوب MVC را تشکیل می‌دهند :

  1. چرخه‌ی حیات برنامه (Application)؛ از لحظه‌ای که برنامه برای اولین بار اجرا می‌شود و تا لحظه‌ی خاتمه‌ی آن را شامل می‌شود.
  2. چرخه‌ی حیات یک درخواست ( Request مسیری که یک درخواست طی می‌کند، اصطلاحا PipeLine نامیده می‌شود که همان چرخه‌ی حیات یک درخواست نیز هست و از لحظه‌ای که درخواست تحویل asp.net شده، تا زمانیکه درخواست ارسال می‌شود را شامل می‌شود.

تمرکز بنده بیشتر بر روی روند و مسیری است که یک درخواست طی می‌کند و قصد دارم با بهره گیری از کتاب Pro Asp.net Mvc 5 و دیگر منابع، چرخه‌ی حیات درخواست را در برنامه‌های Mvc بررسی کرده و در مقالات آتی ماژولها و هندلرها را بررسی کنم.

در asp.net ، برنامه global فایلهای شامل دو فایل Global.asax , Global.asax.cs است.

فایل Global.asax که هیچ گاه نیاز به ویرایش آن نداریم محتویاتی مانند زیر دارد:

<%@ Application Codebehind="Global.asax.cs" Inherits="YourAppName.MvcApplication" Language="C#" %>
و صرفا فایل code behind   مرتبط را برای asp.net مشخص می‌کند. این نوع مشخص سازی را از وب فرمها به یاد داریم.

در این مقاله منظور از فایل global فایل  Global.asax.cs است که مشتق شده از کلاس System.Web.HttpApplication است:

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            ...//
        }
    }
به صورت پیش فرض کدهای بالا ایجاد شده و کلاس MvcApplication شامل متد Application_Start است که نقطه‌ی شروع چرخه‌ی حیات برنامه را مشخص می‌کند. اما متد دیگری به نام Application_End() نیز وج ود دارد که در زمان خاتمه‌ی برنامه فراخوانی خواهد شد و فرصتی را برای آزاد سازی منابع اشغال شده توسط برنامه فراهم می‌آورد .

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

  BeginRequest : به عنوان اولین رویداد، به محض وصول یک درخواست جدید رخ خواهد داد. 

  AuthenticateRequest ,PostAuthenticateRequest : رویداد AuthenticateRequest برای شناسایی کاربر ارسال کننده درخواست، کاربرد دارد و پس از پردازش کلیه‌ی توابع، رویداد PostAuthenticateRequest صدا زده می‌شود.

  AuthorizeRequest :به‌هنگام صدور مجوزهای یک درخواست رخ می‌دهد و مشابه رویداد بالا پس از پردازش کلیه‌ی توابع، رویداد PostAuthorizeRequest صدا زده خواهد شد.  

ResolveRequestCache : پس از صدور مجوزهای یک درخواست در رویداد authorization  زمانیکه ماژولهای کش می‌خواهند اطلاعاتی را از کش سرور مطالبه کنند، رخ می‌دهد و به مانند دو رخداد قبلی، PostResolveRequestCache نیز پس از اتمام پردازش توابع رویداد رخ می‌دهد.

  MapRequestHandler : زمانی که Asp.net می‌خواهد هندلری را برای پاسخگویی به درخواست واصله انتخاب کند رخ می‌دهد و PostMapRequestHandler نیز پس از این انتخاب، تریگر می‌شود.  

AcquireRequestState : جهت بدست آوردن داده‌هایی نظیر سشن و ... مرتبط با درخواست جاری کاربرد داشته و PostAcquireRequestState نیز پس از پردازش توابع رویداد رخ خواهد داد.

  PreRequestHandlerExecute : بالافاصله قبل و همچنین بلافاصله بعد از این که یک هندلر بخواهد درخواستی را پردازش کند، رخ می‌دهد. PostRequestHandlerExecute نیز همانند دیگر رویدادهای گذشته، پس از اتمام پردازش توابع، این رویداد رخ خواهد داد. 

ReleaseRequestState : زمانی رخ می‌دهد که داده‌های مرتبط با درخواست جاری، در ادامه‌ی روند پردازش درخواست مورد نیاز نباشند و پس از پردازش توابع رویداد، PostReleaseRequestState رخ خواهد داد .

UpdateRequestCache : به جهت اینکه ماژولهای مسئول کش، توانایی به روز رسانی داده‌های خود، برای پاسخگویی به درخواستهای بعدی را داشته باشند، این رویداد رخ می‌دهد.

  LogRequest : قبل از انجام عملیات لاگین برای درخواست جاری رخ می‌دهد و پس از پردازش توابع رویداد نیز PostLogRequest تریگر می‌شود.  

  EndRequest : پس از پایان کار پردازش درخواست جاری و مهیا شدن پاسخ مرتبط جهت ارسال به مرورگر تریگر خواهد شد. 

PreSendRequestHeaders : قبل از ارسال HTTP headers به مرورگر این رویداد رخ خواهد داد.

  PreSendRequestContent : بعد از ارسال شدن هدرها و قبل از ارسال محتوای صفحه به مرورگر رخ می‌دهد. 

Error : هر زمان و در هر مرحله از پردازش درخواست، چنانچه خطایی صورت پذیرد این رویداد رخ خواهد داد.

فریم ورک Asp.net   جهت مدیریت بهتر یک درخواست، در تمام مسیر پردازش، رویدادهای بالا را مهیا کرده است. در ادامه نحوه‌ی هندل کردن رویدادهای چرخه‌ی حیات درخواست را در فایل global توضیح می‌دهم. هر چند که استفاده از این فایل بدین منظور، صرفا برای مدیریت مسائل ابتدایی مناسب بوده و در یک پروژه‌ی بزرگ موجب به هم ریختگی فایل global با کدهای زیاد و خوانایی پایین بوده که قابلیت استفاده مجدد در دیگر پروژه‌ها را نیز ندارد.

Asp.net این مشکل را با معرفی ماژولها که در مقالات آتی توضیح خواهم داد، مرتفع کرده است.

در فایل global هر گاه متدی را با پیشوند Application_ و نام یکی از رویدادهای بالا بنویسید Asp.net آن را به عنوان هندلری برای رویداد مذکور می‌شناسد. به عنوان مثال متدی با نام Application_BeginRequest  متد رویداد BeginRequest می‌باشد.

ابتدا یک پروژه‌ی MVC جدید را به نام SimpleApp ایجاد کرده و فایل global آن را مطابق ذیل تغییر می‌دهیم:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace SimpleApp
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        { 
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
        protected void Application_BeginRequest() 
        {
            RecordEvent("BeginRequest");
        }
        protected void Application_AuthenticateRequest() 
        {
            RecordEvent("AuthenticateRequest");
        }
        protected void Application_PostAuthenticateRequest()
        { 
            RecordEvent("PostAuthenticateRequest");
        }
        private void RecordEvent(string name)
        {
            List<string> eventList = Application["events"] as List<string>;
            if (eventList == null) 
            { 
                Application["events"] = eventList = new List<string>(); 
            } 
            eventList.Add(name);
        }
    }
}

در اینجا متدی به نام RecordEvent را در کدهای ذکر شده مشاهده می‌کنید که نام یک رویداد را دریافت و جهت در دسترس قرار دادن در کل برنامه به خاصیت Application از کلاس HttpApplication نسبت داده و متد مذکور را از سه متد دیگر فراخوانی کرده‌ایم. این متدها در زمان رخ دادن رویدادهای BeginRequest, AuthenticateRequest, PostAuthenticateRequest صدا زده خواهند شد.

حال جهت نمایش اطلاعات رویداد نیاز است تغییراتی مشابه ذیل در کنترلر Home ایجاد نماییم.

using System.Web.Mvc;
namespace SimpleApp.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View(HttpContext.Application["events"]);
        }

    }
}

ویوی مرتبط با اکشن متد index را مطابق کدهای ذیل بازنویسی می‌کنیم:

@model List<string>
@{
    ViewBag.Title = "Events List";
 
}
<h5>Events</h5>
<table>
    @foreach (string eventName in Model)
    {    
        <tr>
            <td>@eventName</td>
        </tr>      
    }

</table>
خروجی آن مطابق ذیل خواهد بود :

در این مقاله سعی کردیم ابتدا چرخه‌ی حیات یک Request را فرا گرفته و سپس از طریق فایل global و توسط متدهایی با پیشوند Application_ +نام رویداد (اصطلاحا متدهای ویژه نامیده می‌شوند) چرخه حیات یک درخواست را مدیریت کنیم.
نظرات مطالب
Blazor 5x - قسمت چهارم - مبانی Blazor - بخش 1 - Data Binding
یک نکته‌ی تکمیلی: روش تعریف data binding دو طرفه در کامپوننت‌ها
در مطلب جاری، binding دو طرفه بررسی شد؛ که نکته‌ی مورد بحث آن، به ویژگی‌های استاندارد HTML مانند ویژگی value یک input استاندارد اختصاص داشت. اما اگر بخواهیم در کامپوننت‌های سفارشی خود، این binding دو طرفه را تعریف کنیم تا قابل اعمال به خواص و ویژگی‌های #C باشد (مانند bind-ProprtyName@)، روش کار به نحو دیگری است. نمونه‌ی آن کامپوننت استاندارد InputText خود Blazor است که در اینجا هم دارای bind-Value@ است؛ اما این Value (شروع شده‌ی با حروف بزرگ) یک خاصیت #C تعریف شده‌ی در کلاس InputText است و نه یک ویژگی استاندارد HTML که عموما با حروف کوچک شروع می‌شوند:
<InputText @bind-Value="employee.FirstName" />
برای رسیدن به bind-Value@ فوق، سه مرحله باید طی شود:
الف) یک پارامتر عمومی به نام Value باید در کلاس کامپوننت جاری تعریف شود تا بتوان از طریق والد آن، مقداری را دریافت کرد (یک طرف binding به این نحو تشکیل می‌شود):
[Parameter] public string Value { set; get; }
ب) یک رویداد خاص Blazor، به نام EventCallback نیز باید اضافه شود تا به کامپوننت استفاده کننده‌ی از کامپوننت جاری، تغییرات را اطلاع رسانی کند (به این نحو است که این binding، دو طرفه می‌شود و تغییرات رخ‌داده‌ی در اینجا، به کامپوننت والد در برگیرنده‌ی آن، اطلاع رسانی می‌شود):
[Parameter] public EventCallback<string> ValueChanged { get; set; }
نام آن هم ویژه‌است. یعنی حتما باید با نام پارامتر Value شروع شود (نام پارامتری که قرار است binding دو طرفه روی آن اعمال گردد) و حتما باید ختم به واژه‌ی Changed باشد. این الگوی استاندارد از این جهت مورد استفاده قرار می‌گیرد که در تعریف InputText فوق، چنین پارامتر و مقدار دهی را مشاهده نمی‌کنیم، اما ... در پشت صحنه توسط Blazor به صورت خودکار تشکیل شده و مدیریت می‌شود.

نکته‌ی مهم: در اینجا بجای EventCallback، از Action هم می‌توان استفاده کرد:
[Parameter] public Action<string> ValueChanged { get; set; }
تفاوت اصلی و مهم آن با EventCallback، در فراخوانی نشدن خودکار متد StateHasChanged، در پایان کار آن است. زمانیکه EventCallback ای فراخوانی می‌شود، Blazor به صورت خودکار در پایان کار آن، متد StateHasChanged را نیز فراخوانی می‌کند تا والد دربرگیرنده‌ی کامپوننت جاری، مجددا رندر شود (به همراه تمام کامپوننت‌های فرزند آن). اما <Action<T فاقد این درخواست خودکار رندر و به روز رسانی مجدد UI است.

ج) برای فعالسازی اعتبارسنجی استاندارد فرم‌های Blazor، نیاز به خاصیت ویژه‌ی سومی نیز هست (که اختیاری است):
[Parameter] public Expression<Func<string>> ValueExpression { get; set; }
این خاصیت ویژه نیز باید با نام Value یا همان پارامتر تعریف شده، شروع شود و حتما باید ختم به واژه‌ی Expression شود. در مورد اعتبارسنجی‌ها در قسمت‌های بعدی بیشتر بحث خواهد شد. این پارامتر و مدیریت آن توسط خود Blazor صورت می‌گیرد و به ندرت توسط ما به صورت مستقیمی مقدار دهی خواهد شد؛ مگر اینکه بخواهیم کامپوننتی مانند InputText را سفارشی سازی کنیم.

مرحله‌ی آخر این طراحی، فراخوانی پارامتر ValueChanged است تا به کامپوننت والد این تغییرات را اطلاع رسانی کنیم. روش استاندارد آن به صورت زیر است:
private string _value;

[Parameter]
public string Value
{
   get => _value;
   set
   {
       var hasChanged = string.Equals(_value, value, StringComparison.Ordinal);
       if (hasChanged)
       {
           _value = value;

           if (ValueChanged.HasDelegate)
           {
              _ = ValueChanged.InvokeAsync(value);
           }
         }
    }
}
در اینجا در قسمت set همان پارامتر Value ای که در قسمت الف تعریف کردیم، در صورت بروز تغییری نسبت به قبل، متد InvokeAsync پارامتر ValueChanged را فراخوانی می‌کنیم. تا همین اندازه برای اطلاع رسانی به والد کافی است؛ همچنین وجود مقایسه‌ی بین مقدار جدید و مقدار قبلی، برای کاهش تعداد بار به روز رسانی UI ضروری است. هر بار که ValueChanged.InvokeAsync فراخوانی می‌شود، والد کامپوننت جاری، یکبار دیگر UI را مجددا رندر خواهد کرد. بنابراین هر چقدر تعداد این رندرها کمتر باشد، کارآیی برنامه بهبود خواهد یافت.
در این قطعه کد، بررسی ValueChanged.HasDelegate را هم مشاهده می‌کنید. زمانیکه پارامتر Value ای با طی سه مرحله‌ی فوق تعریف شد، قرار نیست حتما توسط bind-Value@ مورد استفاده قرار گیرد. می‌توان Value را به صورت یک طرفه هم مورد استفاده قرار داد. در این حالت دو پارامتر ب و ج دیگر توسط Blazor ایجاد و مقدار دهی نشده و رهگیری نخواهند شد. یعنی تعریف bind-Value@ در سمت والد، معادل سیم کشی خودکار به ValueChanged و ValueExpression از طرف Blazor است و تعریف دستی آن‌ها ضرورتی ندارد. اما می‌توان bind-Value@ را هم تعریف نکرد و فقط نوشت Value. در این حالت از تنظیمات ب و ج صرفنظر می‌شود. بنابراین ضروری است که بررسی کنیم آیا پارامتر ValueChanged واقعا متصل به روال رویدادگردانی شده‌است یا خیر. اگر خیر، نیازی به اطلاع رسانی و فراخوانی متد ValueChanged.InvokeAsync نیست.
نظرات مطالب
مروری بر کاربردهای Action و Func - قسمت اول
نمی‌توان. چون در اصل Action و Func به این صورت تعریف شده‌اند:
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
البته می‌توان بجای Action و Func، یک delegate سفارشی را از صفر نوشت؛ ولی هدف در اینجا سهولت استفاده‌است.
+ از زمان C# 7.0 اگر نیاز به نامگذاری این پارامترها را داشتید، می‌توانید از tuples به صورت زیر استفاده کنید:
Func<(string firstName, string lastName), string> f = (data) => data.firstName + data.lastName;
f(("Foo", "Bar"));