في هذا الدرس سنقوم بإضافة التحقق إلى book model وهذا سيضمن لنا أن قواعد التحقق مطبقة في أي وقت يحاول فيه المستخدم إضافة أو تعديل كتاب باستخدام التطبيق.
أحد المبادئ الأساسية في ASP.NET MVC هو مبدأ DRY وهي اختصار للكلمات Don’t Repeat Yourself.
هذه المقالة جزء من سلسلة لتعلم أساسيات ASP.NET MVC للمبتدئين:
- المقالة الأولى: أول موقع ويب لك باستخدام ASP.NET MVC
- المقالة الثانية: إضافة متحكمات Controllers لموقع ASP.NET MVC
- المقالة الثالثة: إضافة واجهة View لموقع ASP.NET MVC
- المقالة الرابعة: إضافة Model لموقع ASP.NET MVC
- المقالة الخامسة: إضافة Connection String والعمل مع قواعد البيانات LocalDB
- المقالة السادسة: الوصول إلى البيانات عن طريق المتحكم Controller
- المقالة السابعة: شرح وتجريب تابع التعديل Edit Method
- المقالة الثامنة: إضافة ميزة البحث إلى موقع ASP.NET MVC
- المقالة التاسعة: إضافة حقل جديد لقاعدة البيانات انطلاقاً من كود سي شارب Code First Method
- المقالة العاشرة: إضافة التحقق Validation إلى MVC Model وتطبيق مبدأ DRY
- المقالة الحادية عشر: اختبار وشرح توابع Details و Delete
إن ASP.NET MVC تنصح بتعريف تابع لمرة واحدة ومن ثم استخدامه في أي مكان نحتاجة في التطبيق وهذا يؤدي إلى تقليل الكود الذي نكتبه ويجعل الأخطاء في الكود أقل و أسهل للتعديل.
إن دعم التحقق الذي تقدمه ASP.NET MVC و Entity Framework Code First هو أفضل مثال عن مبدأ DRY
حيث يمكن تعريف قواعد التحقق في مكان واحد (في model class) ويتم تطبيق هذه القواعد في كامل التطبيق.
إضافة قواعد التحقق إلى Book Model
سنبدأ بإضافة بعض قواعد التحقق إلى Book class
قم بفتح الملف Book.cs فنلاحظ أن ال System.ComponentModel.DataAnnotations namespace لايحتوي System.Web
إن DataAnnotations تقوم بتوفير مجموعة من خصائص التحقق التي يمكن أن تطبق على أي class أو خاصية
سنقوم الآن بتعديل Book class للاستفادة من خصائص التحقق. قم باستبدال كود Book class بالكود التالي:
public class Book { public int Id { set; get; } [StringLength(60, MinimumLength = 3)] public string Title { set; get; } [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")] [Required] [StringLength(30)] public string Description { set; get; } [Display(Name = "Publish Date")] [DataType(DataType.Date)] [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime PublishDate { set; get; } [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")] [Required] [StringLength(30)] public string Author { set; get; } public int NumberOfPages { set; get; } public double GoodreadsRate { set; get; } [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")] [StringLength(5)] public string Rating { set; get; } public int TypeId { set; get; } public virtual Type Type { set; get; } }
إن الخاصية StringLength تحدد الطول الأعظمي للسلسلة النصية وتعكس هذا الطول على الخاصية المقابلة في قاعدة البيانات و هذا سيؤدي إلى تغير بنية قاعدة البيانات
قم بالنقر بزر الفأرة اليميني على جدول Books في نافذة Server explorer وقم بالنقر على
Open Table Definition
في الصورة السلبقة نلاحظ أن الحقول النصية موضوعة على أنها (NVARCHAR (MAX
سنستخدم migrations لتحديث قاعدة البيانات. قم بعمل Build للتطبيق ثم قم بفتح نافذة Package Manager Console وقم بإدخال الأوامر التالية:
add-migration DataAnnotations update-database
عند انتهاء تنفيذ التعليمات فإن Visual Studio يقوم بفتح ملف class يحتوي على تعريف DataAnnotations class الذي يرث من DbMigration class
نلاحظ كود التابع Up الذي يقوم بتحديث البنية للجدول Books
public override void Up() { AlterColumn("dbo.Books", "Title", c => c.String(maxLength: 60)); AlterColumn("dbo.Books", "Description", c => c.String(nullable: false, maxLength: 30)); AlterColumn("dbo.Books", "Author", c => c.String(nullable: false, maxLength: 30)); AlterColumn("dbo.Books", "Rating", c => c.String(maxLength: 5)); }
إن الحقل Author لم يعد nullable أي يجب أن يتم إدخال قيمة في هذا الحقل و كذلك الأمر بالنسبة للحقل Description
الحقل Rating له طول أعظمي 5 محارف و الحقل Title له طول أعظمي 60 محرف و طول أصغري 3 محارف
الصورة التالية تعرض بنية الجدول Books
نلاحظ أن الحقول النصية أصبح لها طول محدد و أن الحقول Author و Description لم تعد nullable
إن خصائص التحقق تحدد السلوك الذي نريد تطبيقه على خصائص model
إن الخاصية Required تحدد أن الخاصية يجب أن تملك قيمة (هذا لايمنع المستخدم من أن يدخل فراغ لتحقيق الشرط)
والخاصية MinimumLength تحدد الطول الأصغري للسلسلة النصية
إن الخاصية RegularExpression تستخدم لتحديد ماهي المحارف الممكن إدخالها
في الكود السابق فإن الخصائص Description و Author و Rating يجب أن تقبل فقط الأحرف(أي أن الفراغات و الأرقام والمحارف الخاصة غير مسموحة)
إن الخاصية StringLength تحدد الطول الأعظمي للخاصية النصية ويمكن أيضاً تحديد الطول الأصغري
إن الحقول ذات القيم مثل(decimal, int, float, DateTime) بشكل افتراضي هي مطلوبة وبالتالي لا تحتاج إلى الخاصية Required
إن Code First تضمن أن قواعد التحقق التي قمنا بوضعها في model class يتم تطبيقها قبل أن يقوم التطبيق بحفظ التغييرات في قاعدة البيانات
على سبيل المثال الكود التالي سيعطي exception من النوع DbEntityValidationException عند استدعاء التابع
SaveChanges لأن العديد من الخصائص المطلوبة لم يتم إعطاء قيم لها
BookDBContextdb = new BookDBContext(); Book book= new Book(); book.Title = "Clean Code"; db.Books.Add(book); db.SaveChanges();
الكود السابق يعطي exception التالي
Validation failed for one or more entities. See ‘EntityValidationErrors’ property for more details
يساعد وجود قواعد التحقق التي يتم تطبيقها تلقائيا بواسطة NET Framework. على جعل التطبيق أكثر قوة. كما أنه يضمن أنه لا يمكن أن ننسى التحقق من صحة شيء والسماح عن غير قصد للبيانات الخاطئة في قاعدة البيانات.
قم بتشغيل التطبيق وانتقل إلى الرابط Books/
قم بالنقر على الرابط Create New لإضافة كتاب جديد
قم بملئ الحقول بقيم غير صالحة
فنلاحظ أن jQuery client side validation قامت بتحديد الخطأ وعرض رسالة خطأ
نلاحظ أن النموذج وبشكل أوتوماتيكي قام بوضع إطار أحمر حول text boxes التي تحتوي على بيانات غير صالحة
وعرض رسالة خطأ تحت كل منها
يتم التحقق من الأخطاء في كل من client-side باستخدام JavaScript و jQuery
و في طرف server-side في حال كانت JavaScript معطلة في متصفح الزبون
إن الفائدة من القيام بالتحقق كما فعلنا هي أننا لانحتاج لتعديل أي سطر كود في BooksController class أو في ملف Create.cshtml لتفعيل التحقق
حيث يقوم المتحكم و ال views التي قمنا بإضافتها و بشكل أوتوماتيكي بأخذ قواعد التحقق التي قمنا بتحديدها باستخدام خصائص التحقق على خصائص Book model class
قم باختبار التحقق باستخدام التابع Edit فنلاحظ أن نفس قواعد التحقق مطبقة
إن بيانات النموذج لا ترسل إلى المخدم حتى يتم التحقق من عدم وجود أخطاء في طرف الزبون
يمكن التحقق من ذلك من خلال وضع break point في تابع HTTP Post أو باستخدام fiddler tool أو باستخدام IE F12 developer tools
كيفية عمل التحقق في Create view وتابع Create
ربما نتساءل كيف يتم توليد واجهة التحقق بدون أي تعديلات على الكود في المتحكم
الكود التالي هو لتابع Create في المتحكم BookController حيث أنه لم نقم بأي تعديل على الكود
public ActionResult Create() { ViewBag.TypeId = new SelectList(db.Types, "Id", "Title"); return View(); } // POST: Books/Create // 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 ActionResult Create([Bind(Include = "Id,Title,Description,PublishDate,Author,NumberOfPages,GoodreadsRate,TypeId,Rating")] Book book) { if (ModelState.IsValid) { db.Books.Add(book); db.SaveChanges(); return RedirectToAction("Index"); } ViewBag.TypeId = new SelectList(db.Types, "Id", "Title", book.TypeId); return View(book); }
إن التابع HTTP GET) Create) يعرض نموذج إضافة الكتاب
إن التابع HTTP Post) Create) يعالج البيانات القادمة من المتصفح حيث أن هذا التابع يستخدم الخاصية ModelState.IsValid لفحص فيما إذا كان الكتاب يملك أي أخطاء في التحقق
وباستخدام هذه الخاصية يتم تقييم كل خصائص التحقق التي المطبقة على object فإذا كان object يحتوي على أخطاء تحقق فإن التابع Create يقوم بإعادة عرض النموذج وإذ لم يكن هناك أخطاء فإن التابع يقوم بحفظ الكتاب الجديد في قاعدة البيانات
في مثالنا فإن النموذج(form) لن يرسل إلى المخدم إذا كان هناك أخطاء تحقق في طرف الزبون وهذا يعني أن تابع
HTTP Post) Create) لن يتم استدعاؤه
في حال قمنا بتعطيل JavaScript في المتصفح فإن التحقق من الصحة في طرف الزبون سيتم تعطيله وبالتالي فإن التابع HTTP POST Create سيستدعي الخاصية ModelState.IsValid لفحص فيما إذا كان book object فيه أخطاء تحقق
يمكن وضع break point عند التابع HttpPost Create والتأكد من أن التابع لن يتم استدعاؤه
حيث أن التحقق من طرف الزبون لن يرسل بيانات النموذج إلى المخدم عندما يكون هناك أخطاء تحقق
وفي حال قمنا بتعطيل JavaScript في المتصفح فإن النموذج سيرسل إلى المخدم مع أخطاء وعندها سيتم استدعاء التابع
HTTP POST Create
يمكن تعطيل JavaScript في متصفح Chrome باتباع الخطوات التالية
قم بالنقر على قائمة Chrome والتي لها الشكل في الزاوية العليا اليمنى للمتصفح
قم باختيار Settings
بعد ذلك قم بالنقر على Show advanced settings
في قسم Privacy and security قم بالنقر على زر Content settings
في قسم Javascript قم بتعطيل ال Javascript
الصورة التالية تعرض كيف يتم تعطيل JavaScript في متصفح Chrome
الصور التالية تعرض كيف يتم تعطيل JavaScript في متصفح Internet Exploler
قم بوضع break point عند التابع HttpPost Create وقم بتشغيل التطبيق و الانتقال إلى الرابط Books/
قم بالنقر على الرابط Create New لإضافة كتاب جديد
قم بإدخال بيانات غير صالحة كما في الصورة التالية
بعد إدخال البيانات قم بالنقر على زر Create لإضافة الكتاب
نلاحظ انه سيتم استدعاء التابع HttpPost Create للتحقق من صحة القيم المدخلة وعندها تتفعل break point التي قمنا بوضعها عند التابع HttpPost Create
قم بالنقر على زر Continue في Visual Stdio لاستكمال التنفيذ و قم بالعودة للمتصفح فنلاحظ عرض رسائل الخطأ كما في الصورة التالية
الكود التالي هو لملف Create.cshtml حيث أنه يستخدم من قبل تابع HTTP GET Create وتابع
HTTP POST Create لعرض النموذج الفارغ ولإعادة عرضه في حال وجود أخطاء
@model ArabicArchive.Models.Book @{ ViewBag.Title = "Create"; } @using (Html.BeginForm()) { @Html.AntiForgeryToken()
@Html.ValidationSummary(true, “”, new { @class = “text-danger” })
@Html.LabelFor(model => model.Title, htmlAttributes: new { @class = “control-label col-md-2” })
@Html.LabelFor(model => model.Description, htmlAttributes: new { @class = “control-label col-md-2” })
@Html.LabelFor(model => model.PublishDate, htmlAttributes: new { @class = “control-label col-md-2” })
@Html.LabelFor(model => model.Author, htmlAttributes: new { @class = “control-label col-md-2” })
@Html.LabelFor(model => model.NumberOfPages, htmlAttributes: new { @class = “control-label col-md-2” })
@Html.LabelFor(model => model.GoodreadsRate, htmlAttributes: new { @class = “control-label col-md-2” })
@Html.LabelFor(model => model.Rating, htmlAttributes: new { @class = “control-label col-md-2” })
@Html.LabelFor(model => model.TypeId, “Type”, htmlAttributes: new { @class = “control-label col-md-2” })
}
@section Scripts { @Scripts.Render(“~/bundles/jqueryval”) }
نلاحظ أنه في الكود يتم استخدام التابع Html.EditorFor لإظهار الوسم <input> من أجل كل خاصية من خصائص Book class
السطر التالي هو استدعاء للتابع Html.ValidationMessageFor
هذان التابعان يعملان مع model object المرر من المتحكم إلى view وبشكل أتوماتيكي تبحث عن خصائص التحقق المحددة في ال model وتعرض رسائل الخطأ المناسبة
مايميز هذه الطريقة هو أن المتحكم وال Create view لايعرفان أي شيء عن قواعد التحقق الفعلية المطبقة او حتى عن رسائل الخطأ المعروضة
إن قواعد التحقق و نصوص الأخطاء تحدد فقط في Book class
إن نفس قواعد التحقق مطبقة بشكل أتوماتيكي على Edit view وأي views templates أخرى التي تضيف بيانات أو تعدل بيانات في ال model
إذا أردنا تعديل التحقق لاحقا يمكن القيام بذلك من خلال القيام بإضافة خصائص التحقق إلى model (في مثالنا Book class)
حيث أن التحقق يعرف في مكان واحد ويستخدم في كل مكان في التطبيق وهذا يجعل الكود واضح وأسهل في التعديل والتطوير
وهذا يحقق مبدأ DRY
استخدام خصائص DataType
قم بفتح الملف Book.cs
إن System.ComponentModel.DataAnnotations namespace يوفر خصائص التنسيق بالإضافة إلى مجموعة من خصائص التحقق
قمنا سابقاً بتطبيق الخاصية DataType على حقل PublishDate
الكود التالي يعرض الخاصية PublishDate مع خاصية DataType
[DataType(DataType.Date)] public DateTime PublishDate { set; get; }
إن خصائص DataType تعطي التوجيهات لمحرك العرض لتنسيق البيانات
يمكن استخدام الخاصية RegularExpression للتحقق من تنسيق البيانات
حيث تستخدم الخاصية DataType لتحديد نوع البيانات بشكل أكثر تحديداً من النوع الأساسي في فاعدة البيانات فهي ليست خصاص تحقق
في مثالنا نريد فقط بيانات التاريخ وليس التاريخ والوقت
إن DataType Enumeration يوفر العديد من أنواع البيانات مثل Date و Time و PhoneNumber و Currency و EmailAddress وغيرها
يمكن للخاصية DataType تمكين التطبيق من توفير ميزات خاصة بالنوع تلقائيا على سبيل المثال الرابط mailto يمكن أن يتم إنشاؤه باستخدام DataType.EmailAddress
و حقل اختيار التاريخ يمكن إنشاؤه باستخدام DataType.Date في المتصفحات التي تدعم HTML5
إن خصائص DataType لا توفر أي تحقق كما ذكرنا سابقا
إن DataType.Date لاتؤمن تنسيق التاريخ الذي يتم عرضه وبشكل افتراضي فإن حقل البيانات يتم عرضه وفق التنسيق الافتراضي
إن الخاصية DisplayFormat تستخدم لتنسيق التاريخ
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime PublishDate { set; get; }
إن ApplyFormatInEditMode تحدد بأن التنسيق المحدد يجب أن يطبق عندما تعرض القيمة في مربع النص للتعديل
يمكن استخدام الخاصية DisplayFormat لوحدها ولكن من الأفضل استخدام الخاصية DataType معها أيضاً
إن الخاصية DataType تهتم بمعاني البيانات بدلاً من كيفية عرضها على الشاشة وهي توفر المزايا التالية التي لاتوفرها الخاصية DisplayFormat
يمكن للمتصفح تمكين ميزات HTML5(على سبيل المثال عرض عنصر calendar)
وبشكل افتراضي فإن المتصفح سيعرض البيانات بالتنسيق الصحيح بالاعتماد على locale
إن الخاصية DataType تمكن MVC لاختيار الحقل المناسب لعرض البيانات
في حال استخدام الخاصية DataType مع حقل تاريخ فيجب أن استخدام الخاصية DisplayFormat أيضاً لضمان عرض الحقل بشكل صحيح في متصفح Chrome
ملاحظة: إن jQuery validation لايعمل مع خاصية Range و DateTime على سبيل المثال الكود التالي سيعرض دائماً خطأ من طرف الزبون حتى إذا كان التاريخ المدخل ضمن المجال المحدد
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]
يجب تعطيل jQuery date validation لاستخدام الخاصية Range مع DateTim وهذا غير جيد
إن استخدام الخاصية Range مع DateTim لاينصح به
الكود التالي يعرض الدمج بين الخصائص على سطر واحد
public class Book { public int Id { set; get; } [StringLength(60, MinimumLength = 3)] public string Title { set; get; } [Required,StringLength(30),RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")] public string Description { set; get; } [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true), DataType(DataType.Date), Display(Name = "Publish Date")] public DateTime PublishDate { set; get; } [Required, StringLength(30), RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")] public string Author { set; get; } public int NumberOfPages { set; get; } public double GoodreadsRate { set; get; } [StringLength(5), RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")] public string Rating { set; get; } public int TypeId { set; get; } public virtual Type Type { set; get; } }
في الدرس القادم سنقوم بعمل بعض التحسينات على توابع Details و Delete.
تغذية راجعة
اتمنى عدم التردد في الاستفسار عن أي مفهوم تم ذكره في هذا التدوينة، وأرجو تجربة ماورد في التدوينة بشكل فعلي وعدم الاكتفاء بالقراءة لتحقيق أكبر فائدة ممكنة، وكالعادة سأكون سعيد إن شاركتموني تجربتكم وتصويباتكم في حال وجود أخطاء كتابية.
هذه المقالة مستندة إلى سلسلة دروس مايكروسوفت الرسمية للـ ASP.NET MVC، وذلك لترتيب الدروس المناسب واعتقادي بسلاستها وأهمية نقلها إلى العربية بأسلوب مناسب وتجربة تتوافق مع الأدوات المتاحة لنا والمتوفرة في منطقتنا.