هل أنت مستعد للعمل هنا؟…ستكتب 1000 سطر برمجي اليوم… لنبدأ
يتكون هذا التطبيق من ثلاثة أجزاء..وجميعها سنعمل عليها هنا..يتكون من إنشاء قاعدة البيانات والتطبيق…ويتكون من إنشاء إدارة التطبيق..ويتكون من عمل إختبار الشفرات للتطبيق.
مقدمة
هذا التطبيق به العديد من الخصائص التي قمت بحذفها منه وذلك تسهيلا للشرح والتطبيق الفعلي الذي أنشأته يختلف اختلافا تاما عن ما سأقدمه لك هنا عزيزي المطور ولكن هذا التطبيق الذي ستنشئه هنا سيكون مكتملا من ناحية تعليمية وليس من ناحية عملية..أي لا تستطيع تقديمه لإدارة الجوازات لنشره، اللهم إلا اذا قمت بتطويره بصورة محترفين..وفي الحقيقة سنستخدم في قاعدة البيانات جدول واحد Persons وهذا غير منطقي في حال اردت أن تقوم بتقديمه لإدارة الجوازات ..ففكرة التطبيق هنا (وليس الحقيقي) بسيطة، المواطن سيقدم جوازه للتجديد وسيأتي لإستلامه..والجواز الخاص به سيكون جاهزا فهنا لم أقم بإنشاء جدول يوضح هل تم تجديد الجواز ام لا ولم أقم بإنشاء جدول يوضح في حال تجديد الجواز أين يوجد الجواز الآن وفي أي رفٍ يوجد بمكتب التسليم.
ملاحظة: هل هذا يعني بأنني لا أريد مشاركة الشفرات التي أكتبها معك عزيزي المطور؟! الإجابة بلا..لا أعني هذا ابدا.. لكن حتى تكون على علم…المشروع هذا به العديد من الخصائص التي قمت بحذفها حتى يصبح بسيطا في الشرح هنا فالمشروع هذا قد قمت بتقديمه في ورشة صغيرة في إحدى الجامعات بالسوان لقسم تقانة المعلومات بتلك الجامعة…وقد قمت بإكماله بعد سفري من السودان بصورة تامة حيث من حيث الـ Security والـ Performance وتعديل جميع الشفرات التي ستراها انت هنا جميعها قمت بتغييرها وقد قمت بإستخدام انماط التصميم Design patterns والـ Instrumentation والـ Profiler والـ CLR Profiler و مكتبات الـ Jquery و مكتبة الـ Caching والكثير من الخصائص..كل تلك الخصائص قمت بحذفها من هنا حتى لا يصبح الشرح عسيرا بالنسبة لي أو لانه سيأخذ أيام عديدة ومع ظروف عملي قد يبدو صعبا كتابة شرح لجميع الأكواد والخصائص..وأيضا لتكون على علم عزيزي المطور لقد قمت بعرض المشروع على المسؤولين (…) هناك ولكن لم أجد أي استجابة (…)..فعندما أقول بأنني لن أقوم بعرض جميع شفرات المشروع الحقيقي وشفرات الإختبار Unit Tests فهذا يعني تقليلا للعمل هنا فقط…وسأحاول في مرات قادمات نشر مقالات توضح كيفية اختبار الأكواد مع شرحها ولأنني هنا قمت بنشر أكواد اختبار الشفرات بدون شرح و التي تساعدنا في اختبار برمجياتنا.
الجزء الأول – إنشاء قاعدة البيانات والتطبيق
بسم الله والصلاة والسلام على رسول الله…في هذا التطبيق سنقوم بمراجعة تامة للـ ASP.NET MVC التطبيق بسيط جدا..وقد كنت قمت ببنائه لغرض مــا..كنت في السودان قبل فترة وعندما أردت تجديد الجواز الخاص بي وعند استلام الجوازكان الإستلام بالبحث عن اسم صاحب الجواز على سبورة وبالتالي العملية كانت صعبة جدا..فقررت بناء تطبيق متكامل لتسليم الجوازات عن طريق جهاز الحاسوب..وقمت ببناء التطبيق وللحقيقة التطبيق الذي ستبنيه اليوم هو تطبيق مصغر جدا من التطبيق الحقيقي، حيث يحتوي التطبيق الحقيقي على العديد من الإمكانيات المتقدمة جدا والتي قمت بحذفها من هنا لكي نتعلم بسهولة تامة كيفية إستخدام الـ ASP.NET MVC في بناء التطبيقات، هذا من ناحية، من الناحية الأخرى ستتعلم كيفية استخدام مختبر الشفرات Unit Tests وعمل اختبار للشفرات التي ستكتبها هنا…وكل هذا سيتم داخل بيئة الفيجوال ستديو…وكالعادة البيئة التي اعمل عليها هو نظام التشغيل Windows بالإضافة للـ Visual Studio 2013 زائداً لغة قواعد البيانات SQL Server من داخل بيئة الـ SQL Server Management Studio، لكن يمكنك استخدام بيئة الفيجوال ستديو نفسها لإنشاء قواعد البيانات والجداول لقواعد البيانات تلك والإجراءات المخزنة Stored Procedures إذا أردت أيضا عن طريق استخدام الـ LocalDB والتي يتم تثبيتها مع الفيجوال ستديو 2013، أيضا بإمكانك استخدام فيجوال ستديو 2012 أو 2015 أو 2017 لعمل هذا التطبيق.
سأحاول بقدر الإمكان وضع التطبيق في سياقه الصحيح مع شرح الأكواد التي سنكتبها.
إنشاء المشروع
قم بفتح الفيجوال ستديو وقم بإنشاء تطبيق جديد قم بإختيار الفريم وورك 4.5 ومن على يسار الشاشة قم بالضغط على Web مرتين Double-Click وقم بإختيار Visual Studio 2012 وقم بإختيار المشروع ASP.NET MVC 4 Application قم بتسمية المشروع MvcExplain (بإمكانك تسميته بما يعجبك) وقم بتحديد مسار حفظ التطبيق على أي قرص صلب على جهازك وقم بالضغط على OK. (مرفق الصورة)، بعدها ستظهر شاشة قم بإختيار Basic لهيكلية المشروع ولا تنسى ان تختار No Authentication للمشروع قم أيضا بإختيار محرك نصوص Razor لصفحات الـ Html وقم بالضغط على OK.
إنشاء قاعدة البيانات
بعد أن يقوم الفيجوال ستديو بإنشاء التطبيق قم بفتح الـ Server Explorer، من تلك الشاشة قم بالضغط على العلامة التي بها شعار موصل الكهرباء والتي ملصق عليها علامة الجمع (إنظر للصورة في الأسفل) بالضغط عليها ستفتح معك شاشة لإدخال المزود الخاص بك قم بإدخال LocalDb)\v11.0) على خانة الـ Server name وفي خانة Select or enter a database name قم بإدخال اسم قاعدة البيانات التالية PassportRecive واضغط على Ok سيسألك الفيجوال ستديو بأنه لاتوجد قاعدة بيانات بهذا الإسم في المزود الخاص الذي قمت بإدخاله، هل تريد إنشاء قاعدة بيانات جديدة بهذا الإسم قل له نعم بالضغط على OK.
الآن لقد أنهيت الخطوة الأولى التي تعنى بإنشاء قاعدة البيانات، الآن سنقوم بإنشاء الجدول والحقول التي ستعيننا على تكملة هذا المشروع بصورة صحيحة.
قم بالضغط مرتين Double-Click بالزر الأيسر للمؤشر (ماوس) على قاعدة البيانات التي أنشئتها ستفتح معك عدة خيارات داخل قاعدة البيانات تلك قم بالضغط بالزر الأيمن للمؤشر (ماوس) على Table ومن الشاشة التي ستظهر لك قم بإختيار Add New Table قم بلص الشفرة التالية على أسفل الشاشة او شاشة الإستعلامات التي سيفتحها الفيجوال ستديو تلقائيا:
CREATE TABLE Persons ( [PersonID] INT NOT NULL PRIMARY KEY IDENTITY, [Name] NVARCHAR(50) NOT NULL, [NID] NVARCHAR(50) NOT NULL, [Recived] BIT NULL, [RecivedDate] DATETIME NULL )
أعلى الشاشة في نفس الصفحة توجد كلمة Update قم بالضغط عليها حتى يقوم الفيجوال ستديو بإنشاء الجدول وحقول ذلك الجدول. ستفتح لك صفحة تأكيدية بها خاصيتان واحدة لعمل تحديث والأخرى لتوليد نص لقاعدة البيانات، قم بالضغط على Update Database.
في هذا الجدول لدينا رقم المواطن الذي ستتولى قاعدة البيانات توليده، ولدينا اسم المواطن ورقمه الوطني NID ولدينا حقل من النوع BIT ويعنى بإستلام الجواز من عدمه وحقل أخير يُعنى بتاريخ استلام الجواز.
قم بتنفيذ الجملة الإستعلامية تلك على قاعدة البيانات..وستقوم تلك الجملة بإضافة حقلين لقاعدة البيانات:
SET IDENTITY_INSERT [dbo].[Persons] ON INSERT INTO [dbo].[Persons] ([PersonID], [Name], [NID], [Recived], [RecivedDate]) VALUES (1, N'Mohammed', N'123-87847-888', NULL, NULL) INSERT INTO [dbo].[Persons] ([PersonID], [Name], [NID], [Recived], [RecivedDate]) VALUES (2, N'Ahmed', N'554-78478', NULL, NULL) SET IDENTITY_INSERT [dbo].[Persons] OFF
*** ملاحظة: في المشاريع الكبيرة دائما ما نستخدم شاشة الإستعلام النصي لإنشاء قواعد البيانات وحفظها على الأقراص الصلبة وعمل أرشفة لقاعدة البيانات بتقنية الـ JSON أو الـ XML أو الـ BSON..ومن خلال خبرتنا فهي مهمة جدا…لكن لأن المشروع هذا بسيط جدا مقارنة بالمشاريع الكبيرة فلن تحتاج لعمل كهذا…لكن إذا قامت إدارة الجوازات بطلب تنفيذ هذا المشروع فبالتالي لن يكون بتلك الصورة كما ذكرت لكم في المقدمة بأنني حذفت العديد من الخواص والخصائص حتى يكون الشرح هنا مبسطا.
العمل على المشروع الآن
بعد إنشاء قاعدة البيانات سنقوم بعمل ربط بين قاعدة البيانات والتطبيق من خلال ملف الـ Web.Config عن طريق فرع الإتصال connectionString ولتقوم بالربط قم بفتح ملف الـ Web.config وقم بلصق الشفرة التالية عليه:
<connectionStrings> <add name="PersonDbContext" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=PassportRecive;Integrated Security=True" /> </connectionStrings>
لا تنسى كتابة شفرة الإتصال تلك في سطر واحد..وان لم تفعل فلن يتم الإتصال بين التطبيق وقاعدة البيانات.
في نص الإتصال لقد قمت بإدخال المزود الخاص بي وهو LocalDb\v11.0 وإذا كنت تعمل على هذا المزود قم بإدخاله كما مبين سابقا وإن كنت تعمل على مزوّد آخر قم بإدخال المزوّد الذي تعمل عليه.
قم بالضغط بالزر الأيمن على المؤشر (ماوس) على ملف الـ Models وقم بإنشاء فئة جديدة بإسم Person وقم بلصق الشفرة التالية في صفحة تلك الفئة:
[Table("Persons")] public class Person { [HiddenInput(DisplayValue=false)] public int PersonID { get; set; } [Required(ErrorMessage="Please Enter The Name")] public string Name { get; set; } [Required(ErrorMessage = "Please Enter The NID")] public string NID { get; set; } [HiddenInput(DisplayValue=false)] public bool? Recived { get; set; } [HiddenInput(DisplayValue=false)] public Nullable<DateTime> RecivedDate { get; set; } }
قم بإضافة فضاء الأسماء التالية أعلى الفئة:
using System.Web.Mvc; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema;
الفئة Person توجد بها نفس الحقول الموجود على الجدول الذي أنشأناه بقاعدة البيانات مع بعض الخواص لكل Property والخواص تلك فرضا تظهر في اسم المواطن توجد خاصية Attribute بإسم Required وستفيدنا عند إستلام الجواز من المواطن حيث ستفرض على المستخدم العادي ادخال الاسم وإلا ستظهر له رسالة خطأ كما موضّحة في المعامل Parameter الذي تأخذه تلك الخاصية..الخاصية الأخرى HiddenInput وقد قمنا ببلورتها لبعض الحقول أيضا كتاريخ استلام الجواز وتعني هنا عدم إظهار الحقل أثناء إنشاء حقل جديد لمواطن ما..لا تقلق سترى كيف تعمل تلك الخواص عندما ننشئ صفحات الـ Html.
بالإمكان استخدام الـ Regular Experssion إذا أردت لعمل تحقق يجبر المستخدم على إدخال ارقام فقط بالنسبة للرقم الوطني.
لو لاحظت أن اسم المزوّد في ملف الـ web.Config هو PersonDbContext ولكي نقوم بربط التطبيق بقاعدة البيانات سنُنشئ فئة Class بهذا الإسم داخل الملف Models وسنقوم بكتابة الشفرة التالية عليها:
public class PersonDbContext : DbContext { public PersonDbContext() : base("name=PersonDbContext") { } public DbSet<Person> Persons { get; set; } }
لا تنسى ان تقوم بإستدعاء فضاء الأسماء التالي في أعلى الفئة:
using System.Data.Entity;
في هذه الفئة قمنا بإستخدام تقنية الـ Entity Framework وهي تقنية من مايكروسوفت تقوم بإنشاء جداول وهمية على شكل فئات تمكننا من العمل عليها بدلا من العمل مباشرة على الجداول التي موجودة بقاعدة البيانات وتلك الجداول الوهمية تعتبر كـ Strongly type أي بمعنى أنه سيقوم الفيجوال ستديو بمساعدتنا للعمل عليها من خلال التكملة عند الكتابة في بيئته أو إذا كان هناك خطأ سيقوم الفيجوال ستديو بإخطارنا بالخطأ Compile time error ومساعدتنا على الأخطاء التي تظهر..وتلك التقنية أتت بديلا لتقنية الـ ADO.NET التي نذكرها جميعا والتي تعمل على تنفيذ الجمل الإستعلامية بصورة نصية داخل بيئة الفيجوال ستديو..وكما هو معروف العمل على الإستعلام النصي سيكون به أخطاء مؤكدة في كتابة الكلمات..ولن يستطيع الفيجوال ستديو اكتشاف الخطأ لأنه من النوع String وسنستخدم تقنية الـ LinQ لتنفيذ الجمل الإستعلامية في التطبيق نسبة لسهولتها وأيضا هي Strongly type.
الفئة التي أنشأناها سابقا تحتوي على خاصية Property واحدة وهي Persons وهذه الخاصية مطابقة لإسم الجدول الذي أنشأناه في قاعدة البيانات PassportRecive سابقا، أيضا الفئة ترث من فئة أخرى وهي الـ DbContext وهي تمكننا من عمل الإستعلامات في قاعدة البيانات والجداول الموجود في قاعدة البيانات تلك. أيضا إذا لاحظت قمنا بتعريف اسم الإتصال بصورة صريحة Explicitly من خلال مُنشئ الفئة Constructor. وهذا يقوم بإخبار التطبيق بإستخدام اسم الإتصال PersonDbContext هذا لعمل إتصال مع قاعدة البيانات الموضحة في ملف الـ web.Config.من خلال الجزع Catalog.
ليس بالضرورة استدعاء منشئ الفئة Constructor … لكن هذا للتوضيح..لكن ستحتاجه إذا كان اسم الفئة Class مغاير لإسم الإتصال في ملف الـ web.Config.
قم بالضغط بالزر الأيمن للمؤشر (ماوس) على اسم المشروع في الفيجوال ستديو وقم بإنشاء المجلدات التالية:
Infrastructure Repo
في نفس ملف الـ Models قم بإنشاء واجهة Interface جديدة بالإسم IPerson وقم بلصق الشفرة التالية داخلها:
public interface IPerson { IQueryable<Person> GetAllPersons { get; } Person GetPersonByID(int personID); Person GetPersonByHisNID(string personNID); Person RecivedHisPassport(int personID); void SavePassportInfo(Person person); }
سنقوم يإستخدام فصل الأشياء عن بعضها DI وسنستخدم تقنية الـ Entity Framework لإنشاء مستودع لجلب وتنفيذ العمليات التي نريدها من قاعدة البيانات عن طريق تلك التقنية EF وقد قمنا بإستخدام الـ Code First بإنشائنا للفئة Person يدويا وعدم توليدها من قاعدة البيانات..ولفصل الأشياء من بعضها سنحتاج لإحدى المكتبات التي تساعدنا على ذلك وسنستخدم مكتبة الـ Ninject، إذا كنت تريد فهم ماذا نعني بـ Dependency Injection قم بمراجعة المقال الموضح في الرابط أدناه والمنشور على نفس منصة انفورماتيك والتي شرحت فيها طريقة فصل الأشياء DI عن طريق استخدام مكتبة الـ Ninject، وسترى كيفية تهيئة تلك المكتبة للعمل مع تطبيقات الـ ASP.NET MVC.
كيفية تهيئة مكتبة الـ Ninject للعمل مع مشروع الـ ASP.NET MVC
اضغط على TOOLS في اعلى الشريط المساعد الخاص بالفيجوال واختار الـ Library Package Manager والتي ستظهر لك خيارات عدة إضغط على اول خيار Package Manager Console واكتب السطر التالي:
PM> Install-Package Ninject -Version 3.0.1.10
في هذه الخطوة قمنا بتنزيل مكتبة الـ Ninject وهي المكتبة التي تساعدنا على فصل الفئات عن بعضها البعض بإستخدام الواجهات Interfaces.
في الواجهة التي أنشأناها سابقا لدينا خمسة أساليب Methods الأسلوب الأول يُعني بإرجاع جميع المواطنين من قاعدة البيانات والآخر يقوم بإرجاع مواطن واحد بالرقم الذي تنشئه قاعدة البيانات Identity والأسلوب الثالث يقوم بإرجاع بيانات المواطن عن طريق رقمهُ الوطني والرابع يقوم بتنفيذ جملة Query على قاعدة البيانات في حقل المواطن بعمل تسليم لجواز المواطن والأسلوب الأخير يُعني بحفظ بيانات مواطن جديد يريد عمل إصدار أو تجديد جواز جديد.
بعد إنشاء تلك الواجهة سنقوم بإنشاء فئة ستقوم بعمل تنفيذ Implementation للأساليب الخمسة التي ذكرناها آنفا. وستتولى مكتبة الـ Ninject مهمة حقن تلك الفئة عندما نقوم بطلبها من بيئة الـ MVC Framework من خلال عمل طلب Request او إرسال Response من خلال فئة التحكم Controller من المتصفح لديك.
قم بإنشاء فئة Class في ملف الـ Repo الذي أنشأته سابقا وقم بتسمية تلك الفئة بـ PersonRepository وتلك الفئة عبارة عن تنفيذ للأساليب الموجودة بالواجهة IPerson لذلك سترث منها، الآن قم بنسخ الشفرة التالية داخل تلك الفئة:
public class PersonRepository : IPerson { private PersonDbContext _dbContext = new PersonDbContext(); public IQueryable<Person> GetAllPersons { get { return _dbContext.Persons; } } public Person GetPersonByID(int personID) { return _dbContext.Persons.FirstOrDefault(p => p.PersonID == personID); } public Person GetPersonByHisNID(string personNID) { return _dbContext.Persons.FirstOrDefault(p => p.NID == personNID); } public Person RecivedHisPassport(int personID) { // First Person p = (from x in _dbContext.Persons where x.PersonID == personID select x).First(); // Second p.Recived = true; p.RecivedDate = DateTime.Now; _dbContext.SaveChanges(); return p; } public void SavePassportInfo(Person person) { if (person.PersonID == 0) // Save new one { person.RecivedDate = DateTime.Now; person.Recived = false; _dbContext.Persons.Add(person); } else // Edit exciting one { Person dbEntry = _dbContext.Persons.Find(person.PersonID); if (dbEntry != null) { dbEntry.Name = person.Name; dbEntry.NID = person.NID; dbEntry.Recived = false; } } _dbContext.SaveChanges(); } }
لا تنسى إستدعاء فضاء الأسماء التالي:
using MvcExplain.Models; using System.Data.Entity;
ماذا لدينا في تلك الفئة التي أنشأناها سابقا؟ في لغة الـ C# هناك متحكم شامل بتلك اللغة يدعى CLR وهو الذي يتولى ترجمة الأكواد التي نكتبها بتلك اللغة..في حالة الواجهات Interfaces لو لاحظت لقد قمنا بإنشاء واجهة اسمها IPerson تلك الواجهة تأخذ النوع public للوصول إليها وجميع الأساليب التي بها يجب ان لا تأخذ أي نوع وصول Member accessibility ولذلك سيجبرك المترجم على تعريفها داخل الفئات التي سترث من تلك الواجهة IPerson وفي حالتنا تلك ستكون الفئة هي الـ PersonRepository فجميع الأساليب أخذت أمكانية الوصول لجميع عناصرها النوع public. أيضا الـ CLR سيقوم ببلورة تلك العناصر بالكلمة virtual أثناء عمل التطبيق وإن لم تقوم بكتابة تلك الكلمة بصورة صريحة للعنصر سيقوم المترجم بكتابتها بدلا منك وسيضيف الكلمة sealed إليها وذلك يعني لن تستطيع الوراثة من هذا الإسلوب Method. والعكس إن قمت بكتابتها بصورة صريحة سيقوم المترجم بإتاحة الفرصة لك بالوراثة منها والكتابة فوقها override مع الأخذ في الإعتبار أن المترجم سيضيف كلمة virtual في كلا الحالتين.(لا تشغل نفسك بتلك المعلومات ان كنت لا تملك خبرة عن كيفية ترجمة أكواد لغة الـ C# وهي ليست ضرورية لفهم عمل وآليه التطبيق الذي نبنيه الآن…فقط أردت أن أشرح لك كيفية عمل الوراثة من الواجهات Interfaces).
الآن الفئة PersonRepository ترث من الواجهة IPerson وتقوم بتنفيذ جميع الأساليب الموجودة بالواجهة IPerson. ولأننا سنعمل مع قاعدة لبيانات قمنا بتعريف مثيل للفئة التي تربط التطبيق بقاعدة البيانات وهي الفئة PersonDbContext من النوع private وهذا يعني عدم الوصول إليها من خارج تلك الفئة.
الإسلوب الأول GetAllPersons يتولى مهمة جلب جميع المواطنين من قاعدة البيانات وقمنا بإستخدام الـ Generic من النوع IQuerable والتي بدورها ترث من الـ IEnumarable والتي تقوم بتنفيذ عملية Foreach بالنسبة لقاعدة البيانات .
الإسلوب الثاني GetPersonByHisID يقوم بجلب مواطن واحد من قاعدة البيانات وقمنا بإستخدام تقنية الـ LinQ بإستخدام الـ Element Operator والتي تُدعى FirstOrDefault والتي تقوم بعمل تجميع للمواطنين ثم تقوم بإستدعاء المواطن المطلوب الذي تمت إضافته في الـ constructor هذه العملية تقوم بإرجاع القيمة null إذا لم تجد في التجميع الذي تقوم بعمله الرقم المطابق للرقم الذي أسندناه في الـ constructor.
ويمكنا كتابتها بتلك الصورة فرضا لو كنا نريد أرجاع المواطن الذي رقمه 1
var person = (from c in _dbContext.Persons where c.PersonID == 1 select c).FirstOrDefault();
بإمكانك بدلا من الرقم واحد إسناد المعاملpersonID.
الإسلوب الثالث شبيه بالعنصر الثاني..الفرق اننا سنقوم بإسترجاع قيمة من النوع String.
الإسلوب الرابع RecivedHisPassport يقوم بعمل تنفيذ لجملة في قاعدة البيانات لمواطن مــا، تقوم بتغير الحقل Recived في قاعدة البيانات لـ True والتي تعني أن المواطن قد استلم جوازه..في هذا الإستعلام يتم عمل تجميع للمواطن اولا..هنا:
Person p = (from x in _dbContext.Persons where x.PersonID == personID select x).First();
بعدها يتم إختيار المواطن الذي رقمه مطابق للمعامل Parameter الذي تم اسناده للأسلوب هذا.. بعدها سنقوم بعمل عمليتين على هذا المواطن وهي تغيير الحقل Recived للقيمة True وأيضا سيتم إضافة تاريخ الإستلام للحقل RecivedDate ونقوم بالحفظ وإرجاع القيمة للمواطن داخل قاعدة البيانات والذي يتم في تلك الجملة:
p.Recived = true; p.RecivedDate = DateTime.Now; _dbContext.SaveChanges(); return p;
الإسلوب الأخير يُعني بحفظ مواطن جديد وتعديل مواطن موجود مسبقا على قاعدة البيانات..وتعمل بصورة مبسطة إذا كان الرقم الذي تم إرساله لقاعدة البيانات موجود إذاً قم بالتعديل الذي تستلمه من الـ Caller وإذا كان غير موجود قم بإنشاء حقل جديد.
الآن انتهينا من تهيئة قاعدة البيانات والجمل التي سنستخدمها لتعيينعا على العمل على قاعدة البيانات…الآن سنقوم بالتوجه لعمل صفحات الـ Html وسنرى كيفية استخدام الجمل التي أنشأناها مسبقا.
في المجلد Infrastructure قم بإنشاء فئة Class بالإسم التالي NinjectControllerFactory وقم بلصق الكود التالي بداخلها:
public class NinjectControllerFactory : DefaultControllerFactory { private IKernel _kernel; public NinjectControllerFactory() { _kernel= new StandardKernel(); AddDefaultBingings(); } protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return controllerType == null ? null : (IController) _kernel.Get(controllerType); } private void AddDefaultBingings() { _kernel.Bind<IPerson>().To<PersonRepository>(); } }
لا تنسى أن تقوم بإستدعاء فضاء الأسماء التالي:
using System.Web; using System.Web.Routing; using System.Web.Mvc; using Ninject; using MvcExplain.Models; using MvcExplain.Repo;
قم بلص الشفرة التالية على ملف الـ Global.asax داخل الإسلوب Application_Start
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
iقم بإنشاء Controller جديدة وأعطها اسم PersonController بالضغط على ملف Controllers وأختيار Add لا تقم بتغيير أي شئ عدا الاسم وقم بالضغط على OK. كما موضح بالصورة التالية:
قم بلصق الكودة التالي بتلك الفئة:
public class PersonController : Controller { private IPerson repository; public PersonController(IPerson repo) { this.repository = repo; } // // GET: /Person/ public ActionResult Index() { return View(repository.GetAllPersons); } public ActionResult SerachForPerson(string q) { var persons = repository.GetPersonByHisNID(q); if (persons != null) { return PartialView("_PersonSearch", persons); } else { return View("NotFound"); } } [HttpGet] public ViewResult PersonRecivedHisPassport(int passID) { Person person = repository.GetPersonByID(passID); if(person==null) // return View("HasRecived"); else if (person.Recived==true) return View("HasRecived"); else return View(); } [HttpPost] public ActionResult PersonRecivedHisPassport(int passID, FormCollection form) { Person person = repository.RecivedHisPassport(passID); if (person != null) { TempData["message"] = string.Format("{0} OK", person.Name); return RedirectToAction("Index"); } else { return View("NotFound"); } } }
لا تنسى استدعاء فضاء الاسماء التالي:
using MvcExplain.Models; using MvcExplain.Repo;
لدينا ثلاثة أساليب واحد يُعني بإرجاع جميع المواطنين والثاني يُعني بالبحث عن مواطن نريد أن نقوم بتسليمه لجوازه والثالث بقوم بتنفيذ الإسلوب RecivedHisPassport الذي أنشأناه في فئة الـ PersonDbContect والذي مهمته تغيير حقلي الـ Recived والـ RecivedDate في قاعدة البيانات.
قم بالضغط على مرتين Double-Click بالمؤشر (ماوس) على ملف الـ Content ستجد بداخله ملف لتنسيق صفحات الـ Html واسمه Site.css قم بفتحه ولصق الشفرة التالية بداخله:
TABLE.Grid TD, TABLE.Grid TH { border-bottom: 1px dotted gray; text-align:left; } TABLE.Grid {border-collapse: collapse;width:50%; margin:30px 0 0 100px;} TABLE.Grid TH.NumericCol, Table.Grid TD.NumericCol {text-align: right; padding-right: 1em; } #mainform {} #searchform { margin:120px 0 0 400px;} DIV.searchresults {margin:120px 0 0 400px; font-size:x-large;} input.text {width:50%;padding:16px 20px;box-sizing:border-box;border:2px solid red;border-radius:5px;font-size:x-large;} input.button {width:50%; padding:8px 32px; box-sizing:border-box;font-size:x-large;}
ملاحظة: دائما إذا كنت تريد أداء أفضل لتطبيقك قم بكتابة خصائص ملف الـ Css في سطر واحد بالنسة للـ Element.
قم بفتح ملف الـ _Layout.chtml وستجده في ملف الـ Views داخل ملف الـ Shared وقم بلصق الكود التالي داخل الـ Element التي تُسمى Body:
<div id="messagetemp"> @if (TempData["message"] != null) { <div class="message" style="background:green;color:white;padding:.2em;margin-top:.25em"> @TempData["message"] </div> } <div class="savemessage"> @if (TempData["savemessage"] != null) { <div class="message" style="background:green;color:white;padding:.2em;margin-top:.25em"> @TempData["savemessage"] </div> } </div> </div>
في الشفرة أعلاه قمنا بإستخدام الخاصية TempData لحالتين لدينا كإشعار في حال تم تسليم المواطن لجوازه في حال تم استلام جواز لمواطن ما..وتلك الخاصية في بيئة الـ MVC ترث من TempDataDictionary وكما معروف عنها تحميل قيمتان على هذا المنوال Key-Vlaue وصفحة الـ _Layout.chtml تعتبر مثل صفحة الـ master page في الـ ASP.NET Web Form وستقرأ هذه الرسالة من الأسلوبين الموجودين في فئتي التحكم HomeController والـ AdminControler والتي سنضيفها لاحقا.
قم بفتح ملف الـ RouteConfig.cs والذي ستجده في ملف الـ App_Start وقم بلصق الكود الشفرة التالية داخل الأسلوب RegisterRoutes:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "PassRecive", url: "person/recivehispassport/{passID}", defaults: new { controller = "Person", action = "PersonRecivedHisPassport", passID = (int?)null } ); routes.MapRoute( name: "EditExcitePersonInfo", url: "person/passport/Edit/{personId}", defaults: new { controller = "Admin", action = "EditPersonInfo", personId = (int?)null } ); routes.MapRoute( name: "CreateNewPersonInfo", url: "person/passport", defaults: new { controller = "Admin", action = "CreatePersonInfo" } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Person", action = "Index", id = UrlParameter.Optional } );
هنا قمنا بإضافة أربعة عناوين وصول..الأول لصفحة تسليم الجواز والثاني لتعديل بيانات مواطن والثالث لإنشاء بيانات جديدة لمواطن ما..والأخير للصفحة الرئيسية التي لدينا.
ملاحظة: الكثير من المبرمجين لا يقومون بإستخدام عناوين وصول مشابهة للأساليب التي ينشؤونها في فئات التحكم بل يقومون بإستخدام عنوان الوصول الإفتراضي الذي ينشئه الفيجوال ستديو…من خلال خبرتنا العملية أود أن تأكيد أن إنشاء عناوين وصول خاصة بك سيسهل عملية البحث في موقع بالنسبة للزوار الذين يستخدمون محركات البحث كـ Google و Bing وهذا ما يعرف بالـ SEO وهي إختصار لـ Search Engine Optimization وتعريف بتحسين البحث على محركات البحث والتي تتم عن طريق إنشاء عناوين الوصول الخاصة بك على مشروعك.
الآن سننشئ صقحة الـ Html الأولى وهي صفحة الـ Index
قم بفح الـ HomeContoller التي أنشأناها سابقا وقم بالضغط بالزر الأيمن للمؤشر (ماوس) على الإسلوب Index وقم بالضغط على OK سيقوم الفيجوال ستديو بإنشاء صفحة بإسم Index داخل الملف Person والذي بدروه أنشئ داخل الملف Views
ملاحظة: هذه طريقة متبعة Convention في تقنية الـ MVC وهي كالتراتيبية للملفات والحرف الأوسط لتلك التقنية حرف الـ V والذي يعني View الخاص بالصفحات التي تم إنشاءها في الحرف الأخير C والتي تعني الـ Controller والتي بدورها تقرأ جميع بيناتها من الحرف الأول M التي تُعنى بالفئات داخل الـ Models هذا ببساطة شديدة.
قم بلصق الشفرة التالية داخل صفحة الـ Index:
@model IEnumerable<MvcExplain.Models.Person> @{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="mainform"> @using (Ajax.BeginForm("SerachForPerson", "Person", new AjaxOptions { InsertionMode=InsertionMode.Replace, HttpMethod="GET", UpdateTargetId="searchresults", })) { <div id="searchform"> <input type="text" name="q" class="text"/><br /><br /> <input type="submit" value="Search" class="button" /> </div> } </div> <div id="searchresults"> </div>
هذه الصفحة يوجد بها مربع نص وزر إدخال يقوم ومهمته البحث عن مواطن عن طريق رقمه الوطني..قم بإنشاء صفحة Html أخرى بنفس الطريقة السابقة وقم بتسميتها NotFound وقم بلصق الشفرة التالية داخلها:
@{ ViewBag.Title = "NotFound"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>NotFound</h2> <h1>Sorry There is somthing <i>ERROR</i></h1>
وتلك صفحة تعنى بإظهار أي خطأ يحدث في حال قام المستخدم بإدخال رقم وطني خاطئ لمواطن ما.
قم بالضغط بالزر الأيمن للمؤشر (ماوس) فوق ملف الـ Person الموجود داخل الملف Views وقم بإختيار Add وإختيار View كما موضح بالصورة التالية وقم بكتابة اسم الصفحة HasRecived وقم بالضغط على OK.
ملاحظة: هذه طريقة أخرى أيضا لإنشاء صفحات الـ Html إذا كنت لا تريد إنشائها بالطريقة الأولى..الآن لقد وضحت لديك طريقة إنشاء صفحات الـ Html في المرات القادمة لن أشير لكيفية طريقة إنشائها..فقط سأقول لك أنشئ صفحة Html وأعطها الإسم (…).
قم بنسخ الشفرة التالية داخل ملف الـ HasRecived.cshtml:
@{ ViewBag.Title = "HasRecived"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>HasRecived</h2> <hr /> <h1><i>Sorry</i> This person has <i>Recived</i> his passport!</h1>
وهذه الصفحة تُعني في حال تم إدخال رقم وطني لموطن وقد قام بإستلام جواز مسبقا ستقوم بتوضيح أن هذا المواطن قد استلم جوازه وفي التاريخ الفلاني.
قم بالضغط على ملف Shared بالزر الأيمن للمؤشر (ماوس) وقم بإختيار Add à View وقم بتسميتها _PersonShearch وقم بالضغط على المربع Create as a partial view وقم بالضغط على Ok
وقم بلصق الشفرة التالية بداخلها:
@model MvcExplain.Models.Person @{ ViewBag.Title = "Result"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div class="searchresults"> <ul> <li>@Model.Name</li> <li>@Model.NID</li> <li>@Model.Recived</li> | <li> @Html.ActionLink("please Perss here","PersonRecivedHisPassport",new {passID=Model.PersonID}) </li> <li>@Html.RouteLink("Go", "PassRecive", new { passID = Model.PersonID }) | @Model.Recived</li> </ul> </div>
وهذه الصفحة تُعنى بنتيجة البحث عن مواطن لم يستلم جوازه..وهي من النوع Partial أي هي ليست صفحة Html متكاملة بمعني ستظهر كجزء من صفحة أخرى…وهي شبيهة بصفحات الـ User Control في الـ ASP.NET Web Form.
الصفحة الأخيرة والتنفيذية والتي بها خاصية تسليم المواطن لجوازه والتي اسمها PersonRecivedHisPassport.chtml ..قم بإنشائها وقم بلصق الشفرة التالية عليها:
@model MvcExplain.Models.Person @{ ViewBag.Title = "PersonRecivedHisPassport"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>PersonRecivedHisPassport</h2> <hr /> <div id="recivedform"> @using (Html.BeginForm()) { <p> <input type="submit" value="Recive" /> | @Html.ActionLink("Back to Search Page", "Index") </p> } </div> <hr />
هنيئا لك…الآن لقد أنتهيت من تنفيذ 80% من التطبيق..لدينا خطوة أخيرة وهي تثبيت أداة تُسمى Glimpse وهي أداة جميلة ومهمة جدا دائما ما نستخدمها في تطبيقاتنا وسنستفيد من تلك الأداة في رؤية عناوين الوصول الخاصة بنا التي ستسخدمها الصفحات من خلال المتصفح ومنها سنتأكد هل ستقوم الـ MVC Framework بإختيار روابط الوصول من ملف الـ RouteConfig.cs بصورة صحيحة كما قمنا بتهيئتها.
قم بالضغط بالزر الأيمن للمؤشر (ماوس) وقم بإختيار Manage Nuget Packages ستفتح معك شاشة..قم بإدخال كلمة Glimpse على يمين الشاشة في مربع البحث واضغط Enter ستجد مكتبة Glimpse.mvc4 قم بتثبيت تلك المكتبة… بعد الإنتهاء من تثبيتها قم تشغيل التطبيق لتعمل عليه الآن.
بعد أن تقوم بتشغيل التطبيق قم بكتابة السطر التالي على رابط التطبيق..لتقوم بإعطاء مكتبة Glimpse خاصية العمل مع تطبيق…(عليك بتوقيف عمل تلك المكتبة عندما تقرر نشر تطبيقك على موقع الإستضافة الذي ستستخدمه Web server)
http://localhost:58419/glimpse.axd
بعد كتابة هذا السطر ستفتح معك صفحة تستطيع من خلالها بدء وتوقيف عمل تلك المكتبة. قم بالضغط على Turn Glimpse on كما موضح بالصورة.
والآن بإمكانك كتابة الرابط التالي على رابط الوصول في متصفحة لفتح الصفحة الرئيسية للتطبيق:
http://localhost:58419/Person/Index
لا تنسى سيكون عنوان المنفذ مختلفا..لأنني استخدم الـ IIS Express والذي يقوم بتغيير عنوان المنفذ في كل مرة او في كل تطبيق مختلف.
سيكون التطبيق على تلك الشاكلة كما مبين في تلك الصورة
..قم بإدخال الرقم 123-87847-888 في مربع النص وقم بالضغط على Search ستظهر معك تفاصيل المواطن الذي اسمه Mohammed قم بالضغط على please press here أو بالضغط على Go ستنقلك لصفحة تنفيذ استعلام تسليم الجواز..بالمناسبة الرابطين الموضحين هنا هما يؤديان نفس المهمة فقط قمت بكتابتهما مرتين لأوضح لك الـ Html Helper الخاصة بعناوين الوصول ففي الأول استخدمت Html.RouteLink والذي يقرأ اسم العنوان من ملف الـ RouteConfig.cs والثاني استخدمت فيه Html.ActionLink والذي يقرأ من فئة التحكم الـ HomeController.
لو لاحظت أيضا في الصورة السابقة شكل مكتبة Glimpse وبتمريرك المؤشر (ماوس) على تبويب الـ Views سيظهر لك الوقت الذي أخذته الصفحة في عملية التنفيذ وستظهر لك عنوان الوصول الذي قام الـ MVC Framework بإختياره.
الآن بإمكانك تجريب التطبيق بالبحث عن المواطنين وتسليمهم الجوازات.
ملاحظة لا تنسى لقد تبقى لدينا مهمتين مهة إدارة قاعدة البيانات وعمل إختبار Unit Tests للشفرات..!!
الجزء الثاني – إدارة التطبيق
قم بإنشاء فئة تحكم Controller جديدة وقم بإعطاءها الإسم AdminController وقم بلصق الشفرة تلك بداخلها:
public class AdminController : Controller { private IPerson _repository; public AdminController(IPerson repo) { _repository = repo; } // // GET: /Admin/ public ViewResult Index() { return View(_repository.GetAllPersons.Where(p => p.Recived == false)); } [HttpGet] public ViewResult EditPersonInfo(int personId) { Person person = _repository.GetPersonByID(personId); return View(person); } [HttpPost] public ActionResult EditPersonInfo(Person person) { if (ModelState.IsValid) { _repository.SavePassportInfo(person); TempData["savemessage"] = string.Format("{0} has been saved ", person.Name); return RedirectToAction("Index", "Person"); } else { return View(person); } } public ViewResult CreatePersonInfo() { return View("EditPersonInfo", new Person()); } }
لا تنسى استدعاء فضاء الاسماء التالي:
using MvcExplain.Models; using MvcExplain.Repo;
في هذه الفئة لدينا ثلاثة أساليب الاولى لعرض جميع المواطنين الذين لم يستلموا جوازاتهم والثانية لتعديل بيانات مواطن موجود لدينا..والثالثة لحفظ بيانات مواطن جديد.
لاحظ في الإسلوب الأول Index لقد قمت بإستخدام الجملة التنفيذية داخل الإسلوب نفسه وهذه طريقة خاطئة لانه من المفترض علىّ ان انقل هذا التنفيد Logic داخل الواجهة IPerson واقوم بعمل التنفيذ داخل ملف الـ PersonRepository.cs والذي يرث من الـ IPerson وأقوم بإستدعاء داخل فئة التحكم AdminController مثيل الواحهة _repository بإمكانك عمل تلك العملية للتأكيد على قدرة إستيعابك لما قمنها بعمله على هذا التطبيق. وتلك الجملة بسيطة تستخدم الـ Restriction operator وهي جملة where في تنقية LinQ وهي شبيهة بجملة where في قواعد البيانات SQL وتقوم بعمل تنقيب في مجموعة بيانات محتملة وتقوم بإرجاع قيمة مطابقة للشرط الذي تأخذه في المعامل الخاص بها parameter.
قم بإنشاء صفحة Html للإسلوب Index وقم بلصق الشفرة التالية داخله:
@model IEnumerable<MvcExplain.Models.Person> @{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>Admin Page</h2> @Html.RouteLink("Go To Add New Info","CreateNewPersonInfo") <div id="persons"> <table class="Grid"> <tr> <th>Person</th> <th>NID</th> <th>Status</th> </tr> @foreach (var p in Model) { <tr> <td>@Html.RouteLink(p.Name, "EditExcitePersonInfo", new { personId = p.PersonID })</td> <td>@p.NID</td> <td>@p.Recived</td> </tr> } </table> </div>
وصفحة أخري لـ EditPersonInfo وقم بلصق الشفرة التالية داخلها:
@model MvcExplain.Models.Person @{ ViewBag.Title = "EditPersonInfo"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>EditPersonInfo</h2> @using (Html.BeginForm("EditPersonInfo", "Admin")) { @Html.EditorForModel(); <input type="submit" value="Save" /> @Html.ActionLink("Cancel","Index") }
الإسلوب CreatePersonInfo لن يحتاج لصفحة Html لأنه سيقوم بإستدعاء صفحة الـ EditPersonInfo وإذا رأيت في في الإسلوب بأننا قمنا بإسناد كائن الـ Person لتقوم الصفحة بإرجاعه من خلال صفحة التعديل كالتالي:
return View("EditPersonInfo", new Person());
الـ MVC Framewrok تقوم بإستخدام الـ Command pattern لإرجاع صفحات الـ Hmlt.
أخيرا قم بفتح ملف الـ Global.asax وقم بلصق الشفرة التالية داخل الإسلوب Application_Start:
ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new RazorViewEngine());
تلك الشفرة ستحسن من عمل التطبيق ولأن منصة الـ MVC لديها محركين للعمل على صفحات الـ HTML وهما الـ Razor والـ Web Form وعند تشغيلك للتطبيق ستقوم المنصة بالبحث بالمحركين عن صفحة الـ Index وهذه ستأخذ وقت إضافي..لكن بكتبابتنا لتلك الشفرة السابقة قمنا بإخبار منصة الـ MVC بإن تقوم بعمل تنظيف لجميع محركات إنشاء الـ HTML وثانيا أخبرناها بإنشاء المحرك Razor فقط ليعمل مع التطبيق..وإذا كنت تذكر في بدابة هذا التطبيق عندما أنشأناه لقد إخترنا الـ View Engine الخاص بتطبيقنا هو الـ Razor.
الآن بإمكانك تشغيل التطبيق وكتابة الرابط التالي:
http://localhost:58419/admin/index
لا تنسى سيكون المنفذ مختلفا..هذه الصفحة ستعرض جميع بيانات المواطنين الذي لم يستلموا جوازاتهم..وبإمكانك من تلك الصفحة إضافة بيانات مواطن جديد…والتعديل على بيانات موجودة مسبقا.
الجزء الثالث – عمل أختبار للشفرات Unit tests
كثير من المبرمجين لا يقومون بالقيام بإختبار شفراتهم؛ وهذا خطأ كبير ولأن اختبار الشفرات مهم جدا وكما متعارف عليه في اختبار شفرات بيئة الـ MVC Framework يُقال لك أقلّاها عليك اختبار روابط عناوين الوصول لتطبيقك لأنه إن لا كانت لا تعمل لن يعمل تطبيق أبدا.
قم بالضغط على الـ Solution وليس اسم التطبيق وقم بإنشاء مشروع جديد كما موضح بالصورة التالية:
قم بإختيار Test من على يسار الشاشة وإختيار Unit Test Project كما موضح بالصورة وقم بتسمية المشروع PersonData.UnitTests واضغط Ok.
قم بالضغط على خيار References من داخل المشروع الذي أنشأته مؤخرا وقم بالضغط على Add References من يسار الشاشة إختار Solution وستجد مشروع الـ MvcExplain قم بإختياره ثم قم بضغط OK.
في تلك الخطوة قمنا بربط مشروع إختبار الشفرات بمشروعنا الفعلي وذلك حت نقوم بعمل إختبار لأجزاءه بالطريقة التي نريدها
اضغط على TOOLS في اعلى الشريط المساعد الخاص بالفيجوال واختار الـ Library Package Manager والتي ستظهر لك خيارات عدة إضغط على اول خيار Package Manager Console واكتب السطر التالي:
PM> Install-Package Moq -version 4.0.10827
هنا قمنا بتنزيل مكتبة الـ Moq وهي مكتبة تتيح لنا إنشاء بيانات وهمية في حال أردنا إختبار شفراتنا بدلا من اغختبارها ببيانات حقيقية من داخل قاعدة البيانات وذلك لأنها ستزيد من بطء النظام لذلك نقوم بإستخدام تلك المكتبة.
في شفرات الإختبار تلك لقد قمت بعمل إختبار لفئة التحكم PersonController وفئة الـ RouteConfige.cs فقط ولم أقم بعمل إختبار لجميع الأكواد.
عند إنشائك لمشروع اختبار البرمجيات سيقوم الفيجوال ستديو بإنشاء فئة Class بإسم UnitTest1 سنقوم بكتابة شفرتنا عليها…لكن لا تفعل ذلك في برمجياتك الرسمية والكبيرة…فمثلا إذا أردت إختبار فئة التحكم AdminController قم بإنشاء فئة اسمها AdminControllerUnitTest حتى يكون عملك محترفا ومقبولا..ولا تنسى بأنه قد تترك العمل في الشركة التي تعمل بها وبالتالي هذا يعني تسهيلك للمطورين والمبرمجين الذي سيأتون من بعدك سيكون عليهم سهلا معرفة الأكواد التي كتبتها وهذا ما يعرف بأخلاقيات العمل (قم بالإطلاع على مواضيع هندسة البرمجيات على الإنترنت).
في تلك الفئة UnitTest1 قم بإستدعا فضاء الإسماء التالي:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Linq; using Moq; using System.Collections; using System.Collections.Generic; using System.Web.Mvc; using MvcExplain.Models; using MvcExplain.Repo; using MvcExplain.Controllers; using System.Web.Routing;
وقم بكتابة الشفرة التالية داخل تلك الفئة:
[TestClass] public class UnitTest1 { [TestMethod] public void Can_List_AllPersonInMainPage() { Mock<IPerson> mock = new Mock<IPerson>(); mock.Setup(m => m.GetAllPersons).Returns(new Person[]{ new Person{PersonID=1,Name="Mohammed",NID="23-1234",Recived=false}, new Person{PersonID=2,Name="Ahmed",NID="23-1235",Recived=true}, new Person{PersonID=3,Name="Hussien",NID="23-1236",Recived=true}, new Person{PersonID=4,Name="babiker",NID="23-1237",Recived=true}, new Person{PersonID=5,Name="Ali",NID="23-1238",Recived=false}, new Person{PersonID=6,Name="Khalid",NID="23-1239",Recived=false} }.AsQueryable()); PersonController target = new PersonController(mock.Object); //ViewResult result = target.Index() as ViewResult; IEnumerable<Person> result = (IEnumerable<Person>)target.Index().Model; Person[] personData = result.ToArray(); // Assert Assert.IsNotNull(result); Assert.IsTrue(personData.Length == 6); Assert.AreEqual(personData[0].Name, "Mohammed"); } [TestMethod] public void Can_SearchForSpesificPerson_ByHidsNID() { // Arrange Mock<IPerson> mock = new Mock<IPerson>(); // Arrange - Initil obj Person n = new Person { NID = "23-1234" }; // Action PersonController target = new PersonController(mock.Object); // Action ActionResult result = target.SerachForPerson("23-1234") as ViewResult; Assert.IsNotNull(result); Assert.AreEqual("23-1234",n.NID); } [TestMethod] public void WhenYouWantToRecivePassPort_Return_HasRecived_View() { Mock<IPerson> mock = new Mock<IPerson>(); PersonController target = new PersonController(mock.Object); Person person = new Person { Recived = false }; ViewResult result = target.PersonRecivedHisPassport(person.PersonID); Assert.AreEqual("HasRecived", result.ViewName); } [TestMethod] public void PersonHasBeenRecived_ShouldReturnTrue_WhenNotRecive() { Mock<IPerson> mock = new Mock<IPerson>(); // Arrange PersonController target = new PersonController(mock.Object); Person rec = new Person { Recived = false }; //int p = 1; ActionResult result = target.PersonRecivedHisPassport(rec.PersonID,null); mock.Verify(m => m.RecivedHisPassport(rec.PersonID)); Assert.IsInstanceOfType(result,typeof(ViewResult)); } [TestMethod] public void RouteToHomePage() { // Arrange var mockContext = new Mock<HttpContextBase>(); mockContext.Setup(c => c.Request.AppRelativeCurrentExecutionFilePath).Returns("~/"); // Arrange var routes = new RouteCollection(); MvcExplain.RouteConfig.RegisterRoutes(routes); RouteData routeData = routes.GetRouteData(mockContext.Object); // Assert Assert.IsNotNull(routeData); Assert.AreEqual("Person", routeData.Values["controller"]); Assert.AreEqual("Index", routeData.Values["action"]); Assert.AreEqual(UrlParameter.Optional, routeData.Values["id"]); } [TestMethod] public void RouteToPassRecivePage() { var mockContext = new Mock<HttpContextBase>(); mockContext.Setup(c => c.Request.AppRelativeCurrentExecutionFilePath).Returns("~/person/recivehispassport"); var routes = new RouteCollection(); MvcExplain.RouteConfig.RegisterRoutes(routes); RouteData routeData = routes.GetRouteData(mockContext.Object); Assert.IsNotNull(routeData); Assert.AreEqual("Person", routeData.Values["controller"]); Assert.AreEqual("PersonRecivedHisPassport", routeData.Values["action"]); } }
لإختبار تلك الشفرة ورؤيتها تعمل بصورة صحيحة قم بالضغط على Testعلى شريط الأدوات في فيجوال ستديو وقم بإختيار Windows ومنها إختار Test Explorer ستفتح معك شاشة على يسار الفيجوال ستديو…قم بالضغط على Run All سيقوم الفيجوال ستديو بعمل بناء للتطبيق Build وسيقوم بالتأكد من عمل الأكواد التي قمت بكتابتها وستعطيك إشارة خضراء إذا كانت الأكواد تعمل بصورة صحيحة … وإشارة حمراء إن كان هناك خطأ.
وكما موضح بالصورة التالية أن جميع الأكواد التي إختبرناها تعمل بشكل جيّد.
في هذه الفئة قمنا بعمل اختبار للصفحة الرئيسية وصفحة تسليم الجوازات…أيضا قمنا بالتأكد من من صفحة الـ Index تقوم بإرجاع جميع البيانات الموجودة بقاعدة البيانات..أيضا اختبرنا اسلوب نتيجة البحث…وأيضا في حالة كنا نريد تسليم مواطن لجواز اختبرنا ان ذلك الاسلوب سيقوم بإرجاع الصفحة المخصصة لذلك واخيرا اختبرنا تنفيذ الـ Logic الخاص بتسليم الجواز للمواطن من تغيير False الي True في حقل قاعدة البيانات.
توجد في فضاء الاسماء التالي العديد من الخواص على سبيل المثال منها TestClass والتي تبين ان الفئة تلك تحت الإختبار وسيكون بها العديد من الأساليب التي سيتم إختبارها..وأي اسلوب سيتم أختباره لابد أن يبلور بالخاصية TestMethod,
using Microsoft.VisualStudio.TestTools.UnitTesting;
تحياتي.
تحديث
في حال أردنا معرفة ان المواطن قد قام بإستلام الجواز الخاص به من خلال صفحة البحث سنقوم بإضافة بعض الجمل المنطقية لفئة التحكم PersonController حيث سنقوم بإضافة إسلوب جديد لتلك الفئة وسنقوم بإعطاءه الإسم Details وستكون الشفرة كالتالي:
public ActionResult Details(string Id)
{
var p = repository.GetPersonByHisNID(Id);
if (p != null)
{
return View(p);
}
return View(“NotFound”);
}
سنقوم بإضافة صفحة عرض HTLM للإسلوب السابق وستكون الشفرة الخاصة بالعرض كالتالي:
@model MvcExplain.Models.Person
@{
ViewBag.Title = “Details”;
}
Details – Reciving Details
Name: @Model.Name
National Identity: @Model.NID
Recivied Date: @Model.RecivedDate
الآن أنشأنا صفحة للعرض توضح تفاصيل المواطن الذي قام بإستلام جوازه..حيث قمنا بإضافة الخاصية ReciveDate والتي توضح لنا التاريخ الذي استلم في المواطن الجواز.
الخطوة الأخيرة سنقوم بعمل تعديل بسيط في فئة التحكم PersonController
أولا: قم بتحديث الإسلوب SearchForPerson ليصبح بالشكل التالي:
public ActionResult SerachForPerson(string q)
{
var persons = repository.GetPersonByHisNID(q);
if (persons != null && persons.Recived == true)
{
return View(“Details”, persons);
}
else if (persons != null && persons.Recived == false)
{
return PartialView(“_PersonSearch”, persons);
}
else
{
return View(“NotFound”);
}
}
فلو لاحظت هنا قمنا بإضافة الجملة المنطقية والتي توضح إذا كان المواطن قد استلم جوازه قم بالذهاب لصفحة الـ Details التي أنشأناها أولا وفي حال لم يستلم قم بالذهاب لصفحة نتيجة البحث والتي معرّفة مسبقا والتي ستعرض لنا رابط للذهاب لصفحة تسليم الجواز. والجملة المنطقية الأخيرة ستعرض أي خطأ آخر…
ثانيا: قم بتحديث الإسلوب PersonRecivedHisPassport ليصبح كالتالي:
[HttpGet]
public ViewResult PersonRecivedHisPassport(int passID)
{
Person person = repository.GetPersonByID(passID);
if (person == null)
{
return View(“NotFound”);
}
else
{
return View();
}
}
في هذا الإسلوب قمنا بحذف عرض صفحة HasRecive لأننا لن نحتاجها هنا لأننا قمنا بالتأكد من استلام الجواز من عدمه من بداية البحث. وبالتالي لن نحتاج لهذه الصفحة وبإمكاننا حذفها.
السلام عليكم
شكرا على المجهود القيم وجعله الله في ميزان حسناتك
ارجو النظر في موضوع وضع هذه الدروس في شكل مرئي فيديو
الف شكر