روش رندر یک View در ASP.NET MVC، بر مبنای اطلاعاتی است که از کنترلر، در اختیار View آن قرار میگیرد. اما گاهی از اوقات نیاز است بعضی از قسمتهای صفحه همواره نمایش داده شوند (مانند نمایش تعداد کاربران آنلاین، سخن روز، منوهای کنار صفحه و امثال آن). یک راه حل برای این مساله، اضافه کردن اطلاعات مورد نیاز View در ViewModel ارائه شدهی توسط کنترلر است. هرچند این روش کار میکند اما پس از مدتی به ViewModel هایی خواهیم رسید که تشکیل شدهاند از چندین و چند خاصیت اضافی که الزاما مرتبط با تعریف آن ViewModel نیستند. راه حل بهتر، قرار دادن قسمتهای مشترک صفحات در فایل layout برنامه است؛ اما فایل layout، به سادگی نمیتواند از دایرکتیو model@ برای مشخص سازی مدل و یا مدلهای مورد نیاز خود استفاده کند (هر چند ممکن است؛ اما بیش از اندازه پیچیده خواهد شد).
در نگارشهای پیشین ASP.NET MVC، یک چنین مسائلی را با معرفی Child Actionها
public partial class SidebarMenuController : Controller
{
const int Min15 = 900;
[ChildActionOnly]
[OutputCache(Duration = Min15)]
public virtual ActionResult Index()
{
return PartialView("_SidebarMenu");
}
}
و سپس نمایش آنها توسط Html.RenderAction در فایل layout برنامه، حل میکنند. در ASP.NET Core، جایگزین Child Actionها، مفهوم جدیدی است به نام View Components.
یک مثال: تهیهی اولین View Component
ساختار یک View Component، بسیار شبیه است به ساختار یک Controller، اما با عملکردی محدود. به همین جهت کار تعریف آن با افزودن یک کلاس سیشارپ شروع میشود و این کلاس را میتوان در پوشهای به نام ViewComponents در ریشهی پروژه قرار داد (اختیاری).
سپس برای نمونه، کلاس ذیل را به این پوشه اضافه کنید:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Core1RtmEmptyTest.Services;
namespace Core1RtmEmptyTest.ViewComponents
{
public class SiteCopyright : ViewComponent
{
private readonly IMessagesService _messagesService;
public SiteCopyright(IMessagesService messagesService)
{
_messagesService = messagesService;
}
public IViewComponentResult Invoke(int numberToTake)
{
var name = _messagesService.GetSiteName();
return View(viewName: "Default", model: name);
}
//public async Task<IViewComponentResult> InvokeAsync(int numberToTake)
//{
// return View();
//}
}
}
همانطور که
پیشتر نیز عنوان شد، تزریق وابستگیها در تمام قسمتهای ASP.NET Core در دسترس هستند. در اینجا نیز از سرویس MessagesService بررسی شدهی در مطلب «
ارتقاء به ASP.NET Core 1.0 - قسمت 6 - سرویسها و تزریق وابستگیها» برای نمایش نام سایت استفاده میکنیم.
ساختار کلی یک کلاس ViewComponent شامل دو جزء اصلی است:
الف) از کلاس پایه ViewComponent مشتق میشود. به این ترتیب توسط ASP.NET Core قابل شناسایی خواهد شد.
ب) دارای متد Invoke ایی است که بجای Html.RenderAction در نگارشهای پیشین ASP.NET MVC، قابل فراخوانی است. این متد یک View را باز میگرداند.
ج) در اینجا امکان تعریف نمونهی Async متد Invoke نیز وجود دارد (برای مثال جهت کار با متدهای Async بانک اطلاعاتی).
روش فراخوانی این متدها نیز به این صورت است: ابتدا به دنبال نمونهی async میگردد. اگر یافت شد، همینجا کار خاتمه مییابد. اگر یافت نشد، نمونهی sync یا معمولی آن فراخوانی میشود و اگر این هم یافت نشد، یک استثناء صادر خواهد شد.
د) متد Invoke میتواند دارای پارامترهای دلخواهی نیز باشد و حالت پیش فرض آن بدون پارامتر است.
روش یافتن یک view component توسط ASP.NET Core به این صورت است:
الف) این کلاس باید عمومی بوده و همچنین abstract نباشد.
ب) «یکی» از مشخصههای ذیل را داشته باشد:
1) نامش به ViewComponent ختم شده باشد.
2) از کلاس ViewComponent ارث بری کرده باشد.
3) با ویژگی ViewComponent مزین شده باشد.
نحوه و محل تعریف View یک View Component
پس از تعریف کلاس ViewComponent مورد نظر، اکنون نیاز است View آنرا اضافه کرد. روش یافتن این Viewها توسط ASP.NET Core نیز بر این مبنا است که
الف) اگر این View Component عمومی و سراسری است، باید درون پوشهی shared، پوشهی جدیدی را به نام Components ایجاد کرده و سپس ذیل این پوشه، بر اساس نام کلاس ViewComponent، یک زیر پوشهی دیگر را ایجاد و داخل آن، View مدنظر را اضافه کرد (تصویر ذیل).
/Views/Shared/Components/[NameOfComponent]/Default.cshtml
ب) اگر این View Component تنها باید از طریق Viewهای یک کنترلر خاص قابل دسترسی باشند، زیر پوشهی Component یاد شده را ذیل پوشهی View همان کنترلر قرار دهید (و آنرا از قسمت Shared خارج کنید).
/Views/[CurrentController]/Components/[NameOfComponent]/Default.cshtml
یک نکته: اگر نام کلاسی به ViewComponent ختم شده بود، نیازی نیست تا ViewComponent را هم در حین ساخت پوشهی آن ذکر کرد.
نحوهی استفادهی از View Component تعریف شده و ارسال پارامتر به آن
و در آخر برای استفادهی از این View Component تعریف شده، به فایل layout برنامه مراجعه کرده و آنرا به نحو ذیل فراخوانی کنید:
<footer>
<p>@await Component.InvokeAsync("SiteCopyright", new { numberToTake = 5 })</p>
</footer>
اولین پارامتر متد InvokeAsync، همان نام کلاس View Component است. اگر خواستید پارامتر(های) دلخواهی را به متد Invoke کلاس View Component ارسال کنید (مانند پارامتر int numberToTake در مثال فوق)، آنرا در همینجا میتوان ذکر کرد (با فرمت dictionary و یا anonymous type).
یک نکته: متدهای قدیمی Component.Invoke و Component.Renderدر اینجا حذف شدهاند (اگر مقالات پیش از RTM را مطالعه کردید) و روش توصیه شدهی در اینجا، کار با متدهای async است.
تفاوتهای View Components با Child Actions نگارشهای پیشین ASP.NET MVC
پارامترهای یک View Component از طریق یک HTTP Request تامین نمیشوند و همانطور که ملاحظه کردید در همان زمان فراخوانی آنها به صورت مستقیم فراهم خواهند شد. بنابراین مباحث model binding در اینجا دیگر وجود خارجی ندارند. همچنین View Components جزئی از طول عمر یک کنترلر نیستند. بنابراین اکشن فیلترهای مختلف تعریف شده، تاثیری را بر روی آنها نخواهند داشت (این مشکلی بود که با Child Actions در نگارشهای قبلی مشاهده میشد). همچنین View Components به صورت مستقیم از طریق درخواستهای HTTP قابل دسترسی نیستند. به علاوه Child actions قدیمی، از فراخوانیهای async پشتیبانی نمیکنند.
زمانیکه کلاسی از کلاس پایه ViewComponent ارث بری میکند، تنها به این خواص عمومی از درخواست HTTP جاری دسترسی خواهد داشت:
[ViewComponent]
public abstract class ViewComponent
{
protected ViewComponent();
public HttpContext HttpContext { get; }
public ModelStateDictionary ModelState { get; }
public HttpRequest Request { get; }
public RouteData RouteData { get; }
public IUrlHelper Url { get; set; }
public IPrincipal User { get; }
[Dynamic]
public dynamic ViewBag { get; }
[ViewComponentContext]
public ViewComponentContext ViewComponentContext { get; set; }
public ViewContext ViewContext { get; }
public ViewDataDictionary ViewData { get; }
public ICompositeViewEngine ViewEngine { get; set; }
//...
}
فراخوانی Ajax ایی یک View Component
در ASP.NET Core، یک اکشن متد میتواند خروجی ViewComponent نیز داشته باشد و این تنها روشی است که میتوان یک View Component را از طریق درخواستهای HTTP، مستقیما قابل دسترسی کرد:
public IActionResult AddURLTest()
{
return ViewComponent("AddURL");
}
در این حالت میتوان این اکشن متد را به صورت Ajax ایی نیز بارگذاری و به صفحه اضافه کرد:
$(document).ready (function(){
$("#LoadSignIn").click(function(){
$('#UserControl').load("/Home/AddURLTest");
});
});
امکان بارگذاری View Components از اسمبلیهای دیگر نیز وجود دارد
در مطلب «
ارتقاء به ASP.NET Core 1.0 - قسمت 10 - بررسی تغییرات Viewها» روش دسترسی به Viewهای برنامه را که در اسمبلی آن قرار گرفته بودند، بررسی کردیم. دقیقا همان روش در مورد view components نیز صادق است و کاربرد دارد. جهت یادآوری، این مراحل باید طی شوند:
الف) اسمبلی ثالث حاوی View Componentهای برنامه باید ارجاعاتی را به ASP.NET Core و قابلیتهای Razor آن داشته باشد:
"dependencies": {
"NETStandard.Library": "1.6.0",
"Microsoft.AspNetCore.Mvc": "1.0.0",
"Microsoft.AspNetCore.Razor.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
}
},
"tools": {
"Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final"
}
ب) محل قرارگیری viewهای این اسمبلی ثالث نیز همانند قسمت «نحوه و محل تعریف View یک View Component» مطلب جاری است و تفاوتی نمیکند. فقط برای قرار دادن این Viewها در اسمبلی برنامه باید گزینهی embed را مقدار دهی کرد:
"buildOptions": {
"embed": "Views/**/*.cshtml"
}
ج) مرحلهی آخر هم معرفی این اسمبلی ثالث، به RazorViewEngineOptions به صورت یک EmbeddedFileProvider جدید است. در این مثال، ViewComponentLibrary نام فضای نام این اسمبلی است.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//Get a reference to the assembly that contains the view components
var assembly = typeof(ViewComponentLibrary.ViewComponents.SimpleViewComponent).GetTypeInfo().Assembly;
//Create an EmbeddedFileProvider for that assembly
var embeddedFileProvider = new EmbeddedFileProvider(assembly,"ViewComponentLibrary");
//Add the file provider to the Razor view engine
services.Configure<RazorViewEngineOptions>(options =>
{
options.FileProviders.Add(embeddedFileProvider);
});
د) جهت رفع تداخلات احتمالی این اسمبلی با سایر اسمبلیها بهتر است ویژگی ViewComponent را به همراه نامی مشخص ذکر کرد (در حین تعریف کلاس View Component):
[ViewComponent(Name = "ViewComponentLibrary.Simple")]
public class SimpleViewComponent : ViewComponent
و در آخر فراخوانی این View Component بر اساس این نام صورت خواهد گرفت:
@await Component.InvokeAsync("ViewComponentLibrary.Simple", new { number = 5 })