في هذه التدوينة سوف نقوم بإضافة البحث الى تابع Index ، بحيث نستطيع في مشروعنا التجريبي البحث عن الكتب حسب الاسم أو النوع، يشمل الشرح توضيحات عن توابع Linq بالإضافة إلى وصف متكامل عن الكود اللازم لكتابة الواجهات اللازمة للعرض.
هذه المقالة جزء من سلسلة لتعلم أساسيات 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
سوف نقوم أولاً بتعديل تابع Index في BooksController class كما في الكود التالي:
// GET: Books public ActionResult Index(string searchString) { var books = from b in db.Books select b; if (!string.IsNullOrWhiteSpace(searchString)) { books = books.Where(b => b.Title.Contains(searchString)); } return View(books.ToList()); }
السطر الأول في تابع Index هو عبارة عن استعلام LINQ لجلب الكتب من قاعدة البيانات
var books = from b in db.Books select b;
إذا كان البارمتر searchString يحتوي على سلسلة نصية فإن الاستعلام السابق يتم تعديله ليقوم باختيار الكتب بالاعتماد على قيمة البارامتر
if (!string.IsNullOrWhiteSpace(searchString)) { books = books.Where(b => b.Title.Contains(searchString)); }
إن العبارة b => b.Title هي Lambda Expression
تستخدم Lambdas Expression كبارمترات دخل في استعلامات LINQ المبنية على التوابع مثل تابع Where المستخدم في الكود السابق.
لايتم تنفيذ استعلامات LINQ عند تعريفها أوعند تعديلها باستدعاء توابع مثل Where أو OrderBy وإنما يتم تأجيل تنفيذها وهذا يعني أن تقييم التعبير يؤخر حتى يتم المرور بحلقة على النتيجة المرجعة من الاستعلام أو استدعاء تابع ToList
في مثالنا فإن الاستعلام ينفذ في Index.cshtml view للمزيد من المعلومات حول تنفيذ الاستعلام المؤجل قم بالدخول للرابط التالي Query Execution.
ملاحظة: إن تابع Contains ينفذ في قاعدة البيانات، إن تابع Contains يقابل عبارة SQL LIKE في قاعدة البيانات وهي حساسة لحالة الأحرف.
قم بتشغيل التطبيق وانتقل إلى الرابط Books/Index/ قم بإضافة query string على سبيل المثال searchString=clean?
فيتم عرض الكتب المفلترة حسب السلسلة المدخلة
إذا قمنا بتعديل التابع Index بحيث يقبل بارامتر Id كدخل فإن هذا البارامتر يقابل العنصر {id} للتوجيه الافتراضي في ملف App_Start\RouteConfig.cs
{controller}/{action}/{id}
إن كود التابع Index هو كما في الكود التالي
// GET: Books public ActionResult Index(string searchString) { var books = from b in db.Books select b; if (!string.IsNullOrWhiteSpace(searchString)) { books = books.Where(b => b.Title.Contains(searchString)); } return View(books.ToList()); }
قم بتعديل التابع Index بحيث يصبح كما في الكود التالي
public ActionResult Index(string id) { string searchString = id; var books = from b in db.Books select b; if (!string.IsNullOrWhiteSpace(searchString)) { books = books.Where(b => b.Title.Contains(searchString)); } return View(books.ToList()); }
الآن نستطيع تمرير سلسلة البحث كجزء من الرابط بدلاً من query string
لانسطيع جعل المستخدم يقوم بتعديل الرابط في كل مرة يريد البحث فيها عن كتاب، سنقوم بدلاً من ذلك بإضافة واجهة لتمكين المستخدم من البحث. سنقوم بالتراجع عن التغييرات التي قمنا بها للتابع Index حيث سنرجع التابع Index ليقبل البارامتر searchString كبارامتر دخل
// GET: Books public ActionResult Index(string searchString) { var books = from b in db.Books select b; if (!string.IsNullOrWhiteSpace(searchString)) { books = books.Where(b => b.Title.Contains(searchString)); } return View(books.ToList()); }
قم بفتح الملف Views\Books\Index.cshtml وبعد السطر (“Html.ActionLink(“Create New”, “Create@
قم بإضافة الكود الملون الموضح بالأسفل:
@model IEnumerable<ArabicArchive.Models.Book>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm())
{
<p>
Title: @Html.TextBox("SearchString") <br />
<input type="submit" value="Filter" /> </p>
}
</p>
يقوم Html.BeginForm helper بإضافة وسم <form>
إن Html.BeginForm helper يقوم بإرسال form إلى المخدم عندما يقوم المستخدم بالنقر على زر Filter
في Visual Studio عندما نقوم بتشغيل التطبيق وملف ال view مفتوح فإن Visual Studio يقوم باستدعاء action method للمتحكم المناسب لعرض ال view
مع الإبقاء على ملف Index view مفتوح في Visual Studio قم بتشغيل التطبيق وقم بتجريب البحث عن كتاب.
لايوجد تابع تحميل زائد HttpPost للتابع Index ولانحتاج لمثل هذا التابع لأن التابع لايغير حالة التطبيق وإنما فقط يقوم بعملية الفلترة للبيانات، يمكن إضافة التابع HttpPost Index وفي هذه الحالة سيتم استدعاء التابع HttpPost Index وتنفيذه كما هو موضح بالصورة التالية:
[HttpPost]
public string Index(FormCollection fc, string searchString)
{ return "<h3> From [HttpPost]Index: " + searchString +</h3>";
}
في حال قمنا بإضافة التابع HttpPost Index فسيكون هنالك محدودية في التعامل معه. على سبيل المثال إذا أردت إرسال رابط للأصدقاء بحيث يمكنهم النقر عليه و رؤية نفس اللائحة من الكتب المفلترة.
حيث أننا نلاحظ أن الرابط لطلب HTTP POST هو نفسه لطلب GET
(localhost:xxxxx/Movies/Index) حيث أن الرابط لايحتوي على أي معلومات بحث.
حتى الآن فإن معلومات البحث ترسل إلى المخدم كقيمة حقل في ال form وبالتالي لانستطيع أخذ معلومات البحث لتخزينها أو لإرسالها للأصدقاء في رابط. إن الحل هو باستخدام تابع تحميل زائدBeginForm
الذي يحدد بأن طلب POST يجب أن يضيف معلومات البحث للرابط ويجب أيضاً أن يوجه هذا الطلب إلى نسخة HttpGet للتابع Index
قم باستبدال التابع BeginForm كما هو موضح بالكود التالي:
@using (Html.BeginForm("Index","Books",FormMethod.Get))
الآن عندما نقوم بإرسال الطلب إلى المخدم فإن الرابط يحتوي على سلسلة البحث أيضاً فإن الطلب سيذهب إلى التابع HttpGet Index على الرغم من وجود التابع HttpPost Index
إضافة البحث حسب نوع الكتاب
قم بحذف نسخة HttpPost للتابع Index في حال قمت بإضافتها. سنقوم بإضافة ميزة جديدة وهي البحث عن الكتاب حسب النوع. قم باستبدال التابع Index بالكود التالي:
// GET: Books public ActionResult Index(int? bookTypeId,string searchString) { var books = from b in db.Books select b; ViewBag.BookTypeId = new SelectList(db.Types, "Id", "Title"); if (!string.IsNullOrWhiteSpace(searchString)) { books = books.Where(b => b.Title.Contains(searchString)); } if (bookType!=null) { books = books.Where(x => x.TypeId == bookTypeId); } return View(books.ToList()); }
يأخذ التابع Index بارامتر إضافي وهو bookTypeId ويتم تخزين جميع أنواع الكتب في SelectList object في ViewBag حيث أن أول بارامتر لباني SelectList هو عبارة عن كل أنواع الكتب الموجودة في قاعدة البيانات. البارامتر الثاني هو عبارة عن الخاصية التي سيتم أخذ قيمتها عند اخيتار عنصر معين من اللائحة. البارمتر الثالث هو عبارة عن الخاصية التي سيتم عرضها في اللائحة.
إن الكود التالي يوضح كيف يتم اختبار البارامتر bookTypeId فإذا لم يكن Null يتم أخذ الكتب التي تحمل نفس رقم النوع الممرر بعين الاعتبار في الاستعلام وتضمينها في نتيجة الاستعلام:
if (bookType!=null) { books = books.Where(x => x.TypeId == bookTypeId); }
قم بإضافة Html.DropDownList helper إلى الملف Views\Movies\Index.cshtml كما هو موضح في الكود التالي
@model IEnumerable<ArabicArchive.Models.Book>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm("Index","Books",FormMethod.Get))
{
<p>
Type:@Html.DropDownList("BookTypeId", "All")
Title: @Html.TextBox("SearchString") <br />
<input type="submit" value="Filter" /> </p>
}
</p>
<table class="table">
في الكود التالي:
@Html.DropDownList("BookTypeId", "All")
إن البارامتر BookTypeId يوفر مفتاح ل DropDownList helper لإيجاد <IEnumerable<SelectListItem
في ViewBag
إن ViewBag.BookTypeId معرفة في تابع Index
// GET: Books
public ActionResult Index(int? bookTypeId,string searchString)
{
var books = from b in db.Books select b;
ViewBag.BookTypeId = new SelectList(db.Types, "Id", "Title");
if (!string.IsNullOrWhiteSpace(searchString))
{
books = books.Where(b => b.Title.Contains(searchString));
}
if (bookType!=null)
{
books = books.Where(x => x.TypeId == bookTypeId);
}
return View(books.ToList());
}
البارامتر “All” هو يؤمن خيار في اللائحة وهو عبارة عن سلسلة نصية تعرض في اللائحة وهذه السلسلة النصية ليس لها قيمة مقابلة لها (value) اي عندما نقوم باختيار الخيار “All” في اللائحة فإن المتصفح سيرسل Null في المتحول bookTypeId وبالتالي سيتم تجاهل عملية الفلترة حسب نوع الكتاب اي سيتم أخذ جميع أنواع الكتب بعين الاعتبار في الاستعلام. يمكن وضع خيار يتم اختياره بشكل افتراضي فمثلاً إذا اردنا أن يكون الخيار “Programming” كخيار افتراضي فإننا نقوم بتعديل الكود في المتحكم كما في الكود التالي:
ViewBag.BookTypeId = new SelectList(db.Types, "Id", "Title", db.Types.Single(t => t.Title == "Programming").Id);
قم بتشغيل التطبيق وانتقل إلى Books/Index/ قم بتجريب البحث حسب نوع الكتاب وحسب اسم الكتاب وحسب النوع والاسم معاً:
ملاحظة: في Books/Index/ نلاحظ وجود عامودان باسم Title حيث أن العمود الأول هو للخاصية Title للنوع
(Type class)والعمود الثاني هو للخاصية Title للكتاب(Book class)
في التدوينة القادمة سوف نقوم بإضافة خاصية جديدة ل Book Model.
تغذية راجعة
اتمنى عدم التردد في الاستفسار عن أي مفهوم تم ذكره في هذا التدوينة، وأرجو تجربة ماورد في التدوينة بشكل فعلي وعدم الاكتفاء بالقراءة لتحقيق أكبر فائدة ممكنة، وكالعادة سأكون سعيد إن شاركتموني تجربتكم وتصويباتكم في حال وجود أخطاء كتابية.
هذه المقالة مستندة إلى سلسلة دروس مايكروسوفت الرسمية للـ ASP.NET MVC، وذلك لترتيب الدروس المناسب واعتقادي بسلاستها وأهمية نقلها إلى العربية بأسلوب مناسب وتجربة تتوافق مع الأدوات المتاحة لنا والمتوفرة في منطقتنا.