مقدمه في Built-In Tag Helpers

-

الغرض من Built-In Tag Helpers هو الدمج بين Server-side و Html Elements كما ذكرنا سابقا، وتستخدم داخليًا في ملفات Razor. تستخدم لتنفيذ مجموعة كبيرة من العمليات والاوامرعلى Html Elements . سنناقش هنا هذه Tag Helpers وكيفية ربطها مع Form, Input, Select, Label, Anchor Text Area elements, CSS, JS and Cache.

استخدام Tag Helper في Form Element

تُستخدم Built-in Tag Helpers فيForm Element  لإنشاء action attribute. وهذه Tag Helpers

 هي :


Description
Method
يستخدم لتحديد Action  التي سيتم الربط معها بناءً على نظام التوجيه.إذا تم حذفه ، فسيتم استخدام action في view الحالي.
asp-action
تستخدم لتحديد controller التي سيتم الربط معها بناءً على نظام التوجيه.في حال عدم وجوده، فسيتم استخدام controller في view الحالي.
asp-controller
يستخدم لتحديد اسم المسار المطلوب لتوليد action attribute
asp-route
يستخدم لتحديد قيمة segment الإضافي لعنوان URL، على سبيل المثال ، يتم استخدام asp-route-id  لتقديم قيمة ل id segment.
asp-route-*

يولد رمزًا مخفيًا للتحقق من الطلب لمنع تزوير cross-site الطلبات عبر المواقع. يتم استخدامه مع [ValidateAntiForgeryToken] attribute في HTTP Post action method

asp-antiforgery
يستخدم لتحديد اسم area المستهدفة.
asp-area

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

سنبدأ بأدوات Form Tag Helpers .
استخدام Tag Helpers في Anchor Tag
 يوفر MVC Core  مجموعة من Built-In Tag Helpers  بحيث تستخدم مع Anchor Tag  لغرض إنشاء href attribute وذلك بهدف الربط مع عنوان URL المطلوب،
تستند هذه الاوامر إلى routing system. وفي الجدول أدناه سنعرض بعض هذه الادوات:

الوصفName
تستخدم لتحديد controller المستهدف الذي سيتم ربط URL به
asp-controller
تستخدم لتحديد action المستهدف الذي سيتم ربط URL به
asp-action
تستخدم لتحديد action في حال استخدامها في التطبيق المستهدف الذي سيتم ربط URL به
asp-area
تستخدم لتحديد fragment (جزء من الصفحه) ويتم عرضه باستخدام ‘#’ character
asp-fragment
تستخدم لتحديد route name المستهدف الذي سيتم ربط URL به
as-route

تحدد قيمة المتغيرلإضافيي لعنوان URL الذي سيتم ربط URL به.

مثال asp-route-id = "10" 

يتم توفير القيمة 10 ل id segment الخاص بهذا route.

asp-route-*
إضافة model 

انتقل الى مجلد Models ثم اضف ملف CoursesModel.cs كما في الصورة: 

ثم أضف الكود التالي: 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StudentsAcademy.Models
{
    public class CoursesModel
    {
        public int ID { get; set; }
        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; }
        public DateTime CreatedDate { get; set; }
    }
}
إضافة Controller
انتقل الى مجلد Controllers ثم تأكد من وجود Controller باسم CoursesController كما في الصورة:
تأكد من اضافة Action باسم AllCourses ثم أضف الكود المعلم  التالي اليه:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using StudentsAcademy.Area.StudentPortal.Models;
using StudentsAcademy.Interface;
using StudentsAcademy.Models;
using StudentsAcademy.Repository;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace StudentsAcademy.Controllers
{
    public class CoursesController : Controller
    {
        public IConfiguration Configuration { get; }
        private ICourses Coursesrepository;
        public CoursesController(ICourses repo, IConfiguration configuration)
        {
            Coursesrepository = repo;
            Configuration = configuration;
        }
        public IActionResult Index()
        {
            return View(Coursesrepository.coursesModel);
        }
        public IActionResult AllCourses()
        {
            List<CoursesModel> MyCoursesList = new List<CoursesModel>();
            try
            {
                string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
                using (SqlConnection connection = new SqlConnection(connectionString))
                {
                    DataTable dataTable = new DataTable();
                    string sql = @"select * from courses";
                    SqlCommand command = new SqlCommand(sql, connection);
                    command.CommandType = CommandType.Text;
                    SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
                    // filling records to DataTable
                    dataAdapter.Fill(dataTable);
                    foreach (DataRow item in dataTable.Rows)
                    {
                        MyCoursesList.Add(new CoursesModel
                        {
                            ID = Convert.ToInt32(item["CoursesId"]),
                            CourseNumber = Convert.ToString(item["CourseNumber"]),
                            CourseName = Convert.ToString(item["CourseName"]),
                            CourseDescription = Convert.ToString(item["CourseDescription"]),
                            Price = Convert.ToDecimal(item["Price"]),
                            Capacity = Convert.ToInt32(item["Capacity"]),
                            CreatedDate = Convert.ToDateTime(item["CreatedDate"]),
                        });
                    }
                }
            }
            catch (Exception ex)
            {
            }
            finally
            {
            }
            return View(MyCoursesList);
        }
    }
}
نفهم الكود:
عملنا action يعمل على قراءة الدروس من قاعدة البيانات وعرضها فيView. وعرفنا List  من نوع MyCourses باستخدام الكود :
List<MyCourses> MyCoursesList = new List<MyCourses>();
في الكود التالي بنعمل قراءة المواد من قاعدة البيانات وتخزينها في List باستخدام SqlCommand ثم نقوم بحفظها داخل dataTable باستخدام الكود: 

