ما هي View Components

-

بنتعرف في هذا الدرس على View Components والتي يمكن تعريفها على انها C# classes بحيث يمكن استدعاؤها من View. تساعد View Components في دعم Views من خلال تزويدهم بالبيانات. وبالتالي تساعد في تضمين واستخدام محتويات معقدة في Views.

بعض الأمثلة حيث يمكنك استخدام View Components هي:

  • لوحات المصادقة Authentication Panels في المواقع التي يمكن للمستخدمين من خلالها تسجيل الدخول دون زيارة صفحة منفصلة..
  • إنشاء نظام ملاحة navigation ديناميكي للموقع يتغير حسب دور المستخدم.
  • Shopping Cart panels التي تعرض المنتجات الموجودة حاليًا في سلة التسوق.

تمام وحتي نفهم الموضوع وكالعاده نبدأ بتطبيق مثال:

انشاء View Component

View Components هي مجرد C# classes مشتقة من ViewComponent class المعرفة في Microsoft.AspNetCore.Mvc namespace.

يجب أن يحتوي View Component  على Method باسم ()Invoke والي بتعمل على إرجاع بعض أشكال البيانات إلىView.

يمكن إنشاء View Component في أي مكان في التطبيق ولكن وفقًا لconventions، يجب إنشاؤها داخل مجلد Components  الموجود في root folder للتطبيق.

لإنشاء View Component يجب انشاء مجلد باسم Components في المشروع، لذا انقر بزر الماوس الأيمن فوق project file وحدد Add ➤ New Folder ثم قم بتسمية هذا المجلد باسم Components. ثم انشى class داخل هذا المجلد وقم بتسميته Cart.cs. وما تنسى اشتقاق هذه class من class الأساسية المسميه ViewComponent، وبعدها بنضف Invoke method إليها. الكود التالي يوضح ذلك:

المثال الي بنعملو الان هو فكره بسيطة لكيفية تطبيق سلة المشتريات في التطبيق

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StudentsAcademy.Components
{
    public class Cart : ViewComponent
    {
        public string Invoke()
        {
            return "This is from View Component";
        }

    }
}

لاستدعاء View Component من View، استخدم Razor expression  التالي الذي سيستدعي طريقة Invoke لمكونات View:
@await Component.InvokeAsync("NameofViewComponent") 

تمام وحتي نطبق هذا الكلام ننتقل الان الى ملف Layout.cshtml_ وبعدها نضيف الكود التالي 

@await Component.InvokeAsync("Cart") 
طبعا استخدمنا هذا الكود في صفحة  Layout.cshtml_ لان سلة المشتريات دايما بتكون موجوده في الصفحة الرئيسية امام المستخدم واي تعديل لازم ينعكس عليها مباشرة.
الكود الخاص بصفحة  Layout.cshtml_ بكون كالتالي :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - StudentsAcademy</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">StudentsAcademy</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                        </li>
                    </ul>
                </div>
                @await Component.InvokeAsync("Cart")
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>
    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - StudentsAcademy - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

تمام لاحظ ان هذا ViewComponent المسمى Cart تم وضعه في Header.
طيب الان صار وقت تشغيل التطبيق 
قم بتشغيل التطبيق ستكون نتيجة التنفيذ كما هو واضح في الصورة في الأسفل. 


وبهاي الطريقة يمكن استخدام سلة المشتريات مثلا في هذا المكان، وارجاع مكونات السلة من ملف Cart.

ارجاع اناع مختلفة من Return Types of View Components

في القسم السابق، عملنا على بإرجاع متغير من نوع string في View Components. ويمكن أيضًا أن تقوم " View Components " بإرجاع النوع   IViewComponentResult interface من أسلوب Invoke.
باستخدام IViewComponentResult interface ، يمكنك استخدام طريقة Invoke الخاصة بـ View Components لإرجاع اناع مثل string ، و html ، و partial view.
لإرجاع أنواع مختلفة من View Components مثل string, html and partial view   نحتاج الى استخدام classes  وفي الجدول التالي نوضح ايش هي الاناع التي تستخدم مع ViewComponent وشو هي Types  الي بترجع مع كل نوع

النوع في ViewComponent Types   الي بترجع
ContentViewComponentResult

تستخدم لارجاع انع  encoded HTML.

مثل:

 Content("<h2>some text</h2>")

HtmlContentViewComponentResult

تستخدم لارجاع انع  without encoding.

مثل

