أحد أسباب نجاح ASP.NET هو أنه يقلل من حاجز دخول مطوري الويب. ليس من الضروري أن تكون حاصلاً على درجة الدكتوراه في علوم الكمبيوتر لكتابة كود ASP.NET. العديد من مطوري ASP.NET الذين التقيت بهم في العمل تعلموا أنفسهم بأنفسهم وكانوا يكتبون جداول بيانات Microsoft® Excel® قبل أن يكتبوا C# أو Visual Basic®. والآن، يقومون بكتابة تطبيقات الويب، وبشكل عام، فإنهم يستحقون الثناء على العمل الذي يقومون به.
ولكن مع القوة تأتي المسؤولية، وحتى مطوري ASP.NET ذوي الخبرة يمكن أن يرتكبوا الأخطاء. خلال سنوات عديدة من الاستشارة في مشاريع ASP.NET، اكتشفت أن بعض الأخطاء من المرجح أن تؤدي بشكل خاص إلى عيوب متكررة. يمكن أن تؤثر بعض هذه الأخطاء على الأداء. أخطاء أخرى يمكن أن تمنع قابلية التوسع. يمكن أن تكلف بعض الأخطاء أيضًا فرق التطوير وقتًا ثمينًا في تعقب الأخطاء والسلوك غير المتوقع.
فيما يلي 10 مخاطر يمكن أن تسبب مشكلات أثناء إصدار تطبيقات إنتاج ASP.NET وطرق تجنبها. جميع الأمثلة تأتي من تجربتي الخاصة في بناء تطبيقات ويب حقيقية في شركات حقيقية، وفي بعض الحالات أقدم السياق من خلال وصف بعض المشكلات التي واجهها فريق تطوير ASP.NET أثناء عملية التطوير.
التحكم في التحميل والتخزين المؤقت للإخراج يوجد عدد قليل جدًا من تطبيقات ASP.NET التي لا تستخدم عناصر تحكم المستخدم. قبل الصفحات الرئيسية، استخدم المطورون عناصر تحكم المستخدم لاستخراج المحتوى الشائع، مثل الرؤوس والتذييلات. حتى في ASP.NET 2.0، توفر عناصر تحكم المستخدم طريقة فعالة لتغليف المحتوى والسلوك وتقسيم الصفحة إلى مناطق يمكن التحكم في قابليتها للتخزين المؤقت بشكل مستقل عن الصفحة ككل (عملية تسمى المقاطع، وهي شكل خاص من أشكال التخزين المؤقت للمخرجات). ).
يمكن تحميل عناصر تحكم المستخدم بشكل تصريحي أو قسري. يعتمد التحميل القسري على Page.LoadControl، الذي يقوم بإنشاء عنصر تحكم المستخدم وإرجاع مرجع التحكم. إذا كان عنصر تحكم المستخدم يحتوي على أعضاء من نوع مخصص (على سبيل المثال، ملكية عامة)، فيمكنك إرسال المرجع والوصول إلى العضو المخصص من التعليمات البرمجية الخاصة بك. يقوم عنصر تحكم المستخدم في الشكل 1 بتنفيذ خاصية تسمى BackColor. يقوم التعليمة البرمجية التالية بتحميل عنصر تحكم المستخدم وتعيين قيمة إلى BackColor:
protected void Page_Load(object sender, EventArgs e){// قم بتحميل عنصر تحكم المستخدم وإضافته إلى الصفحة Control control = LoadControl("~/MyUserControl.ascx") ;PlaceHolder1 .Controls.Add(control);// تعيين لون الخلفية الخاص به ((MyUserControl)control).BackColor = Color.Yellow;}
الكود أعلاه بسيط للغاية في الواقع، ولكنه فخ ينتظر المطور الغافل أن يقع فيه. هل يمكنك العثور على الخلل؟
إذا كنت تعتقد أن المشكلة تتعلق بالتخزين المؤقت للمخرجات، فأنت على صواب. كما ترون، يتم تجميع مثال التعليمات البرمجية أعلاه وتشغيله بشكل جيد، ولكن إذا حاولت إضافة العبارة التالية (وهو أمر قانوني تمامًا) إلى MyUserControl.ascx:
<%@ OutputCache Duration="5" VaryByParam="None" %>
ثم في المرة التالية التي تقوم فيها بتشغيل الصفحة، ستشاهد InvalidCastException (يا فرحة!) ورسالة الخطأ التالية:
"لا يمكن إرسال كائن من النوع 'System.Web.UI.PartialCachingControl' لكتابة 'MyUserControl'."
لذلك، يعمل هذا الرمز بشكل جيد بدون توجيه OutputCache، ولكنه يفشل إذا تمت إضافة التوجيه OutputCache. ليس من المفترض أن يتصرف ASP.NET بهذه الطريقة. يجب أن تكون الصفحات (وعناصر التحكم) غير محددة للتخزين المؤقت للإخراج. إذن، ماذا يعني هذا؟
المشكلة هي أنه عند تمكين التخزين المؤقت للمخرجات لعنصر تحكم المستخدم، لم يعد LoadControl يُرجع مرجعًا إلى مثيل التحكم بدلاً من ذلك، بل يُرجع مرجعًا إلى مثيل PartialCachingControl، والذي قد يلتف أو لا يلتف، اعتمادًا على ما إذا كان إخراج عنصر التحكم هو ذاكرة التخزين المؤقت. لذلك، إذا قام مطور باستدعاء LoadControl لتحميل عنصر تحكم المستخدم ديناميكيًا وتحويل مرجع التحكم من أجل الوصول إلى الأساليب والخصائص الخاصة بالتحكم، فيجب عليه الانتباه إلى كيفية القيام بذلك حتى يتم تشغيل التعليمات البرمجية بغض النظر عما إذا كان هناك توجيه OutputCache.
يوضح الشكل 2 الطريقة الصحيحة لتحميل عنصر تحكم المستخدم ديناميكيًا وتحويل مرجع التحكم الذي تم إرجاعه. فيما يلي ملخص لكيفية العمل:
• إذا كان ملف ASCX يفتقد توجيه OutputCache، يقوم LoadControl بإرجاع مرجع MyUserControl. يقوم Page_Load بتحويل المرجع إلى MyUserControl ويقوم بتعيين خاصية BackColor لعنصر التحكم.
• إذا كان الملف ASCX يتضمن توجيه OutputCache ولم يتم تخزين مخرجات عنصر التحكم مؤقتًا، فسيقوم LoadControl بإرجاع مرجع إلى PartialCachingControl الذي تحتوي خاصية CachedControl الخاصة به على مرجع إلى MyUserControl الأساسي. يقوم Page_Load بتحويل PartialCachingControl.CachedControl إلى MyUserControl ويقوم بتعيين خاصية BackColor لعنصر التحكم.
• إذا كان الملف ASCX يتضمن توجيه OutputCache وتم تخزين مخرجات عنصر التحكم مؤقتًا، فسيقوم LoadControl بإرجاع مرجع إلى PartialCachingControl الذي تكون خاصية CachedControl الخاصة به فارغة. لاحظ أن Page_Load لم يعد يستمر. لا يمكن تعيين خاصية BackColor لعنصر التحكم لأن إخراج عنصر التحكم يأتي من ذاكرة التخزين المؤقت للإخراج. بمعنى آخر، لا يوجد MyUserControl لتعيين الخصائص عليه على الإطلاق.
سيتم تشغيل التعليمات البرمجية الموجودة في الشكل 2 بغض النظر عما إذا كان هناك توجيه OutputCache في الملف .ascx. على الرغم من أن الأمر يبدو أكثر تعقيدًا بعض الشيء، إلا أنه سيتجنب الأخطاء المزعجة. البساطة لا تعني دائمًا سهولة الصيانة.
العودة إلى الأعلى جلسات العمل والتخزين المؤقت للمخرجات عند الحديث عن التخزين المؤقت للمخرجات، فإن كلا من ASP.NET 1.1 وASP.NET 2.0 لديهما مشكلة محتملة تؤثر على صفحات ذاكرة التخزين المؤقت للمخرجات في الخوادم التي تعمل بنظام التشغيل Windows Server™ 2003 وIIS 6.0. لقد رأيت شخصيًا هذه المشكلة تحدث مرتين في خادم إنتاج ASP.NET، وفي المرتين تم حلها عن طريق إيقاف تشغيل التخزين المؤقت للإخراج. علمت لاحقًا أن هناك حلًا أفضل من تعطيل التخزين المؤقت للمخرجات. إليك ما بدا عليه الأمر عندما واجهت هذه المشكلة لأول مرة.
ما حدث هو أن أحد مواقع الويب (دعنا نسميها هنا Contoso.com، والذي يدير تطبيقًا عامًا للتجارة الإلكترونية في مجال ويب ASP.NET صغير) اتصل بفريقي يشتكي من تعرضهم لخطأ "الترابط المتقاطع". غالبًا ما يفقد العملاء الذين يستخدمون موقع ويب Contoso.com البيانات التي أدخلوها بشكل مفاجئ ولكنهم يرون بدلاً من ذلك البيانات المتعلقة بمستخدم آخر. بعد قليل من التحليل، وجدنا أن وصف الترابط غير دقيق؛ فالخطأ "عبر الجلسة" أكثر ملاءمة. يبدو أن موقع Contoso.com يقوم بتخزين البيانات في حالة الجلسة، ولسبب ما يتصل المستخدمون أحيانًا وعشوائيًا بجلسات مستخدمين آخرين.
كتب أحد أعضاء فريقي أداة تشخيصية تسجل العناصر الأساسية لكل طلب واستجابة HTTP، بما في ذلك رأس ملف تعريف الارتباط. ثم قام بعد ذلك بتثبيت الأداة على خادم الويب الخاص بشركة Contoso.com وتركها تعمل لبضعة أيام. النتائج واضحة جدا. مرة واحدة تقريبًا من كل 100000 طلب، يقوم ASP.NET بتعيين معرف الجلسة بشكل صحيح لجلسة جديدة تمامًا وإرجاع معرف الجلسة في رأس Set-Cookie. ثم تقوم بإرجاع نفس معرف الجلسة (أي نفس رأس Set-Cookie) على الطلب التالي المجاور مباشرة، على الرغم من أن الطلب كان مرتبطًا بالفعل بجلسة صالحة وتم إرسال معرف الجلسة في ملف تعريف الارتباط بشكل صحيح. في الواقع، يقوم ASP.NET بتبديل المستخدمين بشكل عشوائي من جلسات العمل الخاصة بهم وربطهم بجلسات أخرى.
لقد فوجئنا وشرعنا في معرفة السبب. قمنا أولاً بفحص الكود المصدري لموقع Contoso.com، ومن دواعي ارتياحنا أن المشكلة لم تكن موجودة. بعد ذلك، للتأكد من أن المشكلة لا تتعلق بمضيف التطبيق في مجال الويب، تركنا خادمًا واحدًا فقط قيد التشغيل وأغلقنا جميع الخوادم الأخرى. لا تزال المشكلة قائمة، وهذا ليس مفاجئًا لأن سجلاتنا تظهر أن رؤوس Set-Cookie المطابقة لا تأتي أبدًا من خادمين مختلفين. يقوم ASP.NET بإنشاء معرفات جلسة مكررة عن طريق الخطأ، وهو أمر لا يصدق لأنه يستخدم فئة .NET Framework RNGCryptoServiceProvider لإنشاء هذه المعرفات، وتكون معرفات الجلسة طويلة بما يكفي لضمان عدم إنشاء نفس المعرف مرتين أبدًا (على الأقل في اليوم التالي) لن يتم توليدها مرتين خلال تريليونات السنين). علاوة على ذلك، حتى إذا قام RNGCryptoServiceProvider بإنشاء أرقام عشوائية متكررة عن طريق الخطأ، فهذا لا يفسر سبب استبدال ASP.NET بشكل غامض لمعرف الجلسة الصالح بمعرف جديد (وهو ليس فريدًا).
بناءً على حدسنا، قررنا إلقاء نظرة على التخزين المؤقت للإخراج. عندما يقوم OutputCacheModule بتخزين استجابة HTTP مؤقتًا، يجب الحرص على عدم تخزين رأس Set-Cookie مؤقتًا؛ وإلا فإن الاستجابة المخزنة مؤقتًا التي تحتوي على معرف جلسة جديد ستربط جميع مستلمي الاستجابة المخزنة مؤقتًا (والمستخدم الذي أنشأ طلبه الاستجابة المخزنة مؤقتًا) إلى نفس الجلسة. لقد تحققنا من التعليمات البرمجية المصدر؛ وقد تم تمكين التخزين المؤقت للمخرجات في كلتا الصفحتين على موقع Contoso.com. لقد قمنا بإيقاف تشغيل التخزين المؤقت للإخراج. ونتيجة لذلك، تم تشغيل التطبيق لعدة أيام دون حدوث مشكلة واحدة عبر الجلسات. وبعد ذلك، تم تشغيله دون أي أخطاء لأكثر من عامين. وفي شركة أخرى لديها تطبيق مختلف ومجموعة مختلفة من خوادم الويب، رأينا نفس المشكلة تختفي تمامًا. تمامًا كما هو الحال في Contoso.com، تؤدي إزالة ذاكرة التخزين المؤقت للإخراج إلى حل المشكلة.
وأكدت Microsoft لاحقًا أن هذا السلوك ينبع من مشكلة في OutputCacheModule. (ربما تم إصدار تحديث بحلول الوقت الذي تقرأ فيه هذه المقالة.) عند استخدام ASP.NET مع IIS 6.0 وتمكين التخزين المؤقت لوضع kernel، يفشل OutputCacheModule أحيانًا في إزالة رأس Set-Cookie من الاستجابات المخزنة مؤقتًا التي يمررها إلى http.sys . فيما يلي التسلسل المحدد للأحداث التي تؤدي إلى الخطأ:
• يطلب المستخدم الذي لم يقم بزيارة الموقع مؤخرًا (وبالتالي ليس لديه جلسة مناظرة) صفحة تم تمكين التخزين المؤقت للمخرجات فيها، ولكن مخرجاتها غير متوفرة حاليًا في ذاكرة التخزين المؤقت.
• يقوم الطلب بتنفيذ التعليمات البرمجية التي تصل إلى الجلسة التي تم إنشاؤها مؤخرًا للمستخدم، مما يتسبب في إرجاع ملف تعريف الارتباط لمعرف الجلسة في رأس Set-Cookie للاستجابة.
• يوفر OutputCacheModule الإخراج إلى Http.sys، لكن لا يمكنه إزالة رأس Set-Cookie من الاستجابة.
• يقوم Http.sys بإرجاع الاستجابات المخزنة مؤقتًا للطلبات اللاحقة، مما يؤدي عن طريق الخطأ إلى توصيل مستخدمين آخرين بالجلسة.
المغزى من القصة؟ لا تختلط حالة الجلسة والتخزين المؤقت لإخراج وضع kernel. إذا كنت تستخدم حالة الجلسة في صفحة مع تمكين التخزين المؤقت للمخرجات، وكان التطبيق يعمل على IIS 6.0، فستحتاج إلى إيقاف تشغيل التخزين المؤقت للمخرجات في وضع kernel. ستظل تستفيد من التخزين المؤقت للمخرجات، ولكن نظرًا لأن التخزين المؤقت للمخرجات في وضع kernel أسرع بكثير من التخزين المؤقت العادي للمخرجات، فلن يكون التخزين المؤقت بنفس الكفاءة. لمزيد من المعلومات حول هذه المشكلة، راجع support.microsoft.com/kb/917072.
يمكنك إيقاف تشغيل التخزين المؤقت للمخرجات في وضع kernel لصفحة فردية عن طريق تضمين السمة VaryByParam = "*" في توجيه OutputCache الخاص بالصفحة، على الرغم من أن القيام بذلك قد يؤدي إلى زيادة مفاجئة في متطلبات الذاكرة. هناك طريقة أخرى أكثر أمانًا وهي إيقاف تشغيل التخزين المؤقت لوضع kernel للتطبيق بأكمله عن طريق تضمين العنصر التالي في web.config:
<httpRuntimeenableKernelOutputCache="false" />
يمكنك أيضًا استخدام أحد إعدادات التسجيل لتعطيل التخزين المؤقت للمخرجات في وضع kernel عمومًا، أي تعطيل التخزين المؤقت للمخرجات في وضع kernel لكافة الخوادم. راجع support.microsoft.com/kb/820129 للحصول على التفاصيل.
في كل مرة أسمع فيها تقريرًا من أحد العملاء عن مشكلات محيرة في الجلسة، أسألهم عما إذا كانوا يستخدمون التخزين المؤقت للمخرجات في أي صفحة. إذا كانوا يستخدمون التخزين المؤقت للمخرجات، وكان نظام التشغيل المضيف هو Windows Server 2003، فإنني أوصي بتعطيل التخزين المؤقت للمخرجات في وضع kernel. عادة ما يتم حل المشكلة. إذا لم يتم حل المشكلة، فهذا يعني أن الخطأ موجود في التعليمات البرمجية. كن حذرا!
العودة إلى الأعلى
تذكرة مصادقة النماذج مدى الحياة هل يمكنك تحديد المشكلة بالكود التالي؟
FormsAuthentication.RedirectFromLoginPage(username, true);
قد يبدو هذا الرمز جيدًا، ولكن لا ينبغي أبدًا استخدامه في تطبيق ASP.NET 1.x ما لم يعوض رمز في مكان آخر في التطبيق التأثيرات السلبية لهذا البيان. إذا لم تكن متأكدًا من السبب، استمر في القراءة.
يقوم FormsAuthentication.RedirectFromLoginPage بتنفيذ مهمتين. أولاً، عندما يقوم FormsAuthenticationModule بإعادة توجيه المستخدم إلى صفحة تسجيل الدخول، يقوم FormsAuthentication.RedirectFromLoginPage بإعادة توجيه المستخدم إلى الصفحة التي طلبها في الأصل. ثانيًا، يقوم بإصدار تذكرة مصادقة (يتم حملها عادةً في ملف تعريف ارتباط، ويتم حملها دائمًا في ملف تعريف ارتباط في ASP.NET 1.x) والتي تسمح للمستخدم بالبقاء مصادقًا عليه لفترة زمنية محددة مسبقًا.
المشكلة تكمن في هذه الفترة الزمنية. في ASP.NET 1.x، يؤدي تمرير معلمة أخرى غير صحيحة إلى RedirectFromLoginPage إلى إصدار تذكرة مصادقة مؤقتة تنتهي صلاحيتها بعد 30 دقيقة بشكل افتراضي. (يمكنك تغيير فترة المهلة باستخدام سمة المهلة في عنصر web.config.) ومع ذلك، فإن تمرير معلمة أخرى صحيحة سيؤدي إلى إصدار تذكرة مصادقة دائمة صالحة لمدة 50 عامًا، وهذا يخلق مشكلة لأنه إذا قام شخص ما بسرقة تلك المصادقة! التذكرة، يمكنهم استخدام هوية الضحية للوصول إلى الموقع طوال مدة التذكرة. هناك العديد من الطرق لسرقة تذاكر المصادقة - التحقق من حركة المرور غير المشفرة على نقاط الوصول اللاسلكية العامة، والبرمجة النصية عبر مواقع الويب، والحصول على وصول فعلي إلى كمبيوتر الضحية، وما إلى ذلك - لذا فإن المرور الصحيح إلى RedirectFromLoginPage أكثر أمانًا من تعطيل موقع الويب الخاص بك وليس أفضل بكثير. ولحسن الحظ، تم حل هذه المشكلة في ASP.NET 2.0. يقبل RedirectFromLoginPage الآن المهلات المحددة في web.config لتذاكر المصادقة المؤقتة والدائمة بنفس الطريقة.
أحد الحلول هو عدم تمرير القيمة true مطلقًا في المعلمة الثانية لـ RedirectFromLoginPage في تطبيقات ASP.NET 1.x. ولكن هذا غير عملي لأن صفحات تسجيل الدخول غالبًا ما تحتوي على مربع "احتفظ بتسجيل الدخول" والذي يمكن للمستخدم التحقق منه للحصول على ملف تعريف ارتباط مصادقة دائم وليس مؤقت. الحل الآخر هو استخدام مقتطف التعليمات البرمجية في Global.asax (أو وحدة HTTP إذا كنت تفضل ذلك)، والذي يعدل ملف تعريف الارتباط الذي يحتوي على تذكرة المصادقة الدائمة قبل إعادته إلى المتصفح.
يحتوي الشكل 3 على مقتطف واحد من التعليمات البرمجية. إذا كان مقتطف التعليمات البرمجية هذا موجودًا في Global.asax، فإنه يقوم بتعديل خاصية انتهاء الصلاحية لملف تعريف ارتباط مصادقة النماذج الدائم الصادر بحيث تنتهي صلاحية ملف تعريف الارتباط بعد 24 ساعة. يمكنك ضبط المهلة على أي تاريخ تريده عن طريق تعديل السطر المعلق "تاريخ انتهاء الصلاحية الجديد".
قد تجد أنه من الغريب أن تستدعي طريقة Application_EndRequest طريقة مساعد محلية (GetCookieFromResponse) للتحقق من ملف تعريف ارتباط المصادقة للاستجابة الصادرة. يعد الأسلوب Helper حلاً بديلاً لخلل آخر في ASP.NET 1.1 تسبب في إضافة ملفات تعريف الارتباط الزائفة إلى الاستجابة إذا استخدمت منشئ فهرس سلسلة HttpCookieCollection للتحقق من ملفات تعريف الارتباط غير الموجودة. استخدام مولد فهرس صحيح مثل GetCookieFromResponse يحل المشكلة.
العودة إلى الأعلى حالة العرض: قاتل الأداء الصامت بمعنى ما، حالة العرض هي أعظم شيء على الإطلاق. بعد كل شيء، تتيح حالة العرض للصفحات وعناصر التحكم الحفاظ على الحالة بين عمليات إعادة النشر. ولذلك، لا يتعين عليك كتابة تعليمات برمجية لمنع اختفاء النص الموجود في مربع النص عند النقر فوق زر، أو لإعادة الاستعلام عن قاعدة البيانات وإعادة ربط DataGrid بعد إعادة النشر، كما تفعل في ASP التقليدي.
لكن حالة العرض لها جانب سلبي: عندما تنمو بشكل كبير جدًا، تصبح قاتلة صامتة للأداء. تتخذ بعض عناصر التحكم، مثل مربعات النص، قرارات بناءً على حالة العرض. تحدد عناصر التحكم الأخرى (أبرزها DataGrid وGridView) حالة العرض الخاصة بها استنادًا إلى مقدار المعلومات المعروضة. سأشعر بالخوف إذا عرض GridView 200 أو 300 صف من البيانات. على الرغم من أن حالة عرض ASP.NET 2.0 تبلغ تقريبًا نصف حجم حالة عرض ASP.NET 1.x، إلا أن GridView السيئ يمكن أن يؤدي بسهولة إلى تقليل عرض النطاق الترددي الفعال للاتصال بين المستعرض وخادم الويب بنسبة 50% أو أكثر.
يمكنك إيقاف تشغيل حالة العرض لعناصر التحكم الفردية عن طريق تعيين EnableViewState على false، ولكن بعض عناصر التحكم (خاصة DataGrid) تفقد بعض الوظائف عندما لا تتمكن من استخدام حالة العرض. الحل الأفضل للتحكم في حالة العرض هو الاحتفاظ بها على الخادم. في ASP.NET 1.x، يمكنك تجاوز أساليب LoadPageStateFromPersistenceMedium وSavePageStateToPersistenceMedium الخاصة بالصفحة والتعامل مع حالة العرض بالطريقة التي تريدها. يُظهر الكود الموجود في الشكل 4 تجاوزًا يمنع الاحتفاظ بحالة العرض في الحقول المخفية ويحتفظ بها في حالة الجلسة بدلاً من ذلك. يعد تخزين حالة العرض في حالة الجلسة فعالاً بشكل خاص عند استخدامه مع نموذج عملية حالة الجلسة الافتراضية (أي عندما يتم تخزين حالة الجلسة في عملية عاملة ASP.NET في الذاكرة). في المقابل، إذا تم تخزين حالة الجلسة في قاعدة البيانات، فإن الاختبار فقط يمكنه إظهار ما إذا كان الاحتفاظ بحالة العرض في حالة الجلسة يؤدي إلى تحسين الأداء أو تقليله.
يتم استخدام نفس الأسلوب في ASP.NET 2.0، لكن ASP.NET 2.0 يوفر طريقة أسهل للاستمرار في حالة العرض في حالة الجلسة. أولاً، حدد محول صفحة مخصص يقوم أسلوب GetStatePersister الخاص به بإرجاع مثيل لفئة .NET Framework SessionPageStatePersister:
public class SessionPageStateAdapter :System.Web.UI.Adapters.PageAdapter{public override PageStatePersister GetStatePersister () {return new SessionPageStatePersister(this.Page) }}
بعد ذلك، قم بتسجيل محول الصفحة المخصصة كمحول الصفحة الافتراضي عن طريق وضع ملف App.browsers في مجلد App_Browsers الخاص بالتطبيق الخاص بك كما يلي:
<browsers><browser refID="Default"><controlAdapters><adapter controlType=" System.Web. UI.Page"adapterType="SessionPageStateAdapter" /></controlAdapters></browser></browsers>
(يمكنك تسمية الملف بأي شيء تريده، طالما أنه يحتوي على ملحق .browsers.) بعد ذلك، يقوم ASP.NET بتحميل محول الصفحة ويستخدم SessionPageStatePersister الذي تم إرجاعه للحفاظ على حالة الصفحة بالكامل، بما في ذلك حالة العرض.
أحد عيوب استخدام محول الصفحة المخصصة هو أنه ينطبق بشكل عام على كل صفحة في التطبيق. إذا كنت تفضل الاحتفاظ بحالة عرض بعض الصفحات في حالة الجلسة دون غيرها، فاستخدم الطريقة الموضحة في الشكل 4. كما قد تواجه مشكلات باستخدام هذه الطريقة إذا قام المستخدم بإنشاء نوافذ متصفح متعددة في نفس الجلسة.
العودة إلى الأعلى
حالة جلسة SQL Server: قاتل أداء آخر
يسهّل ASP.NET تخزين حالة الجلسة في قاعدة البيانات: فقط قم بتبديل مفتاح في web.config وسيتم نقل حالة الجلسة بسهولة إلى قاعدة البيانات الخلفية. هذه ميزة مهمة للتطبيقات التي تعمل في مجال الويب لأنها تسمح لكل خادم في المجال بمشاركة مستودع مشترك لحالة الجلسة. يؤدي نشاط قاعدة البيانات المضافة إلى تقليل أداء الطلبات الفردية، لكن قابلية التوسع المتزايدة تعوض الخسارة في الأداء.
يبدو كل هذا جيدًا، ولكن الأمور تتغير عندما تأخذ في الاعتبار بعض النقاط:
• حتى في التطبيقات التي تستخدم حالة الجلسة، فإن معظم الصفحات لا تستخدم حالة الجلسة.
• افتراضيًا، يقوم مدير حالة جلسة ASP.NET بإجراء وصولين (وصول واحد للقراءة ووصول واحد للكتابة) إلى مخزن بيانات الجلسة في كل طلب، بغض النظر عما إذا كانت الصفحة المطلوبة تستخدم حالة الجلسة.
بمعنى آخر، عند استخدام خيار حالة جلسة SQL Server™، فإنك تدفع ثمنًا (وصولان إلى قاعدة البيانات) لكل طلب - حتى عند طلبات الصفحات التي لا علاقة لها بحالة الجلسة. وهذا له تأثير سلبي مباشر على إنتاجية الموقع بأكمله.
الشكل 5 قم بإزالة الوصول غير الضروري إلى قاعدة بيانات حالة الجلسة
فماذا يجب عليك فعله؟ الأمر بسيط: قم بتعطيل حالة الجلسة في الصفحات التي لا تستخدم حالة الجلسة. هذه فكرة جيدة دائمًا، ولكنها مهمة بشكل خاص عندما يتم تخزين حالة الجلسة في قاعدة بيانات. يوضح الشكل 5 كيفية تعطيل حالة الجلسة. إذا كانت الصفحة لا تستخدم حالة الجلسة على الإطلاق، فقم بتضمين EnableSessionState="false" في توجيه الصفحة الخاص بها، مثل هذا:
<%@ Page EnableSessionState="false" ... %>
يمنع هذا التوجيه مدير حالة الجلسة من القراءة والكتابة إلى قاعدة بيانات حالة الجلسة عند كل طلب. إذا كانت الصفحة تقرأ البيانات من حالة الجلسة ولكنها لا تكتب البيانات (أي لا تعدل محتويات جلسة المستخدم)، فقم بتعيين EnableSessionState على ReadOnly كما يلي:
<%@ Page EnableSessionState="ReadOnly" ... %>
أخيرًا، إذا كانت الصفحة تتطلب الوصول للقراءة/الكتابة إلى حالة الجلسة، فاحذف خاصية EnableSessionState أو اضبطها على true:
<%@ Page EnableSessionState="true" ... %>
من خلال التحكم في حالة جلسة العمل بهذه الطريقة، يمكنك التأكد من أن ASP.NET يصل إلى قاعدة بيانات حالة جلسة العمل فقط عند الحاجة إليها حقًا. يعد التخلص من الوصول غير الضروري إلى قاعدة البيانات هو الخطوة الأولى في إنشاء تطبيقات عالية الأداء.
بالمناسبة، الخاصية EnableSessionState عامة. لقد تم توثيق هذه الخاصية منذ الإصدار ASP.NET 1.0، ولكني نادرًا ما أرى المطورين يستفيدون منها. ربما لأنه ليس مهمًا جدًا لنموذج حالة الجلسة الافتراضية في الذاكرة. ولكنه مهم بالنسبة لنموذج SQL Server.
العودة إلى الأعلى الأدوار غير المخزنة مؤقتًا غالبًا ما يظهر البيان التالي في ملف web.config الخاص بتطبيق ASP.NET 2.0 وفي الأمثلة التي تقدم مدير أدوار ASP.NET 2.0:
<roleManager Enabled="true" />
ولكن كما هو موضح أعلاه، فإن هذا البيان له تأثير سلبي كبير على الأداء. هل تعرف لماذا؟
بشكل افتراضي، لا يقوم مدير دور ASP.NET 2.0 بتخزين بيانات الدور مؤقتًا. وبدلاً من ذلك، فإنه يراجع مخزن بيانات الدور في كل مرة يحتاج فيها إلى تحديد الدور، إن وجد، الذي ينتمي إليه المستخدم. وهذا يعني أنه بمجرد مصادقة المستخدم، فإن أي صفحات تستفيد من بيانات الدور (على سبيل المثال، الصفحات التي تستخدم خرائط الموقع مع تمكين إعدادات القطع الأمنية، والصفحات التي تم تقييد الوصول إليها باستخدام توجيهات URL المستندة إلى الدور في web.config) ستتسبب في الدور مدير للاستعلام عن مخزن بيانات الدور. إذا كانت الأدوار مخزنة في قاعدة بيانات، فيمكنك الاستغناء بسهولة عن الوصول إلى قواعد بيانات متعددة لكل طلب. الحل هو تكوين مدير الدور لتخزين بيانات الدور في ملفات تعريف الارتباط مؤقتًا:
<roleManager Enabled="true" castRolesInCookie="true" />
يمكنك استخدام سمات <roleManager> أخرى للتحكم في خصائص ملف تعريف ارتباط الدور - على سبيل المثال، المدة التي يجب أن يظل ملف تعريف الارتباط صالحًا فيها (وبالتالي عدد المرات التي يعود فيها مدير الدور إلى قاعدة بيانات الدور). يتم توقيع ملفات تعريف الارتباط الخاصة بالأدوار وتشفيرها افتراضيًا، وبالتالي يتم تخفيف المخاطر الأمنية، رغم أنها ليست صفرًا.
العودة إلى الأعلىتسلسل خاصية ملف التكوين
توفر خدمة ملفات تعريف ASP.NET 2.0 حلاً جاهزًا لمشكلة الحفاظ على حالة كل مستخدم، مثل تفضيلات التخصيص وتفضيلات اللغة. لاستخدام خدمة ملف التعريف، عليك تحديد ملف تعريف XML الذي يحتوي على السمات التي تريد الاحتفاظ بها نيابة عن مستخدم فردي. يقوم ASP.NET بعد ذلك بتجميع فئة تحتوي على نفس الخصائص وتوفر وصولاً مكتوبًا بقوة إلى مثيلات الفئة من خلال خصائص ملف التكوين المضافة إلى الصفحة.
تعد مرونة الملف الشخصي كبيرة جدًا لدرجة أنها تسمح باستخدام أنواع البيانات المخصصة كخصائص للملف الشخصي. ومع ذلك، هناك مشكلة رأيتها شخصيًا تجعل المطورين يرتكبون الأخطاء. يحتوي الشكل 6 على فئة بسيطة تسمى المنشورات وتعريف الملف الشخصي الذي يستخدم المنشورات كسمة للملف الشخصي. ومع ذلك، تنتج هذه الفئة وملف التكوين هذا سلوكًا غير متوقع في وقت التشغيل. هل يمكنك معرفة السبب؟
تكمن المشكلة في أن المنشورات تحتوي على حقل خاص يسمى _count، والذي يجب إجراء تسلسل له وإلغاء تسلسله من أجل تجميد مثيل الفصل بالكامل وإعادة تجميده. ومع ذلك، لا يتم إجراء تسلسل لـ _count وإلغاء تسلسله لأنه خاص ويستخدم ASP.NET Profile Manager تسلسل XML بشكل افتراضي لإجراء تسلسل للأنواع المخصصة وإلغاء تسلسلها. يتجاهل مُسلسل XML الأعضاء غير العامين. لذلك، يتم إجراء تسلسل لمثيلات المنشورات وإلغاء تسلسلها، ولكن في كل مرة يتم إلغاء تسلسل مثيل فئة، تتم إعادة تعيين _count إلى 0.
أحد الحلول هو جعل _count حقلًا عامًا بدلاً من حقل خاص. الحل الآخر هو تغليف _count بخاصية القراءة/الكتابة العامة. أفضل حل هو وضع علامة على المنشورات على أنها قابلة للتسلسل (باستخدام SerializableAttribute) وتكوين مدير ملف التعريف لاستخدام المُسلسل الثنائي .NET Framework لإجراء تسلسل وإلغاء تسلسل مثيلات الفئة. يحافظ هذا الحل على تصميم الفصل نفسه. على عكس مُسلسلات XML، تقوم المُسلسلات الثنائية بتسلسل الحقول بغض النظر عما إذا كان يمكن الوصول إليها أم لا. يوضح الشكل 7 الإصدار الثابت من فئة المنشورات ويسلط الضوء على تعريف الملف الشخصي المصاحب الذي تم تغييره.
هناك شيء واحد يجب أن تضعه في الاعتبار وهو أنه إذا كنت تستخدم نوع بيانات مخصص كخاصية ملف تعريف وكان نوع البيانات هذا يحتوي على أعضاء بيانات غير عامة يجب إجراء تسلسل لهم لإجراء تسلسل كامل لمثيل من النوع، فاستخدم serializeAs = " Binary" في خصائص إعلان الخاصية وتأكد من أن النوع نفسه قابل للتسلسل. وإلا فلن يتم إجراء التسلسل الكامل، وسوف تضيع الوقت في محاولة تحديد سبب عدم عمل الملف الشخصي.
العودة إلى الأعلى تشبع تجمع مؤشرات الترابط أنا في كثير من الأحيان مندهش جدًا من العدد الفعلي لصفحات ASP.NET التي أراها عند تنفيذ استعلام قاعدة بيانات والانتظار لمدة 15 ثانية أو أكثر حتى يتم إرجاع نتائج الاستعلام. (انتظرت أيضًا 15 دقيقة قبل رؤية نتائج استعلامي!) في بعض الأحيان يكون التأخير نتيجة لا مفر منها للكمية الكبيرة من البيانات التي يتم إرجاعها؛ وفي أحيان أخرى يكون التأخير بسبب سوء تصميم قاعدة البيانات. ولكن بغض النظر عن السبب، فإن استعلامات قاعدة البيانات الطويلة أو أي نوع من عمليات الإدخال/الإخراج الطويلة ستؤدي إلى انخفاض الإنتاجية في تطبيقات ASP.NET.
لقد وصفت هذه المشكلة بالتفصيل من قبل، لذلك لن أخوض في الكثير من التفاصيل هنا. يكفي أن نقول أن ASP.NET يعتمد على تجمع مؤشرات ترابط محدود للتعامل مع الطلبات. إذا كانت كافة سلاسل الرسائل مشغولة في انتظار اكتمال استعلام قاعدة البيانات، أو استدعاء خدمة ويب، أو أي عملية إدخال/إخراج أخرى، فسيتم تحريرها عند انتهاء العملية. مكتمل قبل إصدار سلسلة الرسائل، يجب وضع الطلبات الأخرى في قائمة الانتظار والانتظار. عندما يتم وضع الطلبات في قائمة الانتظار، ينخفض الأداء بشكل كبير. إذا كانت قائمة الانتظار ممتلئة، فإن ASP.NET يتسبب في فشل الطلبات اللاحقة مع وجود خطأ HTTP 503. هذا ليس الوضع الذي نود رؤيته في تطبيق الإنتاج على خادم ويب الإنتاج.
الحل هو الصفحات غير المتزامنة، وهي واحدة من أفضل الميزات غير المعروفة في ASP.NET 2.0. يبدأ طلب صفحة غير متزامنة على مؤشر ترابط، ولكن عندما يبدأ عملية إدخال/إخراج، فإنه يعود إلى مؤشر الترابط هذا وواجهة IAsyncResult الخاصة بـ ASP.NET. عند اكتمال العملية، يقوم الطلب بإعلام ASP.NET من خلال IAsyncResult، ويقوم ASP.NET بسحب مؤشر ترابط آخر من التجمع وإكمال معالجة الطلب. تجدر الإشارة إلى أنه عند حدوث عمليات الإدخال/الإخراج، لا يتم شغل أي مؤشرات ترابط تجمع مؤشرات الترابط. يمكن أن يؤدي ذلك إلى تحسين الإنتاجية بشكل ملحوظ عن طريق منع طلبات الصفحات الأخرى (الصفحات التي لا تقوم بعمليات إدخال/إخراج طويلة) من الانتظار في قائمة الانتظار.
يمكنك قراءة كل ما يتعلق بالصفحات غير المتزامنة في عدد أكتوبر 2005 من مجلة MSDN®. أي صفحة مرتبطة بالإدخال/الإخراج وليست مرتبطة بالآلة وتستغرق وقتًا طويلاً للتنفيذ لديها فرصة جيدة لأن تصبح صفحة غير متزامنة.
عندما أخبر المطورين عن الصفحات غير المتزامنة، غالبًا ما يجيبون بـ "هذا رائع، لكنني لا أحتاج إليها في تطبيقي". وأرد عليهم بـ "هل تحتاج أي من صفحاتك إلى الاستعلام عن قاعدة البيانات؟"
خدمات الويب؟ هل قمت بفحص عدادات أداء ASP.NET للحصول على إحصائيات حول الطلباتالموضوعة
في قائمة الانتظار ومتوسط أوقات الانتظار؟
تتطلب تطبيقات ASP.NET في العالم الحقيقي صفحات غير متزامنة. من فضلك تذكر هذا!
العودة إلى الأعلى انتحال الهوية وتخويل ACL ما يلي هو توجيه تكوين بسيط، ولكنه يجعل عيني تضيء في كل مرة أراها في web.config:
<identity impersonate="true" />
يتيح هذا التوجيه انتحال الهوية من جانب العميل في تطبيقات ASP.NET. يقوم بإرفاق رمز وصول يمثل العميل إلى مؤشر الترابط الذي يتعامل مع الطلب بحيث تكون عمليات التحقق من الأمان التي يقوم بها نظام التشغيل متوافقة مع هوية العميل بدلاً من هوية العملية المنفذة. نادرًا ما تتطلب تطبيقات ASP.NET السخرية؛ تخبرني تجربتي أن المطورين غالبًا ما يقومون بالسخرية لأسباب خاطئة. هذا هو السبب.
يقوم المطورون غالبًا بتمكين الانتحال في تطبيقات ASP.NET بحيث يمكن استخدام أذونات نظام الملفات لتقييد الوصول إلى الصفحات. إذا لم يكن لدى Bob إذن لعرض Salaries.aspx، فسيقوم المطور بتمكين الانتحال بحيث يمكن منع Bob من عرض Salaries.aspx عن طريق تعيين قائمة التحكم بالوصول (ACL) لرفض إذن القراءة لـ Bob. ولكن هناك الخطر الخفي التالي: انتحال الشخصية غير ضروري للحصول على ترخيص ACL. عندما تقوم بتمكين مصادقة Windows في تطبيق ASP.NET، يقوم ASP.NET تلقائيًا بالتحقق من قائمة التحكم بالوصول (ACL) لكل صفحة .aspx مطلوبة ويرفض الطلبات المقدمة من المتصلين الذين ليس لديهم إذن لقراءة الملف. فإنه لا يزال يتصرف بهذه الطريقة حتى لو تم تعطيل المحاكاة.
في بعض الأحيان يكون من الضروري تبرير المحاكاة. ولكن يمكنك عادةً تجنب ذلك من خلال التصميم الجيد. على سبيل المثال، افترض أن Salaries.aspx يستعلم عن قاعدة بيانات للحصول على معلومات الرواتب التي يعرفها المديرون فقط. باستخدام الانتحال، يمكنك استخدام أذونات قاعدة البيانات لحرمان الموظفين غير الإداريين من القدرة على الاستعلام عن بيانات كشوف المرتبات. أو يمكنك تجاهل الانتحال وتقييد الوصول إلى بيانات الرواتب عن طريق تعيين قائمة التحكم بالوصول (ACL) لـ Salaries.aspx بحيث لا يكون لدى غير المسؤولين حق الوصول للقراءة. يوفر النهج الأخير أداءً أفضل لأنه يتجنب السخرية تمامًا. كما أنه يلغي الوصول غير الضروري إلى قاعدة البيانات. لماذا يتم رفض الاستعلام عن قاعدة البيانات لأسباب أمنية فقط؟
بالمناسبة، لقد ساعدت ذات مرة في استكشاف أخطاء تطبيق ASP قديم وإصلاحه والذي كان يتم إعادة تشغيله بشكل دوري بسبب مساحة الذاكرة غير المقيدة. قام مطور عديم الخبرة بتحويل عبارة SELECT الهدف إلى SELECT * دون مراعاة أن الجدول الذي يتم الاستعلام عنه يحتوي على صور كبيرة ومتعددة. تتفاقم المشكلة بسبب حدوث تسرب للذاكرة لم يتم اكتشافه. (منطقة التعليمات البرمجية المُدارة الخاصة بي!) توقف أحد التطبيقات التي كانت تعمل بشكل جيد لسنوات عن العمل فجأة لأن عبارات SELECT التي كانت تستخدم لإرجاع كيلو بايت أو اثنين من البيانات أصبحت الآن تُرجع عدة ميغابايت. أضف إلى ذلك مشكلة التحكم غير الكافي في الإصدار، وستكون حياة فريق التطوير "مفرطة النشاط" - ومن خلال "مفرطة النشاط"، فإن الأمر يشبه الاضطرار إلى مشاهدة أطفالك يلعبون لعبة مزعجة أثناء قيامك بذلك. لعبة كرة القدم مملة في الليل.
من الناحية النظرية، لا يمكن أن يحدث تسرب للذاكرة التقليدية في تطبيقات ASP.NET المكونة بالكامل من تعليمات برمجية مُدارة. ولكن يمكن أن يؤثر الاستخدام غير الكافي للذاكرة على الأداء من خلال فرض عمليات جمع البيانات المهملة بشكل متكرر أكثر. حتى في تطبيقات ASP.NET، كن حذرًا من SELECT *!
العودة إلى الأعلى لا تعتمد عليه بالكامل — قم بإعداد ملف تكوين قاعدة البيانات!
باعتباري مستشارًا، كثيرًا ما يتم سؤالي عن سبب عدم أداء التطبيقات كما هو متوقع. مؤخرًا، سأل أحد الأشخاص فريقي عن سبب قيام تطبيق ASP.NET بإكمال ما يقرب من 1/100 فقط من الإنتاجية (الطلبات في الثانية) المطلوبة لطلب مستند. إن المشكلات التي اكتشفناها من قبل تنفرد بالمشكلات التي رأيناها في تطبيقات الويب التي لم تعمل بشكل صحيح، وهي دروس يجب علينا جميعًا أن نأخذها على محمل الجد.
نقوم بتشغيل SQL Server Profiler ونراقب التفاعل بين هذا التطبيق وقاعدة البيانات الخلفية. وفي حالة أكثر تطرفًا، تسببت نقرة زر واحدة فقط في حدوث أكثر من 1500 خطأ في قاعدة البيانات. لا يمكنك بناء تطبيقات عالية الأداء بهذه الطريقة. تبدأ الهندسة المعمارية الجيدة دائمًا بتصميم جيد لقاعدة البيانات. بغض النظر عن مدى كفاءة الكود الخاص بك ، فلن يعمل إذا تم وزنه بقاعدة بيانات مكتوبة سيئة.
عادةً ما تنتج بنية الوصول إلى البيانات السيئة عن واحد أو أكثر من ما يلي:
• تصميم قاعدة البيانات الضعيفة (عادةً ما يتم تصميمه من قبل المطورين ، وليس مسؤولي قواعد البيانات).
• استخدام مجموعات البيانات و DataAdapters - وخاصة DataAdapter.update ، والتي تعمل بشكل جيد لتطبيقات نماذج Windows والعملاء الأثرياء ، ولكنها غير مثالية بشكل عام لتطبيقات الويب.
• طبقة الوصول إلى البيانات سيئة المصممة (DAL) التي تحتوي على حسابات مبرمجة بشكل سيء وتستهلك العديد من دورات وحدة المعالجة المركزية لأداء عمليات بسيطة نسبيًا.
يجب تحديد المشكلة قبل معالجتها. تتمثل طريقة تحديد مشكلات الوصول إلى البيانات في تشغيل SQL Server Profiler أو أداة مكافئة لمعرفة ما يحدث وراء الكواليس. يتم الانتهاء من ضبط الأداء بعد التحقق من الاتصال بين التطبيق وقاعدة البيانات. جربها - قد تفاجأ بما تجده.
العودة إلى أعلى الخلاصة الآن أنت تعرف بعض المشكلات وحلولها التي قد تواجهها عند بناء تطبيق إنتاج ASP.NET. والخطوة التالية هي إلقاء نظرة فاحصة على الكود الخاص بك ومحاولة تجنب بعض المشكلات التي حددتها هنا. قد يكون ASP.NET قد خفض حاجز الدخول لمطوري الويب ، ولكن تطبيقاتك لديها كل الأسباب لتكون مرنة ومستقرة وفعالة. يرجى النظر في هذا بعناية لتجنب أخطاء المبتدئين.
يوفر الشكل 8 قائمة مرجعية قصيرة يمكنك استخدامها لتجنب المزالق الموضحة في هذه المقالة. يمكنك إنشاء قائمة مراجعة عيب أمان مماثلة. على سبيل المثال:
• هل قمت بتشفير أقسام التكوين التي تحتوي على بيانات حساسة؟
• هل تقوم بالتحقق من الإدخال المستخدم في عمليات قاعدة البيانات والتحقق من صحته وهل تستخدم إدخال HTML المشفر كمخرج؟
• هل يحتوي الدليل الافتراضي الخاص بك على ملفات ذات ملحقات غير محمية؟
هذه الأسئلة مهمة إذا كنت تقدر سلامة موقع الويب الخاص بك ، والخوادم التي تستضيفها ، وموارد الواجهة الخلفية التي يعتمدون عليها.
Jeff Prosise هو محرر مساهم في مجلة MSDN ومؤلف العديد من الكتب ، بما في ذلك برمجة Microsoft .NET (Microsoft Press ، 2002). وهو أيضًا مؤسس مشارك لشركة Wintelect ، وهي شركة استشارية للبرامج والتعليم.
من عدد يوليو 2006 من مجلة MSDN.