في هذه التدوينة سنقوم بشرح توابع Edit وال views التابعة ل Book controller، بحيث نستعرض مجمل الخصائص وكيفية التعامل معها، خصوصاً التوجيه والمساعدات Helper وغيرها من الخواص الأساسية.
هذه المقالة جزء من سلسلة لتعلم أساسيات 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
أولاً علينا بعمل تعديل صغير لجعل حقل PublishDate يعرض فقط التاريخ. قم بفتح الملف Models\Book.cs وقم بإضافة الكود الملون بالأصفر كما يلي:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Data.Entity; using System.Linq; using System.Web; namespace ArabicArchive.Models { public class Book { public int Id { set; get; } public string Title { set; get; } 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; } public string Author { set; get; } public int NumberOfPages { set; get; } public double GoodreadsRate { set; get; } public int TypeId { set; get; } public virtual Type Type { set; get; } }
يمكن أيضاً وضع أي صيغة نريدها للتاريخ على سبيل المثال:
[Display(Name = "Publish Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime PublishDate { set; get; }
إن خاصية Display تحدد الاسم الذي سيظهر بدلاً من اسم الحقل، في مثالنا سيتم عرض Publish Date بدلاً من PublishDate. إن خاصية DataType تحدد نوع البيانات ، في مثالنا هو date حيث أن معلومات الوقت المخزنة في الحقل لن تظهر.
إن خاصية DisplayFormat مطلوبة من أجل عدم الحصول على خطأ في متصفح Chrome الذي يقوم بعرض تنسيقات التاريخ بشكل غير صحيح.
قم بتشغل التطبيق وانتقل الى المتحكم Books. ثم قم بإيقاف مؤشر الفأرة فوق الرابط Edit لرؤية الرابط الذي يشير إليه:
الرابط Edit تم توليده من قبل التابع Html.ActionLink الموجود في ملف Views\Books\Index.cshtml
@Html.ActionLink("Edit", "Edit", new { id=item.Id })
إن Html object هو عبارة عن مساعد وهو عبارة عن خاصية في System.Web.Mvc.WebViewPage class
التابع ActionLink هو عبارة تابع يبسط عملية التوليد الديناميكي لـ HTML hyperlinks التي تشير الى action methods في المتحكمات. حيث أن أول بارمتر لهذا التابع هو عبارة عن نص الرابط الذي سيعرض على سبيل المثال (<a>Edit Me</a>)
البارمتر الثاني للتابع هو عبارة عن اسم action method التي سيتم استدعاؤها، أما البارمتر الأخير للتابع فهو عبارة عن anonymous object الذي يولد بيانات التوجيه في مثالنا Id=3.
الرابط المولد معروض في الصورة السابقة هو http://localhost:17446/Books/Edit/3
إن التوجيه الافتراضي (موجود في الملف App_Start\RouteConfig.cs) يأخذ الشكل {controller}/{action}/{id}
لذلك فإن ASP.NET تترجم http://localhost:17446/Books/Edit/3 الى طلب ل Edit action للمتحكم Books
مع قيمة للبارمتر Id تساوي 3.
قم بفتح الملف App_Start\RouteConfig.cs ولاحظ الكود التالي.
إن التابع MapRoute يستخدم لتوجيه طلبات HTTP الى المتحكم و action method المناسبين بالإضافة إلى تمرير قيمة البارمتر الاختياري Id الى action method. كما أن التابع MapRoute يستخدم أيضاً من قبل HtmlHelpers (على سبيل المثال تابع ActionLink ) لتوليد URLs التي تحدد المتحكم و action method بالإضافة لبيانات التوجيه.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Hello",
url: "{controller}/{action}/{name}/{id}"
);
}
يمكن أيضاً تمرير بارمترات action method باستخدام query string على سبيل المثال فإن الرابط التالي: http://localhost:17446/Books/Edit?Id=3 أيضاً يمرر البارمتر Id بقيمة 3 ل Edit action في المتحكم Books
عند النقر على الرابط Edit نلاحظ وجود حقل TypeId هو عبارة عن قائمة منسدلة تحتوي على جميع أنواع الكتب التي قمنا بإضافتها حيث أننا نقوم عن طريق هذه اللائحة باختيار النوع و يتم أخذ قيمة الخاصية TypeId للعنصر الذي تم اختياره من اللائحة ليكون TypeId للكتاب الذي نقوم بتعديل بياناته:
سنقوم بتغيير اسم الحقل من TypeId إلى Type دون تغيير اي شيء في طريقة العمل الموضحة سابقا لاختيار النوع للكتاب
كما فعلنا في الدرس السابق عندما قمنا بتعديل اسم الحقل من TypeId إلى Type في الملف Index.cshtml
قم بالذهاب الى الملف Views\Books\Edit.cshtml وقم بإيجاد ال TypeId label كما هو موضح بالصورة التالية:
قم بتغيير النص الذي يظهر في ال label من “TypeId” ليصبح “Type” كما هو موضح بالصورة التالية:
إن هذه الطريقة تؤدي الى تغيير اسم الحقل من TypeId إلى Type في ملف Edit.cshtml فقط.
قم بتشغيل التطبيق وانتقل الى الرابط http://localhost:17446/Books/Edit/3 فنجد أن اسم الحقل تحول الى Type
قم بفتح المتحكم Books نلاحظ وجود تابعيين Edit action كما هو موضح
// GET: Books/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Book book = db.Books.Find(id);
if (book == null)
{
return HttpNotFound();
}
ViewBag.TypeId = new SelectList(db.Types, "Id", "Title", book.TypeId);
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 ActionResult Edit([Bind(Include = "Id,Title,Description,PublishDate,Author,NumberOfPages,GoodreadsRate,TypeId")] Book book)
{
if (ModelState.IsValid)
{
db.Entry(book).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.TypeId = new SelectList(db.Types, "Id", "Title", book.TypeId);
return View(book);
}
نلاحظ أن تابع Edit الثاني مسبوق بالخاصية HttpPost وهذه الخاصية تحدد أن بأن هذا التابع يستدعى فقط من أجل طلبات Post. يمكن أيضا تطبيق الخاصية HttpGet للتابع Edit الأول ولكن هذا ليس ضروري لأن هذه الخاصية مطبقة بشكل افتراضي على التابع Edit الأول.
ملاحظة:سنقوم بالإشارة لجميع توابع action المطبق عليها الخاصية HttpGet بشكل افتراضي ب توابع HttpGet.
الخاصية Bind هي آلية حماية مهمة لمنع الهاكرز من إرسال بيانات الى ال Model(هجمات overposting). يجب دائما أن تضمين الخصائص التي نريد تعديلها في الخاصية Bind.
إن الخاصية ValidateAntiForgeryToken تستخدم لمنع الطلبات المزورة وهذه الخاصية مقترنة مع ()Html.AntiForgeryToken@ في ملف Views\Books\Edit.cshtml
إن ()Html.AntiForgeryToken@ يولد رمز لكشف التزوير وهذا الرمز يجب أن يطابق التابع Edit للمتحكم Books.
إن التابع HttpGet Edit يأخذ بارمتر واحد وهو Id الكتاب حيث يتم البحث عن الكتاب باستخدام التابع Find ويعيد الكتاب ل Edit view إذا لم يتم إيجاد الكتاب يتم إرجاع HttpNotFound.
عندما يتم توليد Edit view فإنه يقوم بذلك بالاعتماد على Book class حيث يقوم بإضافة كود لإظهار عناصر
الكود التالي هو ل Edit view الذي تم توليده:
نلاحظ وجود العبارة model ArabicArchive.Models.Book@ في بداية الملف، وهذه العبارة تحدد أن ال view تقبل ال model من النوع Book. نلاحظ أيضاً أنه في الكود يتم استخدام العديد من توابع helper لتسهيل عملية كتابة كود HTML. مثال:
- التابع Html.LabelFor الذي يعرض اسم الحقل (مثال “Title”).
- التابع Html.EditorFor يعرض عنصر <HTML <input في الصفحة.
- التابع Html.ValidationMessageFor يعرض رسائل التحقق المرتبطة بالخاصية.
قم بتشغيل التطبيق و انتقل الى الرابط Books/ قم بالنقر على الرابط Edit لأي كتاب. ثم في المتصفح قم بعرض كود الصفحة Page Source. الكود التالي هو كود Html لعنصر form:
إن الخاصية action للعنصر form موضوعة بالقيمة post والرابط الذي سيعالج هذا الطلب Books/Edit. وإن بيانات ال form سيتم إرسالها إلى السيرفر عندما يتم الضغط على زر Save.
السطر الثاني يعرض رمز XSRF المولد من قبل استدعاء التابع ()Html.AntiForgeryToken@
معالجة طلب Post
الكود التالي يعرض تابع HttpPost Edit:
// 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 ActionResult Edit([Bind(Include = "Id,Title,Description,PublishDate,Author,NumberOfPages,GoodreadsRate,TypeId")] Book book) { if (ModelState.IsValid) { db.Entry(book).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } ViewBag.TypeId = new SelectList(db.Types, "Id", "Title", book.TypeId); return View(book); }
تقوم الخاصية ValidateAntiForgeryToken بالتحقق من رمز XSRF المولد من قبل استدعاء، ()Html.AntiForgeryToken@ في ال view. ثم يقوم ASP.NET MVC model binder بأخذ قيم ال form المرر ويضيف Book object الذي يمرر كبارامتر للتابع. بعدها يقوم التابع ModelState.IsValid بالتحقق من أن البيانات المقدمة من ال form يمكن استخدامها لتعديل Book object.
فإذا كانت البيانات صالحة( صحيحة) فإن بيانات الكتاب يتم حفظها في لائحة Books لل db object (هو عبارة عن object من النوع BookDBContext للتعامل مع قاعدة البيانات).
بعد ذلك يتم حفظ التعديلات التي أجريناها على بيانات الكتاب في قاعدة البيانات باستدعاء التابع SaveChanges لـ db object. بعد حفظ البيانات يتم إعادة توجيه المستخدم إلى التابع Index في المتحكم BooksController حيث يتم عرض لائحة بجميع الكتب الموجودة في قاعدة البيانات بما في ذلك التعديلات التي قمنا بها.
في حال قام المستخدم بإدخال قيم غير صالحة في أحد الحقول أو مجموعة من الحقول فإن client side validation يقوم بعرض رسالة خطأ اسفل الحقل الذي تم فيه ادخال قيم غير صالحة. في حال تعطيل JavaScript فإن client side validation لن تعمل ولكن المخدم سيحدد بأن القيم المررة غير صالحة حيث يتم إعادة عرض ال form مع رسائل خطأ. لاحقاً سنتكلم عن التحقق بشكل مفصل عن ذلك.
إن Html.ValidationMessageFor helpers في Edit.cshtml view تقوم بعرض رسائل الخطأ المناسبة:
جميع توابع HttpGet تتبع نفس النمط حيث تقوم بجلب book object (أو لائحة من الobjects في حالة تابع Index) حيث يتم تمرير ال model إلى ال view.
إن جميع التوابع التي تضيف أو تعدل أو تحذف أو تقوم بأي عملية تعديل على البيانات فإنها تقوم بذلك من خلال تابع التحميل الزائد HttpPost overload.
تعديل البيانات في تابع HTTP GET هو خطر أمني أيضا تعديل البيانات في تابع HTTP GET يخالف ماهو مستحسن في HTTP و يخالف معمارية REST والتي تحدد أن طلبات GET يجب ألا تعدل حالة التطبيق.
بمعنى آخر تنفيذ عملية GET يجب أن تكون آمنة بحيث لاتملك أي تأثيرات جانبية ولاتعدل البيانات الموجودة.
في الدرس التالي سوف نقوم بتطبيق عملية البحث
تغذية راجعة
اتمنى عدم التردد في الاستفسار عن أي مفهوم تم ذكره في هذا التدوينة، وأرجو تجربة ماورد في التدوينة بشكل فعلي وعدم الاكتفاء بالقراءة لتحقيق أكبر فائدة ممكنة، وكالعادة سأكون سعيد إن شاركتموني تجربتكم وتصويباتكم في حال وجود أخطاء كتابية.
هذه المقالة مستندة إلى سلسلة دروس مايكروسوفت الرسمية للـ ASP.NET MVC، وذلك لترتيب الدروس المناسب واعتقادي بسلاستها وأهمية نقلها إلى العربية بأسلوب مناسب وتجربة تتوافق مع الأدوات المتاحة لنا والمتوفرة في منطقتنا.