HtmlContentViewComponentResult("<h2>some text</h2>")

ViewViewComponentResult

تستخدم لارجاع انع  Partial View with optional view model data.

مثل:

View("NameofView")

وحتي نفهم الموضوع نبدا بمثال عملي بالتفصيل لكل نوع منهم :

 ContentViewComponentResult

يتم استخدام class المسمى ContentViewComponentResult لإعادة encoded HTML من View Components. حيث يتم إنشاء Instances لهذا  ContentViewComponentResult class باستخدام طريقة ()Content .
لتوضيح ذلك. قم بتغيير Invoke method  في  Cart View Component’ كما هو موضح أدناه.

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StudentsAcademy.Components
{
    public class Cart : ViewComponent
    {
        //public string Invoke()
        //{
        //    return "This is from View Component, ";
        //}
        public IViewComponentResult Invoke()
        {
            return Content("This is from <h2>View Component</h2>");
        }
    }
}

لاحظ ان هون غيرنا نوع الإرجاع ل Invoke method إلى IViewComponentResult interface. ثم عملنا  encoded HTML  باستخدام طريقة ()Content :

Content("This is from <h2>View Component</h2>")

اعد تشغيل التطبيق ستكون نتيجة التنفيذ : 



إذا قمت بالتحقق من page source ، فستجد أن HTML تم ترميزه Content method مثل:
الهدف من encoding of HTML في ASP.NET Core MVC هو اضافة نوع من الحمياة للصفحات بحيث يمنع المتسللين من إضافة نصوص غير مرغوب فيها إلى موقع الويب.

 HtmlContentViewComponentResult
يتم استخدام class المسمى  HtmlContentViewComponentResult لإرجاع " non-encoded HTML " من View Components.
لفهم هذا الاشي خلونا نغيير Cart View Component  بحيث يقوم بإرجاع instance جديد لفئة HtmlContentViewComponentResult. هذا موضح في الكود أدناه:

using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StudentsAcademy.Components
{
    public class Cart : ViewComponent
    {
        //public string Invoke()
        //{
        //    return "This is from View Component, ";
        //}
        //public IViewComponentResult Invoke()
        //{
        //    return Content("This is from <h2>View Component</h2>");
        //}
        public IViewComponentResult Invoke()
        {
            return new HtmlContentViewComponentResult(new HtmlString("This is from <h2>View Component</h2>"));
        }
    }
}
اعد تشغيل التطبيق ستكون النتيجة : 




استخدم هذه الطريقة عندما تكون متأكدًا بنسبة 100٪ من أن مخرجاتك المرتجعة ستكون آمنة ولن يتم العبث بها.

Returning Partial View
يمكنك أيضًا إرجاع Partial View من View Component. وحتي نعمل ذلك سنقوم باستخدام ViewViewComponentResult class وهي مسؤولة عن إخبار ASP.NET Core بارجاع render the Partial View. توفر ViewComponent base class طريقة  ()View لإنشاء  ViewViewComponentResult class objects.
يوجد 4 طريق لاستخدام View وهي 

View(); // Call default partial view
View(model); // Call default partial view and send model to it
View("ViewName"); // Call partial view by name
View("ViewName ", model); // Call partial view by name and send model to it. 
ويتم البحث في الأماكن التالية عند استدعاء Partial View

/Views/{controller}/Components/{view component}/{partial view name}
/Views/Shared/Components/{view component}/{partial view name}

controller هي التي تتعامل مع HTTP request.
• إذا لم تحدد view name في View() method ، فسيتم اعتبار { partial view name } Default.cshtml.
تمام وحتي نفهم الموضوع نبدأ بإنشاء View Component  لاستدعاء Partial View 
ننتقل الى الملف CoursesModel.cs الموجود داخل المجلد Models  حيث يحتوي على الكود التالي:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StudentsAcademy.Models
{
    public class CoursesModel
    {
        public string CourseNumber { get; set; }
        public string CourseName { get; set; }
        public string CourseDescription { get; set; }
        public decimal Price { get; set; }
        public int Capacity { get; set; }
    }
}
الآن قم بتحديث Cart View Component  لإرجاع View يحتوي على المواد وبنرجعها في model من نوع CoursesModel.

