
مقدمة
مرحبا زملائي!
اليوم أريد أن أشارككم تجربتي في تطوير بنية نموذج العرض كجزء من تطوير تطبيقات الويب ASP.NET باستخدام محرك قالب Razor .
التطبيقات التقنية الموضحة في هذه المقالة مناسبة لجميع الإصدارات الحالية من ASP. NET ( MVC 5 ، Core ، إلخ). المقالة نفسها مخصصة للقراء الذين ، على الأقل ، لديهم بالفعل خبرة في العمل مع هذا المكدس. تجدر الإشارة أيضًا إلى أنه في إطار هذا ، لا نأخذ في الاعتبار فائدة نموذج العرض وتطبيقه الافتراضي (من المفترض أن القارئ على دراية بهذه الأشياء بالفعل) ، فإننا نناقش التنفيذ مباشرة.
التحدي
من أجل استيعاب مناسب وعقلاني للمادة ، أقترح النظر على الفور في المهمة التي ستقودنا بطبيعة الحال إلى المشاكل المحتملة والحلول المثلى.
هذه هي مشكلة الإضافة العادية ، على سبيل المثال ، سيارة جديدة إلى كتالوج معين من المركبات . من أجل عدم تعقيد المهمة المجردة ، سيتم تفويت تفاصيل الجوانب المتبقية عمدا. يبدو أن المهمة الأولية ، مع ذلك ، هي محاولة القيام بكل شيء مع التركيز على زيادة توسيع نطاق النظام (على وجه الخصوص ، توسيع النماذج بالنسبة لعدد الخصائص والمكونات المحددة الأخرى) بحيث يكون من المريح العمل لاحقًا قدر الإمكان.
التنفيذ
دع النموذج يبدو كما يلي (من أجل البساطة ، لا يتم إعطاء أشياء مثل الخصائص الملاحية وما إلى ذلك):
class Transport { public int Id { get; set; } public int TransportTypeId { get; set; } public string Number { get; set; } }
بالطبع ، TransportTypeId هو مفتاح خارجي لكائن من نوع TransportType :
class TransportType { public int Id { get; set; } public string Name { get; set; } }
للتواصل بين الواجهة الأمامية والخلفية ، سنستخدم قالب كائن نقل البيانات . وفقًا لذلك ، سيبدو DTO لإضافة سيارة مثل هذا:
class TransportAddDTO { [Required] public int TransportTypeId { get; set; } [Required] [MaxLength(10)] public string Number { get; set; } }
* يستخدم سمات التحقق القياسية من System.ComponentModel.DataAnnotations
.
حان الوقت لمعرفة ما سيكون نموذج العرض لصفحة إضافة السيارة. من دواعي سرور بعض المطورين أن يعلنوا أن TransportAddDTO نفسه سيكون من هذا القبيل ، ومع ذلك ، هذا خطأ جوهري ، لأنه لا يوجد شيء "مكتظ" في هذه الفئة باستثناء مباشرة لمعلومات الواجهة الخلفية اللازمة لإضافة عنصر جديد (حسب التعريف). بالإضافة إلى ذلك ، قد تكون هناك حاجة إلى بيانات أخرى على صفحة الإضافة: على سبيل المثال ، دليل لأنواع المركبات (يتم على أساسها التعبير عن TransportTypeId لاحقًا). في هذا الصدد ، يقترح نموذج العرض التالي نفسه:
class TransportAddViewModel { public IEnumerable<TransportTypeDTO> TransportTypes { get; set; } }
حيث يكون TransportTypeDTO في هذه الحالة تخطيطًا مباشرًا لـ TransportType (وهذا أبعد ما يكون عن الحالة دائمًا - سواء في اتجاه الاقتطاع أو في اتجاه التوسع):
class TransportTypeDTO { public int Id { get; set; } public string Name { get; set; } }
في هذه المرحلة ، يطرح السؤال المعقول: في Razor سيكون من الممكن نقل نموذج واحد فقط (والحمد لله) ، فكيف يمكن استخدام TransportAddDTO لإنشاء كود HTML داخل هذه الصفحة؟
سهل جدا! يكفي إضافة DTO هذا ، على وجه الخصوص ، إلى نموذج العرض ، شيء من هذا القبيل:
class TransportAddViewModel { public TransportAddDTO AddDTO { get; set; } public IEnumerable<TransportTypeDTO> TransportTypes { get; set; } }
الآن تبدأ المشاكل الأولى. دعنا نحاول إضافة TextBox قياسي لـ "رقم المركبة" إلى الصفحة في ملف .cshtml الخاص بنا (فليكن TransportAddView.cshtml):
@model TransportAddViewModel @Html.TextBoxFor(m => m.AddDTO.Number)
سيؤدي هذا إلى تقديم كود HTML مثل هذا:
<input id="AddDTO_Number" name="AddDTO.Number" />
تخيل أن جزء وحدة التحكم مع طريقة إضافة المركبات يبدو مثل هذا ( الرمز وفقًا لـ MVC 5 ، بالنسبة لـ Core سيكون مختلفًا قليلاً ، لكن الجوهر هو نفسه ):
[Route("add"), HttpPost] public ActionResult Add(TransportAddDTO transportAddDto) {
هنا نرى مشكلتين على الأقل:
- تكون سمات المعرف والاسم مسبوقة بـ AddDTO ، وإذا حاولت طريقة إضافة النقل في وحدة التحكم باستخدام مبدأ ربط النموذج ربط البيانات الواردة من العميل إلى TransportAddDTO ، فسيكون الكائن الموجود داخله بالكامل عبارة عن أصفار (القيم الافتراضية) ، أي سيكون مجرد نسخة فارغة جديدة. من المنطقي - الأسماء الموثقة الموثقة لرقم النموذج ، وليس AddDTO_Number .
- اختفت جميع السمات الوصفية ، أي data-val-required وجميع البيانات الأخرى التي وصفناها بعناية شديدة في AddDTO كسمات للتحقق. بالنسبة لأولئك الذين يستخدمون القوة الكاملة لـ Razor ، يعد هذا أمرًا بالغ الأهمية ، حيث يعد هذا خسارة كبيرة في المعلومات للواجهة الأمامية.
نحن محظوظون ولديهم قرارات مماثلة.
هذه الأشياء "تعمل" عند استخدام ، على سبيل المثال ، غلاف لـ Kendo UI (أي @Html.Kendo().TextBoxFor()
، وما إلى ذلك).
لنبدأ بالمشكلة الثانية: السبب هنا هو أنه في نموذج العرض ، كان مثيل TransportAddDTO الذي تم نقله فارغًا . وتنفيذ آليات التقديم بحيث يتم قراءة السمات في هذه الحالة على الأقل ليس بالكامل. الحل ، على التوالي ، واضح - أولاً في نموذج العرض لتهيئة الخاصية TransportAddDTO بمثيل من الفئة باستخدام المُنشئ الافتراضي. من الأفضل القيام بذلك في خدمة تقوم بإرجاع نموذج عرض تمت تهيئته ، ومع ذلك ، كجزء من المثال ، ستقوم بنفس الشيء:
class TransportAddViewModel { public TransportAddDTO AddDTO { get; set; } = new TransportAddDTO(); public IEnumerable<TransportTypeDTO> TransportTypes { get; set; } }
بعد هذه التغييرات ، ستكون النتيجة مشابهة لما يلي:
<input data-val="true" id="AddDTO_Number" name="AddDTO.Number" data-val-required="The Number field is required." data-val-length="The field Number must be a string with a maximum length of 10." data-val-length-max="10" />
أفضل بالفعل! يبقى التعامل مع المشكلة الأولى - بالمناسبة ، كل شيء معقد إلى حد ما.
لفهمها ، تحتاج أولاً إلى فهم ما هو Razor (يشير ضمنيًا إلى WebViewPage ، وهو مثيل يوجد بداخله .cshtml متاح على هذا النحو) هو خاصية Html التي نشير إليها بهدف استدعاء TextBoxFor
.
بالنظر إليها ، يمكنك أن تفهم على الفور أنها من نوع HtmlHelper<T>
، في حالتنا ، HtmlHelper<TransportAddViewModel>
. ينشأ حل محتمل للمشكلة - لإنشاء HtmlHelper الخاص بك في الداخل ، وتمرير TransportAddDTO إليه كمدخل. نجد أصغر مُنشئ ممكن لمثيل من هذه الفئة:
HtmlHelper<T>.HtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer);
يمكننا تمرير ViewContext مباشرة من مثيل this.ViewContext
خلال this.ViewContext
. this.ViewContext
. الآن دعونا نكتشف مكان الحصول على مثيل لفئة تطبق واجهة IViewDataContainer. على سبيل المثال ، قم بإنشاء التنفيذ الخاص بك:
public class ViewDataContainer<T> : IViewDataContainer where T : class { public ViewDataDictionary ViewData { get; set; } public ViewDataContainer(object model) { ViewData = new ViewDataDictionary(model); } }
كما ترى ، نواجه الآن اعتمادًا على بعض الكائنات التي تم تمريرها إلى المُنشئ بغرض تهيئة ViewDataDictionary ، حيث أن كل شيء بسيط هنا - هذا مثال من TransportAddDTO من نموذج العرض. بمعنى ، يمكنك الحصول على المثال العزيز مثل هذا:
var vdc = new ViewDataContainer<TransportAddDTO>(Model.AddDTO);
وفقًا لذلك ، لا توجد مشاكل في إنشاء HtmlHelper جديد إما:
var Helper = new HtmlHelper<T>(this.ViewContext, vdc);
الآن يمكنك استخدام ما يلي:
@model TransportAddViewModel @{ var vdc = new ViewDataContainer<TransportAddDTO>(Model.AddDTO); var Helper = new HtmlHelper<T>(this.ViewContext, vdc); } @Helper.TextBoxFor(m => m.Number)
سيؤدي هذا إلى تقديم كود HTML مثل هذا:
<input data-val="true" id="Number" name="Number" data-val-required="The Number field is required." data-val-length="The field Number must be a string with a maximum length of 10." data-val-length-max="10" />
كما ترى ، الآن لا توجد مشاكل في العنصر المعروض ، وهو جاهز للاستخدام الكامل. يبقى فقط "لتمشيط" الرمز بحيث يبدو أقل حجمًا. على سبيل المثال ، نقوم بتوسيع ViewDataContainer على النحو التالي:
public class ViewDataContainer<T> : IViewDataContainer where T : class { public ViewDataDictionary ViewData { get; set; } public ViewDataContainer(object model) { ViewData = new ViewDataDictionary(model); } public HtmlHelper<T> GetHtmlHelper(ViewContext context) { return new HtmlHelper<T>(context, this); } }
ثم من Razor يمكنك العمل على النحو التالي:
@model TransportAddViewModel @{ var Helper = new ViewDataContainer<TransportAddDTO>(Model.AddDTO).GetHtmlHelper(ViewContext); } @Helper.TextBoxFor(m => m.Number)
بالإضافة إلى ذلك ، لا يزعج أحد لتوسيع التنفيذ القياسي ل WebViewPage بحيث يحتوي على الخاصية المطلوبة (مع محدد لمثيل من فئة DTO).
الخلاصة
أدى هذا إلى حل المشكلة ، كما حصل على بنية نموذج العرض للعمل مع Razor ، والتي يمكن أن تحتوي على جميع العناصر الضرورية.
وتجدر الإشارة إلى أن ViewDataContainer الناتج أصبح عالميًا ومناسبًا للاستخدام.
يبقى إضافة بعض الأزرار إلى ملف .cshtml الخاص بنا ، وستكتمل المهمة (لا تفكر في المعالجة على الواجهة الخلفية). هذا أقترح القيام به لوحدي.
إذا كان لدى القارئ المحترم أفكار حول كيفية تنفيذ ما هو مطلوب بطرق أكثر مثالية ، فسوف أستمع بكل سرور في التعليقات.
التحيات
بيتر أوسيتروف