string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
                using (SqlConnection connection = new SqlConnection(connectionString))
                {
                    DataTable dataTable = new DataTable();
                    string sql = @"select * from courses";
                    SqlCommand command = new SqlCommand(sql, connection);
                    command.CommandType = CommandType.Text;
                    SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
                    // filling records to DataTable
                    dataAdapter.Fill(dataTable);
}
بعد هيك بنخزن هذه البيانات في List   باستخدام الكود:
 
foreach (DataRow item in dataTable.Rows)
                    {
                        MyCoursesList.Add(new MyCourses
                        {
                            ID = Convert.ToInt32(item["CoursesId"]),
                            CourseNumber = Convert.ToString(item["CourseNumber"]),
                            CourseName = Convert.ToString(item["CourseName"]),
                            CourseDescription = Convert.ToString(item["CourseDescription"]),
                            Price = Convert.ToDecimal(item["Price"]),
                            Capacity = Convert.ToInt32(item["Capacity"]),
                            CreatedDate = Convert.ToDateTime(item["CreatedDate"]),
                        });
                    }

أنشاء View لعرض البيانات 

انقر يمين فوق Action المسمى AllCourses ثم اختر Add- View، وبعدها اختر Razer View  كما في الصورة:
بعد الإضافة ستكون النتيجة: 

أضف الكود التالي الى View 

@model IEnumerable<CoursesModel>
@{ ViewBag.Title = "All Courses";}
<h2>All Reservations</h2>
<table class="table table-sm table-striped table-bordered m-2">
    <thead><tr><th>CourseNumber</th><th>CourseName</th><th>Course Description</th><th>Course Price</th></tr></thead>
    <tbody>
        @if (Model != null)
        {
            foreach (var r in Model)
            {
                <tr>
                    <td>@r.CourseNumber</td>
                    <td>@r.CourseName</td>
                    <td>@r.CourseDescription</td>
                    <td>@r.Price</td>
                </tr>
            }
        }
    </tbody>
</table>

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



سنقوم الان بإضافة زر لغرض استدعاء صفحة لإضافة كورس جديد:

أضف الكود التالي داخل هذا View: 
<a  asp-action="AddCourse"  class="btn btn-success" >Add New Course</a>
يعمل هذا الكود على استدعاء action باسم AddCourse باستخدام Tag Helper المسمى asp-action الكود الذي يعمل على ذلك: 
asp-action="AddCourse"  
يعمل هذا الكود على استدعاء action من داخل نفس controller اذا لم يتم تحديد controller  بشكل صريح في الرابط
لنقم بتشغيل التطبيق بالانتقال الى الرابط 
https://localhost:44382/Courses/AllCourses

ستكون النتيجة: 



لاحظ  تكوين الرابط حيث يتم الربط مع AddCourse في Courses Controller. 

إضافة Action باسم AddCourse 
انتقل الى CoursesController واضفaction  جديد باسم AddCourse ثم أضف الكود التالي: 

           // HTTP GET VERSION
        public IActionResult AddCourse()
        {
            return View();
        }
        [HttpPost]
        public IActionResult AddCourse(CoursesModel coursesModel)
        {
            string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                string sql = $"set dateformat dmy  Insert Into Courses (CourseName,CourseDescription,Price,Capacity,CreatedDate) Values (@CourseName,@CourseDescription,@Price,@Capacity,@CreatedDate)";
                using (SqlCommand command = new SqlCommand(sql, connection))
                {
                    command.CommandType = CommandType.Text;
                    // adding parameters
                    SqlParameter parameter = new SqlParameter
                    {
                        ParameterName = "@CourseName",
                        Value = coursesModel.CourseName,
                        SqlDbType = SqlDbType.NVarChar,
                        Size = 200
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@CourseDescription",
                        Value = coursesModel.CourseDescription,
                        SqlDbType = SqlDbType.NVarChar
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Price",
                        Value = coursesModel.Price,
                        SqlDbType = SqlDbType.Int
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Capacity",
                        Value = coursesModel.Capacity,
                        SqlDbType = SqlDbType.Int
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@CreatedDate",
                        Value = DateTime.Now,
                        SqlDbType = SqlDbType.DateTime
                    };
                    command.Parameters.Add(parameter);
                    
                    connection.Open();
                    command.ExecuteNonQuery();
                    ViewBag.Result = "Success";
                    connection.Close();
                }
            }
            return View("AllCourses");
        }
لاحظ انه تم انشاء 2 action  باسم AddCourse الأول لغاية فتح View من نوع HttpGet والثاني لغاية حفظ البيانات من نوع [HttpPost]
إضافة View 
نحتاج الى إضافة View باسم AddCourse لغرض ادخال البيانات الخاصة بالكورس ثم حفظها. 
تأكد من إضافة View باسم AddCourse داخل مجلد Courses، ثم أضف الكود التالي إليه:

@model CoursesModel
@{
    ViewData["Title"] = "Add New Course";
}
<h2>Add New Course</h2>
<form method="post" asp-controller="Courses" asp-action="AddCourse">
    <div class="form-group">
        <label for="CourseName">Course Name:</label>
        <input class="form-control" name="CourseName" />
    </div>
    <div class="form-group">
        <label for="CourseDescription">Course Description:</label>
        <input class="form-control" name="CourseDescription" />
    </div>
    <div class="form-group">
        <label for="Price">Price:</label>
        <input class="form-control" name="Price" />
    </div>
    <div class="form-group">
        <label for="Capacity">Capacity:</label>
        <input class="form-control" name="Capacity" />
    </div>
    <button type="submit" class="btn btn-primary">Add</button>
</form>

 في هذا View قمنا بإنشاء form tag لاحظ اننا استخدامنا الأكواد لإنشاء action attribute ب form.
 asp-controller="Courses", asp-action="AddCourse"
يحتوي form على طريقة من نوع post، وهذا يعني أنه عندما يتم عمل  submitted ل form بالنقر فوق زر Add، سيتم استدعاء AddCourse Action  من النوع HttpPost  الذي تم تعريفه سابقه.
تم استخدام HTML  elements في هذا view ثم قمنا بتطبيق tag helper attribute على هذه elements
لنأخذ مثال: 

