اشتراکها
نظرسنجیها
آیا قصد مهاجرت به NET Core. را دارید؟
بله. قطعا!
در حال بررسی هستم
خیر
در حال بررسی هستم
خیر
در ادامه میخواهیم نحوهی ایجاد یک فرمساز ساده را ASP.NET MVC بررسی کنیم.
ویوی ایجاد فیلد برای هر فرم:
در ویوی فوق کاربر میتواند برای فرم انتخاب شده فیلدهای موردنظر را تعریف کند:
ویوی نمایش فرم تولید شده برای کاربر نهایی:
همانطور که در کدهای فوق مشخص است از اکشن متدی که در ادامه مشاهده خواهید کرد لیستی از فیلدهای مربوط به یک فرم را برای کاربر به صورت رندر شده نمایش دادهایم. در اینجا باید براساس فیلد FieldType، نوع فیلد را تشخیص دهیم و المنت متناسب با آن را برای کاربر نهایی رندر کنیم. برای اینکار توسط یک حلقه for در بین تمام فیلدها پیمایش میکنیم:
سپس در داخل حلقه یک شرط را برای بررسی نوع فیلد قرار دادهایم:
بعد از بررسی نوع فیلد، خروجی رندر شده به این صورت برای کاربر نهایی به صورت یک عنصر HTML نمایش داده میشود:
همانطور که در کدهای قبلی مشاهده میکنید یکسری فیلد را به صورت مخفی بر روی فرم قرار دادهایم زیرا در زمان پست این اطلاعات به سرور از آنجائیکه مقادیر فیلدهای فرم تولید شده ممکن است چندین مورد باشند، به صورت آرایهایی از عناصر آنها را نمایش خواهیم داد:
خوب، تا اینجا توانستیم یک فرمساز ساده ایجاد کنیم. اما برای ارسال این اطلاعات به سرور به یک مدل دیگر احتیاج داریم. این جدول در واقع محل ذخیرهسازی مقادیر فیلدهای یک فرم و یا فرمهای مختلف است.
این جدول در واقع شامل: آیدی، مقدار فیلد، کلید خارجی فیلد و کلید خارجی فرم میباشد. بنابراین برای ارسال ویو قبلی به سرور اکشنمتد ShowForm را در حالت Post به این صورت خواهیم نوشت:
سورس مثال جاری را نیز میتوانید از اینجا دریافت کنید.
مدلهای برنامه ما به صورت زیر میباشند:
namespace SimpleFormGenerator.DomainClasses { public class Form { public int Id { get; set; } public string Title { get; set; } public virtual ICollection<Field> Fields { get; set; } } public class Field { public int Id { get; set; } public string TitleEn { get; set; } public string TitleFa { get; set; } public FieldType FieldType { get; set; } public virtual Form Form { get; set; } public int FormId { get; set; } } public enum FieldType { Button, Checkbox, File, Hidden, Image, Password, Radio, Reset, Submit, Text } }
توضیح مدلهای فوق:
همانطور که مشاهده میکنید برنامه ما از سه مدل تشکیل شده است. اولین مورد آن کلاس فرم است. این کلاس در واقع بیانگر یک فرم است که در سادهترین حالت خود از یک Id، یک عنوان و تعدادی از فیلدها تشکیل میشود. کلاس فیلد نیز بیانگر یک فیلد است که شامل: آیدی، عنوان انگلیسی فیلد، عنوان فارسی فیلد، نوع فیلد (که در اینجا از نوع enum انتخاب شده است که خود شامل چندین آیتم مانند Text, Radioو... است) و کلید خارجی کلاس فرم میباشد. تا اینجا مشخص شد که رابطه فرم با فیلد، یک رابطه یک به چند است؛ یعنی یک فرم میتواند چندین فیلد داشته باشد.
کلاس کانتکست برنامه نیز به این صورت میباشد:
namespace SimpleFormGenerator.DataLayer.Context { public class SimpleFormGeneratorContext : DbContext, IUnitOfWork { public SimpleFormGeneratorContext() : base("SimpleFormGenerator") {} public DbSet<Form> Forms { get; set; } public DbSet<Field> Fields { get; set; } public DbSet<Value> Values { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Value>() .HasRequired(d => d.Form) .WithMany() .HasForeignKey(d => d.FormId) .WillCascadeOnDelete(false); } } }
همانطور که مشاهده میکنید مدلهای برنامه را در معرض دید EF قرار دادهایم. تنها نکتهایی که در کلاس فوق مهم است متد OnModelCreating است. از آنجائیکه رابطه کلاس Field و Value یک رابطه یکبهیک است باید ابتدا و انتهای روابط را برای این دو کلاس تعیین کنیم.
تا اینجا میتوانیم به کاربر امکان ایجاد یک فرم و همچنین تعیین فیلدهای یک فرم را بدهیم. برای اینکار ویوهای زیر را در نظر بگیرید:
ویو ایجاد یک فرم:
@model SimpleFormGenerator.DomainClasses.Form @{ ViewBag.Title = "صفحه ایجاد یک فرم"; } @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div> <span>عنوان</span> <div> @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" }) </div> </div> <div> <div> <input type="submit" value="ذخیره" /> </div> </div> </div> } <div> @Html.ActionLink("بازگشت", "Index") </div>
@model SimpleFormGenerator.DomainClasses.Field @{ ViewBag.Title = "CreateField"; } @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div> <span>عنوان انگلیسی</span> <div> @Html.EditorFor(model => model.TitleEn, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.TitleEn, "", new { @class = "text-danger" }) </div> </div> <div> <span>عنوان فارسی</span> <div> @Html.EditorFor(model => model.TitleFa, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.TitleFa, "", new { @class = "text-danger" }) </div> </div> <div> <span>نوع فیلد</span> <div> @Html.EnumDropDownListFor(model => model.FieldType, htmlAttributes: new { @class = "form-control" }) @Html.ValidationMessageFor(model => model.FieldType, "", new { @class = "text-danger" }) </div> </div> <div> <span>فرم</span> <div> @Html.DropDownList("FormId", (SelectList)ViewBag.FormList) @Html.ValidationMessageFor(model => model.FormId, "", new { @class = "text-danger" }) </div> </div> <div> <div> <input type="submit" value="ذخیره" /> </div> </div> </div> } <div> @Html.ActionLink("بازگشت ", "Index") </div>
@using SimpleFormGenerator.DomainClasses @model IEnumerable<SimpleFormGenerator.DomainClasses.Field> @{ ViewBag.Title = "نمایش فرم"; } <div> <div> <div> @using (Html.BeginForm()) { @Html.AntiForgeryToken() for (int i = 0; i < Model.Count(); i++) { if (Model.ElementAt(i).FieldType == FieldType.Text) { <text> <input type="hidden" name="[@i].FieldType" value="@Model.ElementAt(i).FieldType" /> <input type="hidden" name="[@i].Id" value="@Model.ElementAt(i).Id" /> <input type="hidden" name="[@i].FormId" value="@Model.ElementAt(i).FormId" /> <div> <label>@Model.ElementAt(i).TitleFa</label> <div> <input type="text" name="[@i].TitleEn" /> </div> </div> </text> } } <div data-formId ="@ViewBag.FormId"> <div> <input type="submit" value="ارسال فرم" /> </div> </div> } </div> <div> @Html.ActionLink("بازگشت", "Index") </div> </div> </div>
for (int i = 0; i < Model.Count(); i++) { // code }
if (Model.ElementAt(i).FieldType == FieldType.Text) { // code }
<input type="text" name="[@i].TitleEn" />
[@i].FieldTyp
public class Value { public int Id { get; set; } public string Val { get; set; } public virtual Field Field { get; set; } [ForeignKey("Field")] public int FieldId { get; set; } public virtual Form Form { get; set; } [ForeignKey("Form")] public int FormId { get; set; } }
[HttpPost] public ActionResult ShowForm(IEnumerable<Field> values) { if (ModelState.IsValid) { foreach (var value in values) { _valueService.AddValue(new Value { Val = value.TitleEn, FormId = value.FormId, FieldId = value.Id}); _uow.SaveAllChanges(); } } return View(values); }
پاسخ به بازخوردهای پروژهها
اجرا نشدن پروژه
برخی از مشکلات مربوط به nuget به فرض برقراری ارتباط با اینترنت، به پروتکل https برمیگردد که به دلیل کندی سرعت یا اشکالات فیلترینگ یا روتینگهای طولانی، زمان اتصال و درخواست منقضی شده و ارتباط ناموفق میشود و بخشی از بستهها دریافت شده و بخشی هم دریافت نمیشود.
راه حل اول رفتن به تنظیمات nuget و تبدیل پروتکل ارتباطی به پروتکل http است که فقط کافیست حرف s را از https حذف کنید و آن مخزن را فعال کرده و در ابتدا قرار دهید.
راه حل دوم جلوگیری از این اتفاق و مداخله، با روشهای متداول ضد ف ی ل ت ر ی ن گ هست.
نکته دیگری هم وجود دارد که علیرغم وجود بستهای در پروژه، ممکن است علامت اخطاری وجود داشته باشد یا برنامه آن بسته را شناسایی نکرده باشد. در این حالت آن بسته را حذف کرده و مجدداً پروژه را build کنید.
پ.ن1: یک پروژه اگر نتواند بستههایش را دریافت کند، درست build نمیشود و بنابراین هر پروژه دیگری هم که از آن استفاده کرده باشد در build خود دچار خطار خواهد شد که به محض برطرف شدن اشکال اول، این پروژه نیز با موفقیت build خواهد شد.
پ.ن2: برخی از خطاهای اینجا بخاطر نبود برخی فایلها در پروژه دانلود شده است که اگر مجدداً دانلود کنید، اشکال برطرف شده است.
پ.ن3: گاهی نیز پیش میآید که باید بصورت دستی وارد عمل شده و برخی از پکیجها را با وارد کردن دستور دریافت آن بسته یا دستور دریافت مجدد آن بسته یا دستور آپدیت آن بسته، دریافت کرد.
راه حل اول رفتن به تنظیمات nuget و تبدیل پروتکل ارتباطی به پروتکل http است که فقط کافیست حرف s را از https حذف کنید و آن مخزن را فعال کرده و در ابتدا قرار دهید.
راه حل دوم جلوگیری از این اتفاق و مداخله، با روشهای متداول ضد ف ی ل ت ر ی ن گ هست.
نکته دیگری هم وجود دارد که علیرغم وجود بستهای در پروژه، ممکن است علامت اخطاری وجود داشته باشد یا برنامه آن بسته را شناسایی نکرده باشد. در این حالت آن بسته را حذف کرده و مجدداً پروژه را build کنید.
پ.ن1: یک پروژه اگر نتواند بستههایش را دریافت کند، درست build نمیشود و بنابراین هر پروژه دیگری هم که از آن استفاده کرده باشد در build خود دچار خطار خواهد شد که به محض برطرف شدن اشکال اول، این پروژه نیز با موفقیت build خواهد شد.
پ.ن2: برخی از خطاهای اینجا بخاطر نبود برخی فایلها در پروژه دانلود شده است که اگر مجدداً دانلود کنید، اشکال برطرف شده است.
پ.ن3: گاهی نیز پیش میآید که باید بصورت دستی وارد عمل شده و برخی از پکیجها را با وارد کردن دستور دریافت آن بسته یا دستور دریافت مجدد آن بسته یا دستور آپدیت آن بسته، دریافت کرد.
همانطور که در مطلب ایجاد زیرگریدها در jqGrid مشاهده کردید، هرچند این قابلیت برای نمایش لیست سادهای از عناصر مفید است اما ... امکانات آنچنانی را به همراه ندارد. برای مثال صفحه بندی، جستجو، سفارشی سازی عناصر و غیره را به همراه ندارد. اگر علاقمند باشید که این امکانات را نیز اضافه کنید، میتوان این زیرگرید را با یک گرید کامل jqGrid نیز جایگزین کرد. همچنین اگر نیاز بود، این گرید جدید چون یک jqGrid کامل است، باز هم میتوان یک سطح دیگر را به آن افزود و الی آخر.
جایگزین کردن یک Subgrid با یک jqGrid کامل
خلاصهی عملیات جایگزینی یک Subgrid را توسط یک jqGrid کامل، در ذیل مشاهده میکنید:
همانند نمایش subgridهای معمولی، ابتدا subGrid: true باید اضافه شود تا ستونی با ردیفهای + دار، ظاهر شود. اینبار توسط روال رویدادگردان subGridRowExpanded، کنترل نمایش subgrid را در دست گرفته و آنرا با یک jqGrid جایگزین میکنیم.
امضای متد grid1RowExpanded، شامل id یک div است که گرید جدید در آن قرار خواهد گرفت، به همراه Id ردیفی که اطلاعات زیرگرید آن نیاز است از سرور واکشی شود.
بر مبنای subGridId، مانند قبل، یک جدول و یک div را برای نمایش jgGrid و pager آن به صفحه به صورت پویا اضافه میکنیم.
سپس تعاریف jqGrid آن مانند قبل است و نکتهی خاصی ندارد. بدیهی است گرید جدید نیز میتواند در صورت نیاز یک subgrid دیگر داشته باشد.
در اینجا تنها نکتهی مهم آن نحوهی ارسال اطلاعات rowId به سرور است. اکشن متدی که قرار است اطلاعات زیر گرید را تامین کند، یک چنین امضایی دارد:
بنابراین نیاز است که به نحوی rowId را به آن ارسال کرد. مشکل اینجا است که Url.Action یک کد سمت سرور است و rowId یک متغیر سمت کاربر. نمیتوان این متغیر را در کدهای Razor مستقیما قرار داد. اما میتوان یک محل جایگزینی را در کدهای سمت سرور پیش بینی کرد. مثلا js-id. زمانیکه این رشته در صفحه رندر میشود، به صورت معمول و به کمک متد replace جاوا اسکریپت، js-id آنرا با rowId جایگزین میکنیم. به این ترتیب امکان تزریق اطلاعات سمت کاربر به خروجی سمت سرور Razor میسر میشود.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
jqGrid12.zip
جایگزین کردن یک Subgrid با یک jqGrid کامل
خلاصهی عملیات جایگزینی یک Subgrid را توسط یک jqGrid کامل، در ذیل مشاهده میکنید:
$('#list').jqGrid({ caption: "آزمایش دوازدهم", //..........مانند قبل subGrid: true, subGridRowExpanded: grid1RowExpanded }); function grid1RowExpanded(subGridId, rowId) { var subgridTableId = subGridId + "_t"; var pagerId = "p_" + subgridTableId; var container = 'g_' + subGridId; $("#" + subGridId).html('<div dir="rtl" id="' + container + '" style="width:100%; height: 100%">' + '<table id="' + subgridTableId + '" class="scroll"></table><div id="' + pagerId + '" class="scroll"></div>'); var url = '@Url.Action("GetOrderDetails", "Home", routeValues: new { id = "js-id" })' .replace("js-id", encodeURIComponent(rowId)); // تزریق اطلاعات سمت کاربر به خروجی سمت سرور $("#" + subgridTableId).jqGrid({ caption: "ریز اقلام سفارش " + rowId, autoencode: true, //security - anti-XSS url: url, //..........مانند قبل }); }
امضای متد grid1RowExpanded، شامل id یک div است که گرید جدید در آن قرار خواهد گرفت، به همراه Id ردیفی که اطلاعات زیرگرید آن نیاز است از سرور واکشی شود.
بر مبنای subGridId، مانند قبل، یک جدول و یک div را برای نمایش jgGrid و pager آن به صفحه به صورت پویا اضافه میکنیم.
سپس تعاریف jqGrid آن مانند قبل است و نکتهی خاصی ندارد. بدیهی است گرید جدید نیز میتواند در صورت نیاز یک subgrid دیگر داشته باشد.
در اینجا تنها نکتهی مهم آن نحوهی ارسال اطلاعات rowId به سرور است. اکشن متدی که قرار است اطلاعات زیر گرید را تامین کند، یک چنین امضایی دارد:
public ActionResult GetOrderDetails(int id, JqGridRequest request)
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
jqGrid12.zip
دلال یا Middle man در دسته الگوهای «کدهایی بیش از اندازه وابسته به هم» قرار میگیرد. زمانیکه یک کلاس، تنها کاری را که انجام میدهد، هدایت فراخوانی به کلاس دیگری باشد، با این الگو مواجه هستیم. تشخیص این کد بد بو معمولا بسیار آسان است.
به طور مثال:
public class ProductQuery { public dynamic GetProductsByCustomerId(int id) { return new ExpandoObject(); } } public class CustomerQuery { private readonly ProductQuery _productQuery; public CustomerQuery(ProductQuery productQuery) { _productQuery = productQuery; } public dynamic GetProducts(int customerId) { return _productQuery.GetProductsByCustomerId(customerId); } } public static class Programm { static void Main(string[] args) { var query = new CustomerQuery(new ProductQuery()); var products = query.GetProducts(1); } }
در کلاس ProductQuery، متدی برای دریافت تمامی محصولات مربوط به یک مشتری وجود دارد. در کلاس CustomerQuery نیز یک متد برای دریافت تمامی محصولات مشتری وجود دارد. در این مثال متد GetProducts در کلاس CustomerQuery را میتوان «متد حسود» نیز نامید. این نوع استفاده از متد، «الگوی دلال» نیز است. زمانیکه تمامی متدهای یک کلاس به این صورت باشند، آن کلاس به عنوان دلال شناخته میشود.
چرا چنین بویی به راه میافتد
معمولا به چند دلیل با این کد بد بو مواجه خواهیم شد:
- انتقال مسئولیتهای یک کلاس به کلاسی دیگر به مرور زمان و تبدیل متدهای آن به هدایت کنندگان فراخوانی.
- عدم تشخیص درست مسئولیتهای یک کلاس و اجبار به افزودن هدایت کنندگان فراخوانی در کلاسهای دیگر. این حالت معمولا زمانی اتفاق میافتد که یک کلاس، مسئولیتهای زیادی داشته باشد و کلاسهای مختلف، صرفا نیاز به هدایت فراخوانی به این کلاس را داشته باشند.
- عدم استفاده مناسب از الگوهای طراحی.
روشهای اصلاح این کد بد بو
روش کلی برای اصلاح چنین بوی بدی، حذف متدها و کلاسهای هدایت کننده فراخوانی و تغییر تمامی استفاده کنندگان از آنها است. در مثال بالا میتوان متد GetProducts از کلاس CustomerQuery را حذف و تمامی فراخوانیهای آن را به متد GetProductsByCustomerId از کلاس ProductQuery انتقال داد، یا بلعکس.
چه کدهایی دلال نیستند
زمانیکه کلاس هدایت کننده فرخوانی به صورت عمدی ساخته شده باشد، معمولا با چنین الگویی روبرو نیستیم. مانند استفاده از الگوهای طراحی زیر:
- Chain of responsibility
- Decorator
- Proxy
- Adapter
هر کدام از الگوهای طراحی ذکر شده در بالا به دلایل خاصی ایجاد میشوند و علارغم شباهت زیاد آنها با کد بد بوی دلال، شرایط مربوط به کد بد بود را دارا نمیباشند و معمولا نیازی به اعمال تغییری در آنها نیست. با مطالعه و بررسی دقیق الگوهای طراحی میتوان از تشخیص اشتباه این الگوی بد جلوگیری کرد.
راههای زیادی برای لاگ کردن خطاهای حاصل در یک برنامه ASP.Net وجود دارند. از روشهای exception handling معمول تا افزودن یک فایل global.asax به برنامه و دریافت و لاگ کردن خطاهای مدیریت نشده توسط روال رخ داد گردان Application_Error آن.
بررسی این خطاها فوق العاده مهم است ، حداقل به دو دلیل : الف) قبل از این که کاربران به شما بگویند برنامه مشکل پیدا کرده، از طریق ایمیل دریافتی مطلع خواهید شد. (فرض کنید علاوه بر ثبت وقایع ، آنها را ایمیل هم میزنید) این مورد در جهت بالا بردن کیفیت کار تمام شده واقعا مؤثر است. ب) رفتارهای مخرب را هم بهتر میتوانید تحت نظر داشته باشید.
تمام این موارد مستلزم کد نویسی است. دریافت خطا در روال Application_Error و سپس کد نویسی برای ارسال ایمیل. از ASP.Net 2.0 به بعد این کار را بدون کد نویسی و با استفاده از امکانات ASP.NET health monitoring نیز میتوان به سادگی و دقت هرچه تمامتر انجام داد.
کار زیادی را قرار نیست انجام دهید! فایل وب کانفیگ سایت را باز کنید و چند سطر زیر را به آن اضافه کنید (قسمت healthMonitoring و همچنین قسمت mailSettings ):
<?xml version="1.0"?>
<configuration>
<appSettings/>
<connectionStrings/>
<system.web>
<compilation debug="true">
</compilation>
<authentication mode="Windows"/>
<healthMonitoring enabled="true">
<providers>
<add name="EmailProvider"
type="System.Web.Management.SimpleMailWebEventProvider"
from="you@domain.com"
to="you@domain.com"
subjectPrefix="Error: "
buffer="true"
bufferMode="Notification"/>
</providers>
<rules>
<add
provider="EmailProvider"
name="All App Events"
eventName="All Errors"/>
</rules>
</healthMonitoring>
</system.web>
<system.net>
<mailSettings>
<smtp deliveryMethod="SpecifiedPickupDirectory">
<specifiedPickupDirectory pickupDirectoryLocation="C:\emails"/>
</smtp>
</mailSettings>
</system.net>
</configuration>
در حالت اجرا بر روی یک سرور ، این قسمت را میتوان به صورت زیر تنظیم نمود و آدرس smtp server را توسط آن مشخص کرد تا به صورت خودکار مورد استفاده قرار گیرد:
<mailSettings>
<smtp from="you@domain.com">
<network host="smtp.domain.com" />
</smtp>
</mailSettings>
شایان ذکر است از ASP.Net 2.0 به بعد امکان ثبت وقایع در event log ویندوز محدود شده است و اگر نیاز به انجام این کار باشد باید دسترسی بیشتری را به یوزر asp.net اعطاء کرد. اما با استفاده از روش فوق، جزئیات خطای حاصل به صورت خودکار به event log ویندوز نیز اضافه میشود.
اگر علاقمند باشید که خطاهای حاصل را در یک دیتابیس نیز لاگ کنید، به این مقاله میتوان رجوع کرد.
مطالب
ASP.NET MVC #2
MVC چیست و اساس کار آن چگونه است؟
الگوی MVC در سالهای اول دهه 70 میلادی در شرکت زیراکس توسط خالقین زبان اسمالتاک که جزو اولین زبانهای شیءگرا محسوب میشود، ارائه گردید. نام MVC از الگوی Model-View-Controller گرفته شده و چندین دهه است که در صنعت تولید نرم افزار مورد استفاده میباشد. هدف اصلی آن جدا سازی مسئولیتهای اجزای تشکیل دهنده «لایه نمایشی» برنامه است.
این الگو در سال 2004 برای اولین بار در سکویی به نام Rails به کمک زبان روبی جهت ساخت یک فریم ورک وب MVC مورد استفاده قرار گرفت و پس از آن به سایر سکوها مانند جاوا، دات نت (در سال 2007)، PHP و غیره راه یافت.
تصاویری را از این تاریخچه در ادامه ملاحظه میکنید؛ از دکتر Trygve Reenskaug تا شرکت زیراکس و معرفی آن در Rails به عنوان اولین فریم ورک وب MVC.
C در MVC معادل Controller است. کنترلر قسمتی است که کار دریافت ورودیهای دنیای خارج را به عهده دارد؛ مانند پردازش یک درخواست HTTP ورودی.
زمانیکه کنترلر این درخواست را دریافت میکند، کار وهله سازی Model را عهده دار خواهد شد و حاوی اطلاعاتی است که نهایتا در اختیار کاربر قرار خواهد گرفت تا فرآیند پردازش درخواست رسیده را تکمیل نماید. برای مثال اگر کاربری جهت دریافت آخرین اخبار به سایت شما مراجعه کرده است، در اینجا کار تهیه لیست اخبار بر اساس مدل مرتبط به آن صورت خواهد گرفت. بنابراین کنترلرها، پایه اصلی و مدیر ارکستر الگوی MVC محسوب میشوند.
در ادامه، کنترلر یک View را جهت نمایش Model انتخاب خواهد کرد. View در الگوی MVC یک شیء ساده است. به آن میتوان به شکل یک قالب که اطلاعاتی را از Model دریافت نموده و سپس آنها را در مکانهای مناسبی در صفحه قرار میدهد، نگاه کرد.
نتیجه استفاده از این الگو، ایزوله سازی سه جزء یاد شده از یکدیگر است. برای مثال View نمیداند و نیازی ندارد که بداند چگونه باید از لایه دسترسی به اطلاعات کوئری بگیرد. یا برای مثال کنترلر نیازی ندارد بداند که چگونه و در کجا باید خطایی را با رنگی مشخص نمایش دهد. به این ترتیب انجام تغییرات در لایه رابط کاربری برنامه در طول توسعه کلی سیستم، سادهتر خواهد شد.
همچنین در اینجا باید اشاره کرد که این الگو مشخص نمیکند که از چه نوع فناوری دسترسی به اطلاعاتی باید استفاده شود. میتوان از بانکهای اطلاعاتی، وب سرویسها، صفها و یا هر نوع دیگری از اطلاعات استفاده کرد. به علاوه در اینجا در مورد نحوه طراحی Model نیز قیدی قرار داده نشده است. این الگو تنها جهت ساخت بهتر و اصولی «رابط کاربری» طراحی شده است و بس.
تفاوت مهم پردازشی ASP.NET MVC با ASP.NET Web forms
اگر پیشتر با ASP.NET Web forms کار کرده باشید اکنون شاید این سؤال برایتان وجود داشته باشد که این سیستم جدید در مقایسه با نمونه قبلی، چگونه درخواستها را پردازش میکند.
همانطور که مشاهده میکنید، در وب فرمها زمانیکه درخواستی دریافت میشود، این درخواست به یک فایل موجود در سیستم مثلا default.aspx ارسال میگردد. سپس ASP.NET یک کلاس وهله سازی شده معرف آن صفحه را ایجاد کرده و آنرا اجرا میکند. در اینجا چرخه طول عمر صفحه مانند page_load و غیره رخ خواهد داد. جهت انجام این وهله سازی، View به فایل code behind خود گره خورده است و جدا سازی خاصی بین این دو وجود ندارد. منطق صفحه به markup آن که معادل است با یک فایل فیزیکی بر روی سیستم، کاملا مقید است. در ادامه، این پردازش صورت گرفته و HTML نهایی تولیدی به مرورگر کاربر ارسال خواهد شد.
در ASP.NET MVC این نحوه پردازش تغییر کرده است. در اینجا ابتدا درخواست رسیده به یک کنترلر هدایت میشود و این کنترلر چیزی نیست جز یک کلاس مجزا و مستقل از هر نوع فایل ASPX ایی در سیستم. سپس این کنترلر کار پردازش درخواست رسیده را شروع کرده، اطلاعات مورد نیاز را جمع آوری و سپس به View ایی که انتخاب میکند، جهت نمایش نهایی ارسال خواهد کرد. در اینجا View این اطلاعات را دریافت کرده و نهایتا در اختیار کاربر قرار خواهد داد.
آشنایی با قرارداد یافتن کنترلرهای مرتبط
تا اینجا دریافتیم که نحوه پردازش درخواستها در ASP.NET MVC بر مبنای کلاسها و متدها است و نه بر مبنای فایلهای فیزیکی موجود در سیستم. اگر درخواستی به سیستم ارسال میشود، در ابتدا، این درخواست جهت پردازش، به یک متد عمومی موجود در یک کلاس کنترلر هدایت خواهد شد و نه به یک فایل فیزیکی ASPX (برخلاف وب فرمها).
همانطور که در تصویر مشاهده میکنید، در ابتدای پردازش یک درخواست، آدرسی به سیستم ارسال خواهد شد. بر مبنای این آدرس، نام کنترلر که در اینجا زیر آن خط قرمز کشیده شده است، استخراج میگردد (برای مثال در اینجا نام این کنترلرProducts است). سپس فریم ورک به دنبال کلاس این کنترلر خواهد گشت. اگر آنرا در اسمبلی پروژه بیابد، از آن خواهد خواست تا درخواست رسیده را پردازش کند؛ در غیراینصورت پیغام 404 یا یافت نشد، به کاربر نمایش داده میشود.
اما فریم ورک چگونه این کلاس کنترلر درخواستی را پیدا میکند؟
در زمان اجرا، اسمبلی اصلی پروژه به همراه تمام اسمبلیهایی که به آن ارجاعی دارند جهت یافتن کلاسی با این مشخصات اسکن خواهند شد:
1- این کلاس باید عمومی باشد.
2- این کلاس نباید abstract باشد (تا بتوان آنرا به صورت خودکار وهله سازی کرد).
3- این کلاس باید اینترفیس استاندارد IController را پیاده سازی کرده باشد.
4- و نام آن باید مختوم به کلمه Controller باشد (همان مبحث Convention over configuration یا کار کردن با یک سری قرار داد از پیش تعیین شده).
برای مثال در اینجا فریم ورک به دنبال کلاسی به نام ProductsController خواهد گشت.
شاید تعدادی از برنامه نویسهای ASP.NET MVC تصور کنند که فریم ورک در پوشهی استانداردی به نام Controllers به دنبال این کلاس خواهد گشت؛ اما در عمل زمانیکه برنامه کامپایل میشود، پوشهای در این اسمبلی وجود نخواهد داشت و همه چیز از طریق Reflection مدیریت خواهد شد.
قطعه کد زیر در برنامههای ASP.NET، نام مرورگر کاربر و همچنین شماره نگارش آنرا باز میگرداند:
برای مثال با فایرفاکس، چنین خروجی را دارد:
اما ... با مرورگر جدید Edge مایکروسافت، خروجی کروم را مشاهده خواهیم کرد:
از این جهت که user agent این مرورگر، چنین شکلی را دارد و ختم به edge است:
برای رفع این مشکل، نیاز است فایل جدیدی را به مجموعهی «browser definition files» دات نت اضافه کنیم. این فایلها عموما در مسیر زیر یافت میشوند:
برای نمونه مسیر ذیل را برای مشاهدهی فایلهای مرورگرهای موجود، بررسی کنید:
در این بین اثری از تعریف مرورگر edge نیست. برای حل این مشکل، الزاما نیازی نیست تا فایل مرورگر جدیدی را به پوشهی فوق اضافه کنیم. میتوان تعریف این فایل را در پوشهی استانداردی به نام App_Browsers نیز در ریشهی پروژه، قرار داد:
با این محتویات:
در اینجا user agent مرورگر کاربر دریافت شده و اگر ختم به Edge بود، نام و شماره نگارش صحیح آن، دریافت خواهد شد.
اکنون پس از این تنظیمات، برنامه (تفاوتی نمیکند که وب فرم باشد یا MVC) اطلاعات صحیحی را نمایش میدهد:
var browser = Request.Browser.Browser + " " + Request.Browser.Version;
برای مثال با فایرفاکس، چنین خروجی را دارد:
اما ... با مرورگر جدید Edge مایکروسافت، خروجی کروم را مشاهده خواهیم کرد:
از این جهت که user agent این مرورگر، چنین شکلی را دارد و ختم به edge است:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240
برای رفع این مشکل، نیاز است فایل جدیدی را به مجموعهی «browser definition files» دات نت اضافه کنیم. این فایلها عموما در مسیر زیر یافت میشوند:
<windir>\Microsoft.NET\Framework\<ver>\CONFIG\Browsers
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Browsers
با این محتویات:
<browsers> <browser id="Edge" parentID="Chrome"> <identification> <userAgent match="Edge/(?'version'(?'major'\d+)(?'minor'\.\d+))" /> </identification> <capabilities> <capability name="browser" value="Edge" /> <capability name="version" value="${version}" /> <capability name="majorversion" value="${major}" /> <capability name="minorversion" value="${minor}" /> </capabilities> </browser> </browsers>
اکنون پس از این تنظیمات، برنامه (تفاوتی نمیکند که وب فرم باشد یا MVC) اطلاعات صحیحی را نمایش میدهد:
نظرات مطالب
ASP.NET MVC #1
- ASP فرق میکنه با ASP.NET؛ ASP یک فناوری مبتنی بر COM دهه نود میلادی بود و با آمدن ASP.NET در ابتدای سالهای 2000، توسعه آن توسط مایکروسافت خاتمه پیدا کرد.
- خروجی چه ASP دهه نود که الان به آن Classics ASP گفته میشود، چه ASP.NET دهه بعد از 2000، چه PHP، چه JSP و امثال آن همگی HTML هستند. مرورگرها بجز متون، HTML، CSS، جاوا اسکریپت و تصاویر به صورت پیش فرض قادر به پردازش محتوای دیگری نیستند؛ مگر اینکه افزونه خاصی را بکار برده باشند؛ مانند همین سیلورلایت یا فلش.
- در HTML و CSS چندین و چند روش قرار دادن عناصر در صفحه وجود دارند مانند static، absolute، fixed، relative. بر همین اساس در طراحی HTML یک سری مباحث Responsive یا واکنشگرا نیز وجود دارند که با استفاده از ترکیب CSS و HTML به خوبی قابل پوشش هستند. نمونهاش را در مباحث twitter bootstrap سایت میتوانید پیدا کنید. مثلا twitter bootstrap 3 یک فریم ورک CSS اصطلاحا mobile first است. یعنی طوری طراحی شده که سایت شما را به خوبی بتواند با اندازههای کوچک نمایشگرها تطابق دهد و قابل استفاده کند.
نتیجه گیری؟
ASP.NET یک فناوری سمت سرور است که نهایتا میتواند یک خروجی استاندارد قابل تفسیر توسط مرورگرها را تولید کند. در این بین شما میتوانید از توانمندیهای موجود در CSS، HTML و جاوا اسکریپت، برای بهبود دسترسی پذیری به سایت خودتان کمال استفاده را نمائید. اما اساسا این مباحث (مثلا طراحی واکنشگرا) ربطی به فناوریهای سمت سرور ندارند و جزو مباحث سمت کاربر محسوب میشوند.
- خروجی چه ASP دهه نود که الان به آن Classics ASP گفته میشود، چه ASP.NET دهه بعد از 2000، چه PHP، چه JSP و امثال آن همگی HTML هستند. مرورگرها بجز متون، HTML، CSS، جاوا اسکریپت و تصاویر به صورت پیش فرض قادر به پردازش محتوای دیگری نیستند؛ مگر اینکه افزونه خاصی را بکار برده باشند؛ مانند همین سیلورلایت یا فلش.
- در HTML و CSS چندین و چند روش قرار دادن عناصر در صفحه وجود دارند مانند static، absolute، fixed، relative. بر همین اساس در طراحی HTML یک سری مباحث Responsive یا واکنشگرا نیز وجود دارند که با استفاده از ترکیب CSS و HTML به خوبی قابل پوشش هستند. نمونهاش را در مباحث twitter bootstrap سایت میتوانید پیدا کنید. مثلا twitter bootstrap 3 یک فریم ورک CSS اصطلاحا mobile first است. یعنی طوری طراحی شده که سایت شما را به خوبی بتواند با اندازههای کوچک نمایشگرها تطابق دهد و قابل استفاده کند.
نتیجه گیری؟
ASP.NET یک فناوری سمت سرور است که نهایتا میتواند یک خروجی استاندارد قابل تفسیر توسط مرورگرها را تولید کند. در این بین شما میتوانید از توانمندیهای موجود در CSS، HTML و جاوا اسکریپت، برای بهبود دسترسی پذیری به سایت خودتان کمال استفاده را نمائید. اما اساسا این مباحث (مثلا طراحی واکنشگرا) ربطی به فناوریهای سمت سرور ندارند و جزو مباحث سمت کاربر محسوب میشوند.