إضافة ميزة البحث إلى موقع ASP.NET MVC

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

هذه المقالة جزء من سلسلة لتعلم أساسيات ASP.NET MVC للمبتدئين:

سوف نقوم أولاً بتعديل تابع 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، وذلك لترتيب الدروس المناسب واعتقادي بسلاستها وأهمية نقلها إلى العربية بأسلوب مناسب وتجربة تتوافق مع الأدوات المتاحة لنا والمتوفرة في منطقتنا.

مدير تقني وشريك مؤسس لـ فسيلة تِك، مبرمج متعدد المهارات، مهتم في إنجاز أمور استثنائية في مجال التكنولوجيا وأعمل جاهداً لترك أثر إيجابي في الحياة