<div class="form-group">
        <label for="CourseName">Course Name:</label>
        <input class="form-control" name="CourseName" />
</div>
تم ربط اسم الكورس ب html Label element باستخدام الامر
 for="CourseName"
هذه الخصائص يتم قراءتها من ملف CoursesModel.css
وقمنا أيضا بربط اسم الكورس مع input element باستخدام الامر
name="CourseName". 
سيقوم MVC تلقائيًا بربط parameter  ب طريقة AddCourse Action HttpPost
هذه هي التقنية تسمى Model Binding وهي مفيدة جدًا في إنشاء أكواد عالية الجودة.
وهكذا بالنسبة لبقية الخصائص في الصفحة.

قم بتشغيل التطبيق واكيد بتكون النتيجة: 



ثم انقر فوق Add New Course سيتم فتح صفحة جديده كما في الصورة: 


بعد ذلك، قم بملء form وانقر فوق الزر "Add".
في حال تم نجاح العميلة سيتم الرجوع الى صفحة جميع المواد مع سجل جديد كما في الصورة: 


يمكن الاستغناء عن asp-action and asp-controller tag helpers في هذه الحالة لان هذا Form موجود داخل Add Course ولن يستدعي أي action اخر في مكان آخر. لذلك سيستدعي بشكل تلقاي AddCourse action فقط. يمكن الاكتفاء فقط بكتابة السطر التالي داخل Form
<form method="post">
....
</form>
لم نقم باي عملة تحقق هنا validation  ، لكيفية اجراء التحقق في MVC Core للمزيد انتقل الى الرابط: 

استخدام asp-antiforgery Tag Helper

يعمل asp-antiforgery Tag Helper على انشاء رمزًا Token  مخفيًا للتحقق من الطلب لمنع تزوير الطلبات عبر المواقع (CSRF) cross-site request forgery (CSRF). عند استخدام هذه السمة في Form element ، فإن ASP.NET Core MVC يقوم بأمرين:
  • يضيف security token في hidden input element إلى form.
  • يضيف ملف cookie إلى response.
سيقوم التطبيق بمعالجة الطلب فقط إذا كان يحتوي على cookie and hidden value من form، والتي لا يمكن للمواقع الخبيثة والضارة malicious الوصول إليها ، وبالتالي يتم منع CSRF.
لاستخدام هذه الميزة ، كل ما عليك فعله هو أضافة
 tag helper – asp-antiforgery="true"
إلى form element. ومن ثم إضافة attribute [ValidateAntiForgeryToken]  في Action.
لتطبيق ذلك بشكل عملي انتقل الى AddCourse View ومن ثم قم بتعديل الكود لإضافة هذه الميزة كما هو واضح في الاسفل

@model CoursesModel
@{
    ViewData["Title"] = "Add New Course";
}
<h2>Add New Course</h2>
<form method="post" asp-controller="Courses" asp-action="AddCourse" asp-antiforgery="true">
    <div class="form-group">
        <label for="CourseName">Course Name:</label>
        <input class="form-control" name="CourseName" />
    </div>
    <div class="form-group">
        <label for="CourseDescription">Course Description:</label>
        <input class="form-control" name="CourseDescription" />
    </div>
    <div class="form-group">
        <label for="Price">Price:</label>
        <input class="form-control" name="Price" />
    </div>
    <div class="form-group">
        <label for="Capacity">Capacity:</label>
        <input class="form-control" name="Capacity" />
    </div>
    <button type="submit" class="btn btn-primary">Add</button>
</form>
بعد ذلك، يجب إضافة [ValidateAntiForgeryToken] attribute على AddCourse  من النوع HttpPost .
الكود: 

  [HttpPost]
        [ValidateAntiForgeryToken]

        public IActionResult AddCourse(CoursesModel coursesModel)
        {
            string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                string sql = $"set dateformat dmy  Insert Into Courses (CourseName,CourseDescription,Price,Capacity,CreatedDate) Values (@CourseName,@CourseDescription,@Price,@Capacity,@CreatedDate)";
                using (SqlCommand command = new SqlCommand(sql, connection))
                {
                    command.CommandType = CommandType.Text;
                    // adding parameters
                    SqlParameter parameter = new SqlParameter
                    {
                        ParameterName = "@CourseName",
                        Value = coursesModel.CourseName,
                        SqlDbType = SqlDbType.NVarChar,
                        Size = 200
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@CourseDescription",
                        Value = coursesModel.CourseDescription,
                        SqlDbType = SqlDbType.NVarChar
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Price",
                        Value = coursesModel.Price,
                        SqlDbType = SqlDbType.Int
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Capacity",
                        Value = coursesModel.Capacity,
                        SqlDbType = SqlDbType.Int
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@CreatedDate",
                        Value = DateTime.Now,
                        SqlDbType = SqlDbType.DateTime
                    };
                    command.Parameters.Add(parameter);
                    connection.Open();
                    command.ExecuteNonQuery();
                    ViewBag.Result = "Success";
                    connection.Close();
                }
            }
            return View("AllCourses");
        }

أعد تشغيل التطبيق بالانتقال الى صفحة الإضافة وتحقق من كود المصدر ل HTML الذي تم إنشاؤه لـ Add Course View. ستجد hidden input field  الذي تم إنشاؤه داخل form element  ويحتوي على security token كما هو موضح في الصورة الموضحة أدناه:


لمشاهدة  Cookies  التي تم انشاءها بواسطه هذا ال tag انتق الى نافذة Developer Tools ، عن طريق المتصفح كما في الصورة : 