using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using StudentsAcademy.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StudentsAcademy.Components
{
    public class Cart : ViewComponent
    {
        public IViewComponentResult Invoke()
        {
            CoursesModel[] Couurses = new CoursesModel[] {
                new CoursesModel() { CourseNumber = "0001",CourseName="Math",CourseDescription="Math Desc",Capacity=20, Price = 115 },
                new CoursesModel() { CourseNumber = "0002",CourseName="Sciences",CourseDescription="Sciences Desc",Capacity=30, Price = 120 },
                new CoursesModel() { CourseNumber = "0003",CourseName="Physics",CourseDescription="Physics Desc",Capacity=25, Price = 140 },
               
            };
            return View(Couurses);
        }
    }
}
قم بتشغيل التطبيق واكيد بتكون النتيجة خطأ كما في الصورة في الأسفل:




تمام نفهم سبب هذا الخطأ:
يبدأ HomeController  هنا بطلب HTTP Request  بحيث يعمل استدعاء ل View باسم Cart وبما ان عملنا تعديل على هذه ViewComponent  بحيث ترجع View. وبسبب عدم انشاء هذه View لحد الان في التطبيق وكمان ما حددنا اسم Partial View في Cart View() method.
بناء على ذلك سيقوم MVC بالبحث عن View Partial باسم Default.cshtml في الأماكن التالية:
/Views/Home/Components/Cart/Default.cshtml
/Views/Shared/Components/Cart/Default.cshtml
وبما ان كمان ما عندنا view بهذا الاسم في الطبيق فا اكيد برجع الخطأ الي فوق.
تمام لحل هذه المشكلة نحتاج الى إنشاء هذا partial view. لذا قم بإنشاء View يسمى Default.cshtml في المجلد
/Views/Home/Components/Cart/ 

بعد انشاء هذا ال view بكون شكل التطبيق :




بعد الاضافة نضيف الكود التالي :
@model CoursesModel[]
<table style="width:50%">
    <tr>
        <td><u>Product Name</u></td>
        <td><u>Price</u></td>
    </tr>
    @{
        foreach (CoursesModel p in Model)
        {
        <tr>
            <td>@p.CourseNumber</td>
            <td>@p.CourseName</td>
            <td>@p.CourseDescription</td>
            <td>@p.Price</td>
        </tr>
        }
    }
</table>
قم بتشغيل التطبيق بالانتقال الى الرابط:
https://localhost:44382/
ستكون النتيجة:


طبعا اكيد مثل هيك نتيجة بكل المقايس خطأ، لا يمكن عرض مثل هذه المخرجات في هذا المكان. مشان هيك خلونا نعمل تعديل بحيث نقوم الان بارجاع مجموع اسعار المواد الموجود في السله وعرضها بالصفحة على شكل Cart

تمام الان ننتقل الى ملف cart.cs ثم قم بتعديل الكود الى الآتي : 

using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using StudentsAcademy.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StudentsAcademy.Components
{
    public class Cart : ViewComponent
    {
        public IViewComponentResult Invoke()
        {
            CoursesModel[] Couurses = new CoursesModel[] {
                new CoursesModel() { CourseNumber = "0001",CourseName="Math",CourseDescription="Math Desc",Capacity=20, Price = 115 },
                new CoursesModel() { CourseNumber = "0002",CourseName="Sciences",CourseDescription="Sciences Desc",Capacity=30, Price = 120 },
                new CoursesModel() { CourseNumber = "0003",CourseName="Physics",CourseDescription="Physics Desc",Capacity=25, Price = 140 },
               
            };
            return View("Default",Couurses.Sum(o => o.Price).ToString());
        }
    }
}

قمنا بحساب مجموع المواد في آخر سطر، ثم تم استدعاء Partial View  Default وارسلنا لها مجموع المواد كمتغير. من خلال سطر الكود : 

return View("Default",Couurses.Sum(o => o.Price).ToString());

واكيد بنحتاج نعدل Partial View لذا قم بتعديل الكود الى التالي : 

@model string
Total Cart (@Model)

الآن قم بتشغيل التطبيق بالانتقال الى الرابط
https://localhost:44382/ 
 ستكون النتيجة


يمكنك الوصول إلى جميع الخصائص مثل Request و ViewBag و RouteData داخل View Component تمامًا مثل Controllers. على سبيل المثال،
من هذا الكود:
string target = RouteData.Values["id"] 
حيث نعمل هنا على تعريف متغير من نوع string نستقبل فيه قيمة ID من URL  المرسل. بنفس الطريقة، يمكنك أيضًا استخدام ميزة Dependency Injection في ASP.NET Core في View Component.