در الگوی View ،MVC ها وظیفه نمایش داده های برنامه و تعامل با کاربران را بر عهده دارد. آنها همچنین با جدا کردن markup رابط کاربری از سایر بخش های برنامه، به ایجاد اصل Separation of Concerns (SoC) در یک برنامه MVC کمک می کنند.
یک View، یک قالب HTML است که Razor markup در آن تعبیه شده است. یک view، پسوند cshtml. دارد و بر پایه زبان #C است. Razor markup با HTML markup تعامل برقرار میکند تا یک صفحه وب که به سمت کلاینت ارسال میشود را تولید کند.
به طور کلی، برای هر اکشن متد در کنترلرها، فایلهای view مربوط به آنها به صورت جداگانه وجود دارند و فایلهای view به داخل پوشه های هم نام با خود کنترلرها، group میشوند. View ها در پوشه Views
در root اپلیکیشن ذخیره میشوند.
نگاهی به برنامه BookStore
که در مقاله قبلی ایجاد کرده بودیم بیندازیم:
همانطور که میبینید view ها برای BooksController
در پوشه Views، داخل پوشه Books قرار گرفته اند. پوشه Books
شامل view هایی برای متدهای Create
, Delete
, Details
, Edit
و Index
است. زمانیکه یک کاربر، به سمت یکی از این اکشنها، درخواست میفرستد، در نتیجه، اکشن متد مورد نظر در BooksController
، از view مناسب مرتبط به خود برای ایجاد یک صفحه وب و برگرداندن آن به کاربر استفاده میکند.
در این مقاله، از مدل و کنترلری که در قسمت قبل ایجاد کرده بودیم با کمی تغییرات جزئی دوباره استفاده خواهیم کرد. اما view ها را از ابتدا خواهیم ساخت.
اکیدا توصیه می کنیم سرفصلهای کامل این سری آموزشی را مشاهده کنید: آموزش سریالی ASP.NET Core MVC.
برای دانلود سورس کد این مقاله، روی این لینک کلیک کنید: سورس کد View ها, Partial View ها و Layout ها.
ما این مقاله را به قسمتهای زیر تقسیم میکنیم:
- تعریف Model و Controller
- استفاده از Razor Markup برای ایجاد View ها
- مفهوم Partial View ها
- Layout ها در ASP.NET Core
تعریف Model و Controller
از model که قبلا داشتیم دومرتبه استفاده میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Book { public int Id { get; set; } [Display(Name = "Book Title")] [Required] public string Title { get; set; } public string Genre { get; set; } [DataType(DataType.Currency)] [Range(1, 100)] public decimal Price { get; set; } [Display(Name = "Publish Date")] [DataType(DataType.Date)] public DateTime PublishDate { get; set; } } |
برای ساده نگه داشتن کارها، میخواهیم view های index
, details
و edit
را از ابتدا پیاده سازی کنیم. این، همه سناریوهای رایجی را که هنگام ایجاد view ها با آنها مواجه می شویم را پوشش می دهد.
اکنون اجازه دهید controller موجود خود را با حذف action method هایی که استفاده نمی کنیم کمی تغییر دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
public class BooksController : Controller { private readonly BookStoreWithDataContext _context; public BooksController(BookStoreWithDataContext context) { _context = context; } // GET: Books public async Task<IActionResult> Index() { return View(await _context.Book.ToListAsync()); } // GET: Books/Details/5 public async Task<IActionResult> Details(int? id) { if (id == null) { return NotFound(); } var book = await _context.Book .FirstOrDefaultAsync(m => m.Id == id); if (book == null) { return NotFound(); } return View(book); } // GET: Books/Edit/5 public async Task<IActionResult> Edit(int? id) { if (id == null) { return NotFound(); } var book = await _context.Book.FindAsync(id); if (book == null) { return NotFound(); } return View(book); } // POST: Books/Edit/5 // To protect from overposting attacks, please enable the specific properties you want to bind to, for // more details see http://go.microsoft.com/fwlink/?LinkId=317598. [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Edit(int id, [Bind("Id,Title,Genre,Price,PublishDate")] Book book) { if (id != book.Id) { return NotFound(); } if (ModelState.IsValid) { try { _context.Update(book); await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!BookExists(book.Id)) { return NotFound(); } else { throw; } } return RedirectToAction(nameof(Index)); } return View(book); } private bool BookExists(int id) { return _context.Book.Any(e => e.Id == id); } } |
اکنون model و controller را آماده کرده ایم. مرحله بعدی ایجاد view ها است.
استفاده از Razor Markup برای ایجاد View ها
View هایی که مختص یک کنترلر هستند در پوشه Views/[ControllerName] قرار می گیرند. View هایی که بین کنترلرها به اشتراک گذاشته میشوند در پوشه Views/Shared قرار میگیرند.
برای ایجاد یک view، یک فایل جدید اضافه میکنیم و به آن نام یکسان با نام اکشن متد موجود در کنترلر مورد نظر با پسوند cshtml. را میدهیم.
برای مثال، برای ایجاد یک view برای اکشن Index
در BooksController، باید یک فایل Index.cshtml در پوشه Views/Books ایجاد کنیم. با انجام این کار، ما یک view برای صفحه index خواهیم داشت.
در قسمت اول این سری آموزشی، ما از HTML Helper method ها برای ایجاد view های خود استفاده کرده بودیم. در این مقاله، قصد داریم با استفاده از tag helper ها، از یک رویکرد متفاوت برای ایجاد view ها استفاده کنیم.
Tag helper ها یک تجربه HTML پسند را برای توسعه فراهم میکنند. در بیشتر موارد، Razor markup با استفاده از Tag Helper ها شبیه به HTML استاندارد است. Tag Helper ها، نقل و انتقال صریح بین HTML و #C را در view های Razor کاهش می دهد.
در بیشتر موارد، Tag Helper ها، رویکرد جایگزین برای یک HTML Helper های خاص ارائه میدهند. اما درک این نکته مهم است که Tag Helper ها نمیتوانند جایگزین HTML Helper ها شوند، زیرا برخی از HTML Helper ها، معادل Tag Helper خودشان را ندارند. بنابراین، در بعضی موارد، باید از همان HTML helper ها استفاده کنیم.
Index View
حالا view ای را برای صفحه Index ایجاد کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
@model IEnumerable<WorkingWithViews.Models.Book> @{ ViewData["Title"] = "Index"; Book firstBook = Model.ToList().FirstOrDefault(); } <h1>Index</h1> <p> <a asp-action="Create">Create New</a> </p> <table class="table"> <thead> <tr> <th> <label asp-for="@firstBook.Id"></label> </th> <th> <label asp-for="@firstBook.Title"></label> </th> <th> <label asp-for="@firstBook.Genre"></label> </th> <th> <label asp-for="@firstBook.Price"></label> </th> <th> <label asp-for="@firstBook.PublishDate"></label> </th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> <label>@item.Id</label> </td> <td> <label>@item.Title</label> </td> <td> <label>@item.Genre</label> </td> <td> <label>@item.Price</label> </td> <td> <label>@item.PublishDate</label> </td> <td> <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> <a asp-action="Details" asp-route-id="@item.Id">Details</a> <a asp-action="Delete" asp-route-id="@item.Id">Delete</a> </td> </tr> } </tbody> </table> |
ما اینجا از یک strongly typed model استفاده میکنیم و آن model، از نوع IEnumerable<book>
است.
اول ما یک متغیر تعریف کردیم و اولین کتاب موجود در list را به آن اختصاص دادیم. ما این کار را برای گرفتن نام property ها و اختصاص آنها به header ها انجام دادیم:
1 |
Book firstBook = Model.ToList().FirstOrDefault(); |
ویژگی asp-for، نام ویژگی مورد نظر داخل model را به داخل HTML تولید شده استخراج میکند. بنابراین، ببینیم چطور یک label با نام ویژگی Title
را render میکنیم:
1 |
<label asp-for="@firstBook.Title"></label> |
عالی!
حالا ما باید تمام item های موجود در books collection را render کنیم. برای این منظور، ما از یک حلقه foreach
استفاده میکنیم که به ما کمک میکند تا یک HTML table را render کنیم. ما میتوانیم label را به همراه مقادیر property ها render کنیم:
1 |
<label>@item.Title</label> |
برای ایجاد action link ها، میتوانیم از ویژگی asp-action استفاده کنیم و برای پاس دادن پارامترها، میتوانیم از فرمت asp-route-{parametername} استفاده کنیم. بنابراین با این تفسیر، برای پارامتر id
، از asp-route-id
استفاده میکنیم:
1 |
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
Details View
حالا details view را ایجاد میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
@model WorkingWithViews.Models.Book @{ ViewData["Title"] = "Details"; } <h1>Details</h1> <div> <h4>Book</h4> <hr /> <dl class="row"> <dt class="col-sm-2"> <label asp-for="Title"></label> </dt> <dd class="col-sm-10"> @Model.Title </dd> <dt class="col-sm-2"> <label asp-for="Genre"></label> </dt> <dd class="col-sm-10"> @Model.Genre </dd> <dt class="col-sm-2"> <label asp-for="Price"></label> </dt> <dd class="col-sm-10"> @Model.Price.ToString("C") </dd> <dt class="col-sm-2"> <label asp-for="PublishDate"></label> </dt> <dd class="col-sm-10"> @Model.PublishDate.ToShortDateString() </dd> </dl> </div> <div> <a asp-action="Edit" asp-route-id="@Model.Id">Edit</a>| <a asp-action="Index">Back to List</a> </div> |
ما این view را شبیه به Index View ایجاد کردیم. با این تفاوت که model اینجا از نوع Book
است. برای دریافت نام property، میتوانیم از ویژگی asp-for استفاده کنیم:
1 |
<label asp-for="Title"></label> |
برای نمایش مقادیر property، میتوانیم با استفاده از Model
directive@ به خصوصیات درون model دسترسی داشته باشیم:
1 2 3 |
<dd class="col-sm-10"> @Model.Title </dd> |
Edit View
به محض اینکه کار با Details view تمام شد، میتوانیم کار خود را با ایجاد Edit view ادامه دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
@model WorkingWithViews.Models.Book @{ ViewData["Title"] = "Edit"; } <h1>Edit</h1> <h4>Book</h4> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="Edit"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <input type="hidden" asp-for="Id" /> <div class="form-group"> <label asp-for="Title" class="control-label"></label> <input asp-for="Title" class="form-control" /> <span asp-validation-for="Title" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Genre" class="control-label"></label> <input asp-for="Genre" class="form-control" /> <span asp-validation-for="Genre" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Price" class="control-label"></label> <input asp-for="Price" class="form-control" /> <span asp-validation-for="Price" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="PublishDate" class="control-label"></label> <input asp-for="PublishDate" class="form-control" /> <span asp-validation-for="PublishDate" class="text-danger"></span> </div> <div class="form-group"> <input type="submit" value="Save" class="btn btn-primary" /> </div> </form> </div> </div> <div> <a asp-action="Index">Back to List</a> </div> |
برای edit view نیز model از نوع Book است.
asp-validation-summary
tag helper برای نمایش خلاصه پیغامهای اعتبارسنجی به کار میرود:
1 |
<div asp-validation-summary="ModelOnly" class="text-danger"></div> |
ValidationSummary.ModelOnly فقط پیغامهای اعتبارسنجی که در سطح model اعمال میشود را نمایش میدهد. ValidationSummary.All پیغامهای اعتبارسنجی در هر دو سطح property و model را نمایش میدهد.
برای هر property، یک label
برای نمایش نام property، یک فیلد input
برای ویرایش مقدارو یک عنصر span
برای نمایش پیغامهای اعتبارسنجی مربوط به همان property ایجاد کرده ایم:
1 2 3 |
<label asp-for="Title" class="control-label"></label> <input asp-for="Title" class="form-control" /> <span asp-validation-for="Title" class="text-danger"></span> |
ما یک دکمه برای submit کردن form در قسمت پایین View داریم:
1 |
<input type="submit" value="Save" class="btn btn-primary" /> |
وقتی میخواهیم صفحه را بدون ارائه مقادیر معتبر ذخیره کنیم، بسته به تنظیماتی که برای asp-validation-summary ارائه میکنیم، خطاهای اعتبارسنجی نمایش داده میشوند. بنابراین، بیایید این را در عمل ببینیم.
وقتی مقدار را با ModelOnly تنظیم میکنیم:
زمانیکه مقدار را به All تنظیم میکنیم:
تمام. ما view ها را برای صفحات Index
, Details
و Edit
ایجاد کردیم.
مفهوم Partial View ها
یک partial view، یک فایل Razor markup (.cshtml
) است که خروجی HTML را به داخل خروجی render شده در view دیگر تولید میکند.
Partial view ها به طور قابل توجهی در دو نوع سناریو مورد استفاده قرار میگیرند. اولین سناریو زمانی است که میخواهیم فایلهای markup بزرگ را به component های کوچکتر تقسیم کنیم. اگر فایل markup ما، بزرگ، پیچیده و از چند قطعه منطقی تشکیل شده است، بهتر است که هر قطعه را به یک partial view تقسیم کنیم. سپس کد در فایل markup قابل مدیریت میشود، زیرا markup فقط شامل ساختار کلی صفحه و شامل reference به partial view ها خواهد بود.
سناریوی دوم زمانی است که میخواهیم تکراری بودن محتوای رایج در بین فایلهای markup را کاهش دهیم. هنگامی که ما نیاز به استفاده از عناصر نشانه گذاری یکسان در فایل های markup داریم، می توانیم آن محتوای markup را به داخل یک partial view منتقل کرده و از آن در view ها استفاده مجدد کنیم. به این ترتیب تغییرات آتی در آن markup باید فقط در یک مکان انجام شود و با این کار، ما ماژولار بودن کد خود را بهبود میبخشیم.
با این حال، Partial view، رویکرد توصیه شده ای برای حفظ عناصر مشترک در طرح بندی نمیباشد. ما بهترین روش برای ایجاد عناصر مشترک در طرح بندی را در بخش بعدی یاد خواهیم گرفت.
حالا فرض کنید که میخواهیم اطلاعات نویسندگان را در چند مکان در برنامه BookStore
خود نمایش دهیم. ایجاد یک partial view برای نمایش اطلاعات نویسنده، رویکرد ایده آلی خواهد بود.
روی پوشه Shared
راست کلیک کرده و Add
-> View
را انتخاب کنید:
در دیالوگ باکس Add MVC View، میخواهیم نام View را Authors_ بگذاریم، سپس گزینه Create as a Partial View را تیک بزنید و بر روی Add کلیک کنید:
بیایید مقداری متن ساختگی به فایل authors.cshtml_ اضافه کنیم:
1 2 3 |
<h3>Authors</h3> <p>This section is used to display information about authors.</p> |
حالا با استفاده از partial
tag helper، این partial view را به داخل details view کتاب اضافه کنیم:
1 2 3 |
<div> <partial name="_Authors" /> </div> |
تمام. حالا میتوانیم ببینیم که صفحه جزییات کتاب، قسمت نویسندگان را نیز نمایش میدهد:
ما فقط با جای گذاری این partial view به داخل view های دیگر، میتوانیم این قسمت نویسندگان را در آنها نیز نمایش دهیم.
در این قسمت، ما یاد گرفتیم که چطور یک partial view را ایجاد کنیم و چطور از آن داخل یک view استفاده کنیم.
Layout ها در ASP.NET Core
اکثر برنامه های تحت وب دارای یک طرح بندی مشترک هستند که در حین حرکت بین صفحات، تجربه ای ثابت را برای کاربر فراهم می کند. در یک اپلیکیشن ASP.NET Core MVC، برای ارائه یک همچین تجربه ای بین صفحات، ما از یک فایل layout استفاده میکنیم.
layout به طور نمونه، شامل عناصر مشترک رابط کاربری همانند header, menu و یک footer است. خیلی از اپلیکیشنها، منابع رایجی همانند script ها و stylesheet ها را با یکدیگر به اشتراک میگذارند. ما تمام این عناصر مشترک را میتوانیم در یک فایل layout تعریف کنیم که میتواند به هر view داخل اپلیکیشن ارجاع داده شود. Layout ها به کاهش تکرار کد در view ها کمک میکند.
وقتی ما با استفاده از template پیشفرض ارائه شده توسط ویژوال استودیو، یک اپلیکیشن ASP.Net Core MVC را ایجاد میکنیم، آن یک فایل layout پیشفرض (Layout.cshtml_
) تولید میکند و آن را به داخل پوشه Shared
قرار میدهد. در حین ایجاد view ها، ما یک option برای مشخص کردن یک فایل layout داریم. ما بعدا میتونیم آن را با تنظیم ویژگی Layout
داخل View تغییر دهیم:
حالا فایل layout پیشفرض را با یکدیگر بررسی میکنیم.
فایل layout، شامل یک بخش <head> در قسمت بالای layout است که شامل link ،Title به stylesheet و غیره است:
1 2 3 4 5 6 |
<head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - WorkingWithViews</title> <link rel="stylesheet" href="~/css/site.css" /> </head> |
سپس یک بخش <body> داریم که شامل یک header به همراه menu است. body همچنین یک container div
دارد که داخل آن، متد ()RenderBody فراخوانی میشود. این متد، صفحه content را render میکند:
1 2 3 4 5 6 |
<div class="container"> <partial name="_CookieConsentPartial" /> <main role="main" class="pb-3"> @RenderBody() </main> </div> |
پس از آن یک بخش <footer> وجود دارد.
ما معمولاً script ها را در انتهای سند بارگذاری می کنیم تا اطمینان حاصل کنیم که همه وابستگی ها بارگیری می شوند:
1 2 |
<script src="~/js/site.js" asp-append-version="true"></script> @RenderSection("Scripts", required: false) |
در این بخش، یاد گرفتیم که چگونه با استفاده از یک فایل Layout، ظاهر و شکل یکسانی را برای برنامه خود حفظ کنیم.
نتیجه گیری
در این مقاله، ما عناوین زیر را بررسی کردیم:
- استفاده از Razor Markup برای ساخت View ها
- استفاده مجدد از قسمتهایی از صفحه با استفاده از Partial View ها
- ایجاد یک شکل و ظاهر مشترک برای اپلیکیشن با استفاده از فایلهای Layout
در قسمت بعدی این سری آموزشی، ما مدیریت state در ASP.NET Core MVC را بررسی خواهیم کرد.