ثم انتقل إلى  Tab Application ثم انقر فوق Cookies. سترى ملف تعريف الارتباط ل anti-forgery cookie  الذي تم إنشاؤه بواسطة Core MVC وسيتم إرساله إلى controller جنبًا إلى جنب مع response (انظر الصورة في الاسفل).

يحتوي hidden field على كائن binary blob تم إنشاؤه عشوائيًا يبلغ طوله 128 بايت. يحتوي cookie على نفس  binary blob الكبيرة ولكنه مشفر باستخدام Data Protection API مع الاحتفاظ بالمفتاح  key في Local Security Authority لنظام التشغيل Windows.
يمكنك ملاحظة الفرق بين قيمة  Cookies وقيمة hidden field
في مثالنا السابق : 
قيمة hidden filed : 

CfDJ8Japx8KzpRhFjpArZ7grigg7s_ynDr9Wu39c6vPDV92sdRqg74JXd9gh-DkIAZjkcEpRXyBvtGJwwfqKAI8ijjBnZx09l8w-iNCz0HRYM4kznA2eD8rDFsrYS12N50kcWdLWymzriCzkynOY4c2nK0g

قيمة Cookies :

CfDJ8Japx8KzpRhFjpArZ7grighx6XKiTwjrK8yio1fDj6LUnpLFr-pPqH91ZEZnkFpMssAL8bQoFu3Rx9DxL8AjDGkZ1JmPom6n_vloqlcipkUzJ4FKNMrml1V4Wb4_HV9R2tCn78qeGS1hSqCG7G85l20

للمزيد حول ذلك يمكن النقر فوق  

في Controller’s action ، ستقوم attribute - [ValidateAntiForgeryToken] من التحقق تلقائيًا من صحة cookie هذا و security token.

استخدام Tag Helper مع  Label Elements
يستخدم Tag Helper المسمى asp-for  مع Label element . ولا يوجد tag helper آخر يستخدم مع Label. يتم استخدامه لتعيين attribute   و contents  ل label element.
استخدمنا هذا tag helper سابقا في add course View كما هو موضح في الاسفل

@model CoursesModel
@{
    ViewData["Title"] = "Add New Course";
}
<h2>Add New Course</h2>
<form method="post" asp-controller="Courses" asp-action="AddCourse" asp-antiforgery="true">
    <div class="form-group">
        <label asp-for="CourseName">Course Name:</label>
        <input class="form-control" asp-for="CourseName" />
    </div>
    <div class="form-group">
        <label asp-for="CourseDescription">Course Description:</label>
        <input class="form-control" asp-for="CourseDescription" />
    </div>
    <div class="form-group">
        <label asp-for="Price">Price:</label>
        <input class="form-control" asp-for="Price" />
    </div>
    <div class="form-group">
        <label asp-for="Capacity">Capacity:</label>
        <input class="form-control" asp-for="Capacity" />
    </div>
    <button type="submit" class="btn btn-primary">Add</button>
</form>
عند التنفيذ ستكون التيجة : 


لاحظ اكواد Html بعد التنفيذ حيث تم ربط Label باستخدام for .
يقوم ‘asp-for’ Tag Helper  بأمرين:
  1. يعين attribute الخاصة ب label وذلك بربط Label مع  Model property’s name.
  2. يعين محتوى labels بخاصية Model Property.
استخدام Tag Helper مع  Input Elements
يستخدم Tag Helper المسمى asp-for  مع Input element  حيث يستخدم لتحديد:
name, Id, type and value attributes

بالرجوع الى الكود السابق ستلاحظ اننا استخدمنا هذا tag مع input  لاحظ الكود التالي لاسم الكورس:

<div class="form-group">
        <label asp-for="CourseName">Course Name:</label>
        <input class="form-control" asp-for="CourseName" />
</div>
يوفر ASP.NET Core تلقائيا نوع القيمة type value ل HTML controls بناء على النوع الذي تم ربط HTML controls به من Model Property.
مثال ذلك :
CourseName  : من نوع String 
Price             : من نوع decimal
وهكذا
يوضح الجدول أدناه كيفية إعطاء قيم الأنواع المختلفة:

Type attribute for Input Element
Model Type
Number
byte, sbyte, int, uint, short, ushort, long, ulong
text
float, double, decimal
text
string
checkbox
bool
datetime
DateTime
استخدام Tag Helper مع  Select Elements

في MVC Core نوعين من Built-In Tag Helpers تستخدم مع Select element وهما :

Description
Name
يعمل على تعين Id، Name  ل the Select element بناء على Model Property name.
asp-for
يحدد مصدر القيم ل option elements الموجودة داخل Select element.
asp-items
لنأخذ مثال على ذلك بتغير Capacity الى Select Elements 
دعنا نغير حقل quantity في Create View’s  من input ل select  كما هو موضح أدناه:

  <select class="form-control" asp-for="Capacity">
            <option disabled selected value="">Select Capacity</option>
            <option>25</option>
            <option>30</option>
            <option>35</option>
            <option>40</option>
        </select>
بعد التنفيذ ستكون النتيجة : 





تحقق الآن من كود HTML الذي تم إنشاؤه لهذا select element في المتصفح. سترى أنه يحتوي على id and value attributes. انظر الكود أدناه:


استخدام asp-items مع  Select Elements
يتم استخدام asp-items Tag Helper  لتحديد مصدر القيم option elements داخل Select Element.حيث يمكن الحصول على مصدر البينات من قاعدة البيانات مثلا او ملف Enum  مخزن في التطبيق.
مثال على ذلك : 
سنقوم بتعريف Enum يحتوي على مجموعة ارقام خاصة ب Capacity لذلك انتقل الى مجلد Share واضف مجلد جديد باسم Enums ومن ثم اضف class باسم  Capacity من نوع enum كما في الكود في الاسفل : 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace StudentsAcademy.Share.Enums
{
    public enum Capacity
    {
        Capacity1=25,
        Capacity2=30,
        Capacity3=35,
        Capacity4=40
    }
}

