ASP.NET Razor: حل بعض مشكلات العمارة لنموذج العرض

الصورة


مقدمة


مرحبا زملائي!
اليوم أريد أن أشارككم تجربتي في تطوير بنية نموذج العرض كجزء من تطوير تطبيقات الويب 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) { //     transportAddDto... } 

هنا نرى مشكلتين على الأقل:


  1. تكون سمات المعرف والاسم مسبوقة بـ AddDTO ، وإذا حاولت طريقة إضافة النقل في وحدة التحكم باستخدام مبدأ ربط النموذج ربط البيانات الواردة من العميل إلى TransportAddDTO ، فسيكون الكائن الموجود داخله بالكامل عبارة عن أصفار (القيم الافتراضية) ، أي سيكون مجرد نسخة فارغة جديدة. من المنطقي - الأسماء الموثقة الموثقة لرقم النموذج ، وليس AddDTO_Number .
  2. اختفت جميع السمات الوصفية ، أي 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 الخاص بنا ، وستكتمل المهمة (لا تفكر في المعالجة على الواجهة الخلفية). هذا أقترح القيام به لوحدي.


إذا كان لدى القارئ المحترم أفكار حول كيفية تنفيذ ما هو مطلوب بطرق أكثر مثالية ، فسوف أستمع بكل سرور في التعليقات.


التحيات
بيتر أوسيتروف

Source: https://habr.com/ru/post/ar416315/


All Articles