استخدام Tag Helpers مع  Data Cache

Data Cache مخزن مؤقت في الذاكره يستخدم لتخزين البيانات بشكل مؤقت بغرض تسريع عرض View. 
يمكن الاحتفاظ بمحتوى View في cache element مثل:
<cache>
    Some Content
</cache>
مثال : 
لاضافة التاريخ والوقت في التطبيق سنقوم باستخدام cache element .
انتقل إلى ملف Layout.cshtml_ وأضف عنصر cache element  كما هو موضح أدناه:

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link rel="stylesheet" asp-href-include="lib/bootstrap/css/bootstrap.min.css" />
</head>
<body>
    <div class="container-fluid">
        <div class="bg-info text-warning">
            <cache>
                Date Time: @DateTime.Now.ToString("HH:mm:ss")
            </cache>
        </div>
        @RenderBody()
    </div>
</body>
</html>
الآن قم بتشغيل التطبيق الخاص  بالانتقال الى الرابط:
https://localhost:44382/Courses/AddCourse



سيتم عرض التاريخ والوقت في جميع الصفحات التي تسخدم ملف Layout.cshtml_
لاحظ انه في حال تحديث الصفحة لن يتم تغيير الوقت، هذا لأن الوقت مخزن بشكل مؤقت.
فيما يلي بعض الخصائص ل Tag Helpers for the Cache Element :

Description
Name
ريعمل على تحدد وقت وتاريخ حيث ستنتهي فيه ذاكرة التخزين المؤقت.expires-on
يعمل على تحديد الوقت TimeSpan الذي ستنتهي عنده ذاكرة التخزين المؤقت.
expires-after
Sliding time هو الفترة المنقضية منذ آخر استخدام.لذلك يعمل tag helper هذا على تحديد Sliding time الذي تنتهي فيه ذاكرة التخزين المؤقت.
expires-sliding
يحدد query string key الذي سيتم استخدامه للإدارة ،الإصدارات المختلفة لمحتويات ذاكرة التخزين المؤقت.
vary-by-query
يحدد cookie الذي سيتم استخدامه للإدارة الإصدارات المختلفة لمحتويات ذاكرة التخزين المؤقت.
vary-by-cookie
يحدد مفتاحًا key لإدارة الإصدارات المختلفة لمحتويات ذاكرة التخزين المؤقت.
vary-by
استخدام  expires-after- Tag Helper

انتقل الى ملف  Layout.cshtml_ ثم قم بتغيير كود cache code باضافة expires-after كما هو موضح أدناه.

<cache expires-after="@TimeSpan.FromSeconds(50)">
                Date Time: @DateTime.Now.ToString("HH:mm:ss")
</cache>

هذا يعني الآن صلاحية ذاكرة التخزين المؤقته ستنتهي بعد 50 ثانية.

استخدام  expires-on - Tag Helper

الآن قم بتغيير cache code في  Layout.cshtml_ باضافة expires-on:

<cache expires-on="@DateTime.Parse("2050-01-01")">
                Date Time: @DateTime.Now.ToString("HH:mm:ss")
</cache>

هذا الكود يعني ان ذاكرة التخزين المؤقت ستبقى موجوده حتي عام 2050.
استخدام  expires-sliding - Tag Helper
الآن قم بتغيير cache code في  Layout.cshtml_ باضافة expires-sliding.

<cache expires-sliding="@TimeSpan.FromSeconds(20)">
                Date Time: @DateTime.Now.ToString("HH:mm:ss")
</cache>

سيؤدي هذا إلى انتهاء صلاحية ذاكرة التخزين المؤقت في غضون 20 ثانية من آخر وقت تم استخدامه.
استخدام  vary-by - Tag Helper
يتم استخدام Tag Helper  المسمى vary-by  لتحديد key لإدارة إصدارات مختلفة من محتويات ذاكرة التخزين المؤقت cache contents.
الآن قم بتغيير cache code في _ Layout.cshtml على النحو التالي:

<cache expires-sliding="@TimeSpan.FromSeconds(10)" vary-by = "@ViewContext.RouteData.Values["action"]">
   Date Time: @DateTime.Now.ToString("HH:mm:ss")
</cache>

هذا يعني أن محتويات ذاكرة التخزين المؤقت تعمل بناءً على current action. وفي حال وجود اكثر من action، سيتم إنشاء إصدارات بعدد action من ذاكرة التخزين المؤقت (1 لكل action method).
يعمل expire-sliding على تعيين وقت sliding expiry لهذه الإصدارات.

استخدام Tag Helpers مع JavaScript Files
يدعم MVC Core مجموعة من Built-In Tag Helpers المضمنة لملفات JavaScript والتي تستخدم لإدارة ملفات JavaScript في Views حيث تعمل على تضمينها inclusion او استباعدها exclusion.
بعض هذه الأدوات المهمة لملفات JavaScript هي:

الوصفالاسم
يحدد ملفات JavaScript التي سيتم تضمينها في View.
asp-src-include
يحدد ملفات JavaScript المراد استبعادها من View..
asp-src-exclude
يحدد ملف JavaScript المراد تضمينه في حالة وجود مشكلة ما مع شبكة توصيل المحتوى Content Delivery Network.
asp-fallback-src-include
يحدد ملف JavaScript لاستبعاده إذا كانت هناك مشكلة ما مع شبكة توصيل المحتوى Content Delivery Network.
asp-fallback-src-exclude
يحدد جزءًا من JavaScript سيتم استخدامه إذا تم تحميل JavaScript بشكل صحيح من شبكة توصيل المحتوى Content Delivery Network.
asp-fallback-test
يحدد query string  التي سيتم تطبيقها على مسار ملفات JavaScript كلما تغيرت محتويات الملف. يستخدم هذا في خرق ذاكرة التخزين المؤقت Cache Busting
asp-append-version
يمكن أيضًا استخدام مجموعة من البدائل wildcards لإنشاء نمط pattern  لمطابقة الملفات. يمكن استخدام هذه الأنماط مع
 asp-src-include, asp-src-exclude, asp-fallback-src-include and asp-fallback-src-exclude 
الأنماط patterns الشائعة هي:

Description
Example
Pattern

يطابق نمطًا واحدًا فقط ، حيث يعمل على مطابقة ما بعد الاشارة / فقط ويعمل على استبعاد غير ذلك. 

امثلة التطابق 

JS/myscript1.js 

JS/myscriptZ.js

JS/myscripts.js 

JS/myscript?.js
?

يتطابق مع أي عدد من الأحرف لاول مقطع بعد "/" بحيث اذا توفر اكثر من مقطع سيتم استبعاده. 

امثلة التطابق

JS/myscript.js 

 JS/validate.js 

 JS/js123.js 

JS/*.js
*

يتطابق مع أي عدد من الأحرف بما في ذلك "/" بحيث يتطابق مع اي عدد من المقاطع.

امثلة التطابق

JS/Validate/myscript.js 

 JS/jQuery/validate.js 

 JS/js123.js 

JS/**.js
**
الفائدة من استخدام patterns في ملفات JavaScript كبيرة وعملية جدا، حيث يمكن من خلالها التأكد من تضمين الملفات المطلوبة في View

ادراج ملفات JS داخل View 
انتقل الى ملف Layout.cshtml_ ثم افتح الملف، حيث سنقوم هنا بتضمين ملفات bootstrap.js داخله. 
توجد ملفات bootstrap.js في المشروع عادتا في (wwwroot/lib/bootstrap/js) كما في الصورة
حيث ستلاحظ وجود اكثر من ملف Js داخل هذا المجلد، في حال الرغبة بتضمين اكثر من ملف يمكن استخدام طريقتين : 
طريقة 1
ادراج اسماء الملفات كل على حدى كما الكود التالي:

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link rel="stylesheet" asp-href-include="lib/bootstrap/css/bootstrap.min.css" />
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.min.js"></script>
</head>
<body>
    <div class="container-fluid">
        <div class="bg-info text-warning">
            <cache expires-sliding="@TimeSpan.FromSeconds(10)" vary-by="@ViewContext.RouteData.Values["action"]">
                Date Time: @DateTime.Now.ToString("HH:mm:ss")
            </cache>
        </div>
        @RenderBody()
    </div>
</body>
</html>
 طريقة 2 
استخدام patterns
انتقل الآن إلى Layout.cshtml_ و أضف asp-src-include tag helper كما هو موضح في الكود أدناه:

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link rel="stylesheet" asp-href-include="lib/bootstrap/css/bootstrap.min.css" />
    <script asp-src-include="lib/bootstrap/**/*.js"></script>
</head>
<body>
    <div class="container-fluid">
        <div class="bg-info text-warning">
            <cache expires-sliding="@TimeSpan.FromSeconds(10)" vary-by="@ViewContext.RouteData.Values["action"]">
                Date Time: @DateTime.Now.ToString("HH:mm:ss")
            </cache>
        </div>
        @RenderBody()
    </div>
</body>
</html>
بهذه الطريقة يتم تضمين جميع ملفات js الموجوده داخل مجلد bootstrap 
قم بتشغيل التطبيق الخاص  ومن ثم اعرض View Page Source.

ستلاحظ ان جميع ملفات js الموجوده داخل مجلد bootstrap تم ادراجها في الصفحه

طيب شو إذا كان المطلوب ادراج ملف bootstrap.bundle.js فقط ، الحل :استخدم نمط المطابقة التالي:
<script asp-src-include="/lib/bootstrap/**/*bundle.js"></script>
بهذه الطريقة ستضمن ادراج جميع ملفات js المطلوبة

استنثاء ملفات JS من View 
لاستبعاد  ملفات Js من View يجب استخدام asp-src-exclude tag helper.
الان سنقوم باضافة كود في ملف Layout.cshtml _ لغرض ازالة ملفات  js التالية من View: 
 ‘slim’, ‘min’ & ‘bundle’.
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link rel="stylesheet" asp-href-include="lib/bootstrap/css/bootstrap.min.css" />
    <script asp-src-include="lib/bootstrap/**/*.js" asp-src-exclude="**.slim.*,**.min.*,**.bundle.*"></script>
</head>
<body>
    <div class="container-fluid">
        <div class="bg-info text-warning">
            <cache expires-sliding="@TimeSpan.FromSeconds(10)" vary-by="@ViewContext.RouteData.Values["action"]">
                Date Time: @DateTime.Now.ToString("HH:mm:ss")
            </cache>
        </div>
        @RenderBody()
    </div>
</body>
</html>

قم بتشغيل التطبيق ومن ثم اعرض View Page Source. ستجد فقط ملف bootstrap.js مضافًا. انظر الصورة أدناه:

ميزة اختراق ذاكرة التخزين المؤقت Cache Busting  من Tag Helper "asp-fragment

يتم تخزين الصور وملفات CSS وملفات JavaScript مؤقتًا على المتصفحات عند اول زيارة للصفحه.ثم بعدها يعمل  المتصفح  على توفير هذه الملفات إلى صفحة الويب مباشرة من ذاكرة التخزين المؤقت الخاصة بها cache. هذا الامر يجعل تحميل الصفحة يتم بشكل أسرع حيث لا يتم طلب هذه الملفات من server مرة ثانيه.
لكن يوجد مشكلة هنا، حيث ان اي تغيير على الملفات لن ينعكس بشكل مباشر على الملفات التي تم تخزينها مؤقتا، لنفرض انه تم اجراء بعض التغيرات على ملفات JS مثلا، في هذه الحالة سيستمر المتصفح بالقراءه من الملفات السابقة المخزنه في الذاكرة المؤقتة. لذلك لا تنعكس تغييرات JavaScript الجديدة على الصفحات.
للتغلب على هذه المشكلة؟ يجب أن يكون هناك طريقة ما لإخبار المتصفح بتنزيل الملف من server عند إجراء تغييرات على الملف. تسمى معالجة هذه المشكلة بخرق Cache Busting.
حتي نتمكن من استخدام خرق ذاكرة التخزين المؤقت Cache Busting  يجب استخدام الامر
 asp-append-version Tag Helper. 
الآن عند إجراء أي تغيير على ملف JavaScript ، وباستخدام هذه الخاصيه يعرف المتصفح أن هذا الملف يحتاج إلى تنزيل أحدث ملف JS من الخادم ومن ثم القيام بالتخزين المؤقت مرة أخرى.
والكود التالي هو الي بيعمل هذا الاشي:

<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link rel="stylesheet" asp-href-include="lib/bootstrap/css/bootstrap.min.css" />
    <script asp-src-include="lib/bootstrap/**/*.js" asp-src-exclude="**.slim.*,**.min.*,**.bundle.*" asp-append-version="true"></script>
</head>
عند تنفيذ الكود ستلاحظ انه تم ارسال Query string مع ملف bootstrap حيث يتم التأكد من وجود نسخة احدث واحضارها




تضمين ملفات JavaScript من شبكات توصيل المحتوى Content Delivery Networks باستخدام Tag Helpers
تحتوي Delivery Networks (CDN) على عدد من Data Servers الموزعة حول العالم. تحتوي جميع Data Servers  هذه على نسخة محفوظة من الملفات الثابتة static files لموقع الويب مثل الصور و CSS و JS.
عندما يفتح المستخدم موقع الويب في المتصفح ، بدلاً من طلب الملفات الثابتة من servers الخاص بموقع الويب ، فإنه يطلبها من Data Server الأقرب إلى الموقع الجغرافي للمستخدم.
هذا يقلل من وقت تحميل website ويوفر أيضًا في bandwidth.
لذك ننصح دائما باستخدام CDN حتى لو كان موقع الويب الخاص بك صغيرًا.
تستخدم الاوامر Tag Helpers التالية للعمل مع شبكات CDN 
asp-fallback-src-include
 asp-fallback-src-exclude
 asp-fallback-test  
لنأخذ مثال على ذلك :
في المثال التالي سنقوم باستخدام ملفات jQuery التي هي عبارة عن مكتبة JavaScript شائعة جدًا يمكن تحميلها من ASPNET CDN.
اضفنا الرابط التالي الخاص بملف jQuery التالي في View 
http://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js
لنفترض انه ولسبب ما حدث بعض المشاكل ،وفشل CDN في توفير jQuery. من الرابط السابق، لذلك في مثل هذه الحالة ، يجب عليك تحميل jQuery من server موقع الويب الخاص بك.
الكود التالي الذي يعمل على ذلك: 

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link rel="stylesheet" asp-href-include="lib/bootstrap/css/bootstrap.min.css" />
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"
            asp-fallback-src-include="/lib/jquery/**/*.min.js"
            asp-fallback-src-exclude="**.slim.**"
            asp-fallback-test="window.jQuery">
    </script>
</head>
<body>
    <div class="container-fluid">
        <div class="bg-info text-warning">
            <cache expires-sliding="@TimeSpan.FromSeconds(10)" vary-by="@ViewContext.RouteData.Values["action"]">
                Date Time: @DateTime.Now.ToString("HH:mm:ss")
            </cache>
        </div>
        @RenderBody()
    </div>
</body>
</html>
تمام نفهم الكود 
الامر src attribute 
هذا الامر خاص ب script tag  حيث يعمل على تحديد موقع ASPNET CDN لتحميل jQuery.
الامر asp-fallback-src-include  
يستخدم لتضمين ملفات jQuery المحلية المحدده في حال فشل CDN
الامر asp-fallback-src-exclude 
يسستخدم لتحديد ملفات jQuery المحلية واستبعادها في حالة فشل CDN.
الامر  asp-fallback-test Tag Helper 
يحدد JavaScript code  (وهنا تم كتابة الكود "window.jQuery") التي سيتم تقييمها على المتصفح.

استخدام Tag Helpers في ملفات CSS Files
تماما كما في ملفات JavaScript  يدعم MVC Core  مجموعة من الاوامر Built-In Tag Helpers التي تستخدم لإدارة وتضمين واستبعاد ملفات CSS واستبعادها من Views. 
بعض هذه الأدوات المهمة لملفات CSS هي:


Description
Name
يحدد ملفات CSS التي سيتم تضمينها في طريقة View
asp-href-include
يحدد ملفات CSS التي سيتم استبعادها من View.
asp-href-exclude
يحدد query string التي سيتم تطبيقها على مسار ملف  CSS، كلما تغيرت محتويات الملف. يستخدم هذا في Cache Busting.
asp-append-version
يحدد ملف CSS المراد تضمينه في حالة وجود مشكلة ما في Content Delivery Network
asp-fallback-href-include
يحدد ملف CSS لاستبعاده إذا كانت هناك مشكلة ما في Content Delivery Network
asp-fallback-href-exclude
تحدد فئة CSS التي سيتم استخدامها لاختبار Content Delivery Network.
asp-fallback-href-test-class
تحدد CSS class property التي سيتم استخدامها لاختبار Content Delivery Network
asp-fallback-href-test-property
يحدد قيمة CSS class property التي سيتم استخدامها اختبار Content Delivery Network.
asp-fallback-href-test-property
ادراج ملفات CSS داخل View 
 تماما كما اوضحنا في ملفات JavaScript، يحتوي التطبيق على العديد من ملفات Bootstrap CSS الموجودة ضمن المجلد
 wwwroot/lib/bootstrap/css 
 كما هو موضح بالصورة الواردة أدناه:

لتضمين جميع ملفات bootstrap CSS من asp-href-include Tag Helper ، قم بتغيير كود layout كما هو موضح أدناه.

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @*<link rel="stylesheet" asp-href-include="lib/bootstrap/css/bootstrap.min.css" />*@
    <link rel="stylesheet" asp-href-include="/lib/bootstrap/**/*min.css" />
    <script asp-src-include="lib/bootstrap/**/*.js" asp-src-exclude="**.slim.*,**.min.*,**.bundle.*" asp-append-version="true"></script>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"
            asp-fallback-src-include="/lib/jquery/**/*.js"
            asp-fallback-src-exclude="**.slim.**"
            asp-fallback-test="window.jQuery">
    </script>
</head>
<body>
    <div class="container-fluid">
        <div class="bg-info text-warning">
            <cache expires-sliding="@TimeSpan.FromSeconds(10)" vary-by="@ViewContext.RouteData.Values["action"]">
                Date Time: @DateTime.Now.ToString("HH:mm:ss")
            </cache>
        </div>
        @RenderBody()
    </div>
</body>
</html>

قم بتشغيل التطبيق ومن ثم اعرض View Page Source، ستجد انه تم اضافة جميع الملفات bootstrap التي ينتهي اسمها بـ css. انظر الصورة أدناه:

استثناء ملفات CSS من View
يتم استخدام asp-href-exclude Tag Helper لاستبعاد ملفات CSS من View.
انظر الكود أدناه حيث سيتم استبعاد جميع ملفات CSS ل reboot  و grid :

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link rel="stylesheet" asp-href-include="/lib/bootstrap/**/*min.css"
          asp-href-exclude="**/*reboot*,**/*grid*" />
    <script asp-src-include="lib/bootstrap/**/*.js" asp-src-exclude="**.slim.*,**.min.*,**.bundle.*" asp-append-version="true"></script>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"
            asp-fallback-src-include="/lib/jquery/**/*.js"
            asp-fallback-src-exclude="**.slim.**"
            asp-fallback-test="window.jQuery">
    </script>
</head>
<body>
    <div class="container-fluid">
        <div class="bg-info text-warning">
            <cache expires-sliding="@TimeSpan.FromSeconds(10)" vary-by="@ViewContext.RouteData.Values["action"]">
                Date Time: @DateTime.Now.ToString("HH:mm:ss")
            </cache>
        </div>
        @RenderBody()
    </div>
</body>
</html>
قم بتشغيل التطبيق ومن ثم اعرض View Page Source، ستجد انه تم اضافة ملف bootstrap واحد فقط، انظر الصورة أدناه:


تضمين ملفات CSS من Content Delivery Networks باستخدام Tag Helpers 
يمكنك أيضًا تضمين ملفات CSS من Content Delivery Networks باستخدام Tag Helpers. 
في المثال التالي نقوم باستخدام ملفات CSS من CDN يسمى maxcdn حيث ستقوم باستدعاء الملف   bootstrap.min.css 
انظر الكود التالي: 

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css"
          asp-fallback-href-include="/lib/bootstrap/**/*.min.css"
          asp-fallback-href-exclude="**/*reboot*,**/*grid*"
          asp-fallback-test-class="btn"
          asp-fallback-test-property="display"
          asp-fallback-test-value="inline-block"
          rel="stylesheet" />
    <link rel="stylesheet" asp-href-include="/lib/bootstrap/**/*min.css"
          asp-href-exclude="**/*reboot*,**/*grid*" />
    <script asp-src-include="lib/bootstrap/**/*.js" asp-src-exclude="**.slim.*,**.min.*,**.bundle.*" asp-append-version="true"></script>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"
            asp-fallback-src-include="/lib/jquery/**/*.js"
            asp-fallback-src-exclude="**.slim.**"
            asp-fallback-test="window.jQuery">
    </script>
</head>
<body>
    <div class="container-fluid">
        <div class="bg-info text-warning">
            <cache expires-sliding="@TimeSpan.FromSeconds(10)" vary-by="@ViewContext.RouteData.Values["action"]">
                Date Time: @DateTime.Now.ToString("HH:mm:ss")
            </cache>
        </div>
        @RenderBody()
    </div>
</body>
</html>

تستخدم  href attribute  لتحديد عنوان CDN URL  والذي هو هنا في هذه الحاله MaxCDN URL لملف bootstrap.min.cs.
أدوات المساعدة الأخرى تستخدم في تضمين واستبعاد الملفات من website directory في حالة عدم توفر CDN.
يتم استخدام أدوات مساعدة العلامات الثلاث التالية لاختبار ما إذا كان ملف bootstrap قد تم تنزيله بشكل صحيح من CDN أم لا. tag helpers هم:
  1. asp-backback-test-class
  2. asp-fallback-test-property
  3. asp-backback-test-value
هذه الاوامر تعمل على إضافة meta element إلى المستند الذي تمت إضافته إلى الفئة المحددة بواسطة asp-fallback-test-class tag helper.
سيكون شكل meta element المضاف إلى المستند كما في الصورة:

الآن يتم استخدام مساعد علامة asp-Fallback-test-property لتحديد خاصية CSS التي تم تعيينها بواسطة فئة CSS هذه (هنا @@#btn@@#) ، ويتم استخدام مساعد علامة asp-fallback-test-value لتحديد القيمة التي سيتم تعيينه على.
يضيف مساعد العلامات أيضًا JavaScript إلى view التي تختبر قيمة خاصية CSS على meta element لمعرفة ما إذا كان ملف CSS قد تم تحميله بشكل مثالي من CDN أم لا. إذا فشل اختبار JavaScript ، فسيتم استخدام ملفات CSS الاحتياطية بدلاً من ذلك.